[
  {
    "path": ".github/workflows/gh-pages.yml",
    "content": "name: Js\n\non:\n  push:\n    branches:\n      - master  # Set a branch to deploy\n  pull_request:\n\njobs:\n  deploy:\n    runs-on: ubuntu-20.04\n    concurrency:\n      group: ${{ github.workflow }}-${{ github.ref }}\n    steps:\n      - uses: actions/checkout@v3\n        with:\n          submodules: true  # Fetch Hugo themes (true OR recursive)\n          fetch-depth: 0    # Fetch all history for .GitInfo and .Lastmod\n\n      - name: Setup Hugo\n        uses: peaceiris/actions-hugo@v2\n        with:\n          hugo-version: '0.91.2'\n          extended: true\n\n      - name: Build\n        run: hugo -D\n\n      - name: Deploy\n        uses: peaceiris/actions-gh-pages@v3\n        with:\n          external_repository: ten-ltw/JavaScript-The-Definitive-Guide-7th-zh\n          personal_token: ${{ secrets.JS_GITHUB_TOKEN }}\n          publish_dir: ./public\n          publish_branch: page\n\n      - name: Algolia Docsearch Uploader\n        uses: guzhongren/algolia-docsearch-upload-action@v1.0.0\n        env:\n          FILE_PATH: \"./public/index.json\"\n          APPLICATION_ID: ${{secrets.ALGOLIA_APPLICATION_ID}}\n          ADMIN_API_KEY: ${{secrets.ALGOLIA_API_KEY}}\n          INDEX_NAME: \"JS\""
  },
  {
    "path": ".gitignore",
    "content": "# File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig\n\n# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,macos,hugo\n# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,macos,hugo\n\n### Hugo ###\n# Generated files by hugo\n/public/\n/resources/_gen/\n/assets/jsconfig.json\nhugo_stats.json\n\n# Executable may be added to repository\nhugo.exe\nhugo.darwin\nhugo.linux\n\n# Temporary lock file while building\n/.hugo_build.lock\n\n### macOS ###\n# General\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# Icon must end with two \\r\nIcon\r\r\n\n# Thumbnails\n._*\n\n# Files that might appear in the root of a volume\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n.com.apple.timemachine.donotpresent\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n\n### macOS Patch ###\n# iCloud generated files\n*.icloud\n\n### VisualStudioCode ###\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n!.vscode/*.code-snippets\n\n# Local History for Visual Studio Code\n.history/\n\n# Built Visual Studio Code Extensions\n*.vsix\n\n### VisualStudioCode Patch ###\n# Ignore all local history of files\n.history\n.ionide\n\n# Support for Project snippet scope\n.vscode/*.code-snippets\n\n# Ignore code-workspaces\n*.code-workspace\n\n# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,macos,hugo\n\n# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)\n\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"themes/LoveIt\"]\n\tpath = themes/LoveIt\n\turl = https://github.com/dillonzq/LoveIt.git\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021 Mr.Ten\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# JavaScript-The-Definitive-Guide-7th-zh\n\n《JavaScript 权威指南第七版》中英对照\n\n在线阅读：[https://js.okten.cn/](https://js.okten.cn/)\n\n<img src=\"./content/posts/cover.jpg\" width=24% />\n\n## 目录\n\n- [第 1 章 JavaScript 概述](content/posts/ch1.md)\n- [第 2 章 词法结构](content/posts/ch2.md)\n- [第 3 章 类型、值和变量](content/posts/ch3.md)\n- [第 4 章 表达式和运算符](content/posts/ch4.md)\n- [第 5 章 语句](content/posts/ch5.md)\n- [第 6 章 对象](content/posts/ch6.md)\n- [第 7 章 数组](content/posts/ch7.md)\n- [第 8 章 函数](content/posts/ch8.md)\n- [第 9 章 类](content/posts/ch9.md)\n- [第 10 章 模块](content/posts/ch10.md)\n- [第 11 章 JavaScript 标准库](content/posts/ch11.md)\n- [第 12 章 迭代器和生成器](content/posts/ch12.md)\n- [第 13 章 异步 JavaScript](content/posts/ch13.md)\n- [第 14 章 元编程](content/posts/ch14.md)\n- [第 15 章 Web 浏览器中的 JavaScript](content/posts/ch15.md)\n- [第 16 章 服务器端 JavaScript](content/posts/ch16.md)\n- [第 17 章 JavaScript 工具和扩展](content/posts/ch17.md)\n\n模块、标准库选择性阅读\nNode 相关先放一放\n\n## License\n\n[MIT](./LICENSE)\n"
  },
  {
    "path": "archetypes/default.md",
    "content": "---\ntitle: \"{{ replace .Name \"-\" \" \" | title }}\"\ndate: {{ .Date }}\ndraft: true\n---\n\n"
  },
  {
    "path": "config.toml",
    "content": "# for build\nbaseURL = 'https://blog.okten.cn/'\ntheme = \"LoveIt\"\nminify = true\nhtml = \"keep\"\nWhitespace = false\n\ntitle = \"《JavaScript 权威指南第七版》中英对照\"\n# 网站语言, 仅在这里 CN 大写 [\"en\", \"zh-CN\", \"fr\", \"pl\", ...]\nlanguageCode = \"zh-CN\"\n# 语言名称 [\"English\", \"简体中文\", \"Français\", \"Polski\", ...]\nlanguageName = \"简体中文\"\n# 是否包括中日韩文字\nhasCJKLanguage = true\n\n# 作者配置\n[author]\n  name = \"Ten Li\"\n  email = \"li.tw.ten@gmail.com\"\n  link = \"https://blog.okten.cn/\"\n\n# 菜单配置\n[menu]\n  [[menu.main]]\n    weight = 1\n    identifier = \"posts\"\n    # 你可以在名称 (允许 HTML 格式) 之前添加其他信息, 例如图标\n    pre = \"<i class='fas fa-thin fa-archive'></i>\"\n    # 你可以在名称 (允许 HTML 格式) 之后添加其他信息, 例如图标\n    post = \"\"\n    name = \"所有章节\"\n    url = \"/posts/\"\n    # 当你将鼠标悬停在此菜单链接上时, 将显示的标题\n    title = \"\"\n\n[markup]\n  [markup.highlight]\n    noClasses = false\n[module]\n  [module.hugoVersion]\n    extended = true\n    min = \"0.62.0\"\n[params]\n  description = \"This is my blog\"\n  # date format\n  dateFormat = \"2006-01-02\"\n  # website description for RSS, SEO, Open Graph and Twitter Cards\n  # 网站描述, 用于 RSS, SEO, Open Graph 和 Twitter Cards\n  # website images for Open Graph and Twitter Cards\n  # 网站图片, 用于 Open Graph 和 Twitter Cards\n  images = []\n  [params.header]\n    desktopMode = \"fixed\"\n    mobileMode = \"auto\"\n    [params.header.title]\n      # URL of the LOGO\n      # LOGO 的 URL\n      logo = \"\"\n      name = \"《JavaScript 权威指南第七版》中英对照\"\n      pre = \"\"\n      post = \"\"\n\n  # Footer config\n  # 页面底部信息配置\n  [params.footer]\n    enable = true\n    custom = \"\"\n    hugo = false\n    copyright = true\n    author = true\n    since = 2020\n    # ICP info only in China (HTML format is supported)\n    # ICP 备案信息，仅在中国使用 (支持 HTML 格式)\n    icp = \"\"\n    # license info (HTML format is supported)\n    # 许可协议信息 (支持 HTML 格式)\n    license= \"<a rel=‘license external nofollow noopener noreffer’ href=‘https://creativecommons.org/licenses/by-nc/4.0/’ target=‘_blank’>CC BY-NC 4.0</a>\"\n  # Section (all posts) page config\n  [params.section]\n    paginate = 20\n    dateFormat = \"01-02\"\n    # amount of RSS pages\n    # RSS 文章数目\n    rss = 10\n\n  # List (category or tag) page config\n  # List (目录或标签) 页面配置\n  [params.list]\n    # special amount of posts in each list page\n    # list 页面每页显示文章数量\n    paginate = 20\n    # date format (month and day)\n    # 日期格式 (月和日)\n    dateFormat = \"01-02\"\n    # amount of RSS pages\n    # RSS 文章数目\n    rss = 10\n\n  # App icon config\n  # 应用图标配置\n  [params.app]\n    # optional site title override for the app when added to an iOS home screen or Android launcher\n    # 当添加到 iOS 主屏幕或者 Android 启动器时的标题, 覆盖默认标题\n    title = \"《JavaScript 权威指南第七版》中英对照\"\n    # whether to omit favicon resource links\n    # 是否隐藏网站图标资源链接\n    noFavicon = false\n    # modern SVG favicon to use in place of older style .png and .ico files\n    # 更现代的 SVG 网站图标, 可替代旧的 .png 和 .ico 文件\n    svgFavicon = \"\"\n    # Android browser theme color\n    # Android 浏览器主题色\n    themeColor = \"#ffffff\"\n    # Safari mask icon color\n    # Safari 图标颜色\n    iconColor = \"#5bbad5\"\n    # Windows v8-11 tile color\n    # Windows v8-11 磁贴颜色\n    tileColor = \"#da532c\"\n\n  # Search config\n  # 搜索配置\n  [params.search]\n    enable = true\n    # type of search engine [\"lunr\", \"algolia\"]\n    # 搜索引擎的类型 [\"lunr\", \"algolia\"]\n    type = \"algolia\"\n    # max index length of the chunked content\n    # 文章内容最长索引长度\n    contentLength = 4000\n    placeholder = \"搜索文章标题或内容...\"\n    # max number of results length\n    # 最大结果数目\n    maxResultLength = 10\n    # snippet length of the result\n    # 结果内容片段长度\n    snippetLength = 30\n    # HTML tag name of the highlight part in results\n    # 搜索结果中高亮部分的 HTML 标签\n    highlightTag = \"em\"\n    # whether to use the absolute URL based on the baseURL in search index\n    # 是否在搜索索引中使用基于 baseURL 的绝对路径\n    absoluteURL = false\n    [params.search.algolia]\n      index = \"JS\"\n      appID = \"I74G1T3YFH\"\n      searchKey = \"9f39718c1951c948495770664edec6b8\"\n\n  # Home page config\n  # 主页信息设置\n  [params.home]\n    # amount of RSS pages\n    # RSS 文章数目\n    rss = 10\n    # Home page profile\n    # 主页个人信息\n    [params.home.profile]\n      enable = true\n      # Gravatar Email for preferred avatar in home page\n      # Gravatar 邮箱，用于优先在主页显示的头像\n      gravatarEmail = \"\"\n      # URL of avatar shown in home page\n      # 主页显示头像的 URL\n      avatarURL = \"./posts/cover.jpg\"\n      # title shown in home page (HTML format is supported)\n      # 主页显示的网站标题 (支持 HTML 格式)\n      # title = \"<div style='background-color: #b59595;top: 0;left: 0;height: 100%;width: 100%;z-index: -1;position: absolute;'></div>\"\n      # subtitle shown in home page (HTML format is supported)\n      # 主页显示的网站副标题 (允许 HTML 格式)\n      subtitle = \"《JavaScript 权威指南第七版》中英对照\"\n      # whether to use typeit animation for subtitle\n      # 是否为副标题显示打字机动画\n      typeit = true\n      # whether to show social links\n      # 是否显示社交账号\n      social = true\n      # disclaimer (HTML format is supported)\n      # 免责声明 (支持 HTML 格式)\n      disclaimer = \"\"\n    # Home page posts\n    # 主页文章列表\n    [params.home.posts]\n      enable = true\n      # special amount of posts in each home posts page\n      # 主页每页显示文章数量\n      paginate = 6\n  # Social config in home page\n  # 主页的社交信息设置\n  [params.social]\n    GitHub = \"ten-ltw\"\n    Linkedin = \"\"\n    Twitter = \"\"\n    Instagram = \"ten_is_studing\"\n    Facebook = \"\"\n    Telegram = \"\"\n    Medium = \"\"\n    Gitlab = \"\"\n    Youtubelegacy = \"\"\n    Youtubecustom = \"\"\n    Youtubechannel = \"\"\n    Tumblr = \"\"\n    Quora = \"\"\n    Keybase = \"\"\n    Pinterest = \"\"\n    Reddit = \"\"\n    Codepen = \"\"\n    FreeCodeCamp = \"\"\n    Bitbucket = \"\"\n    Stackoverflow = \"\"\n    Weibo = \"\"\n    Odnoklassniki = \"\"\n    VK = \"\"\n    Flickr = \"\"\n    Xing = \"\"\n    Snapchat = \"\"\n    Soundcloud = \"\"\n    Spotify = \"\"\n    Bandcamp = \"\"\n    Paypal = \"\"\n    Fivehundredpx = \"\"\n    Mix = \"\"\n    Goodreads = \"\"\n    Lastfm = \"\"\n    Foursquare = \"\"\n    Hackernews = \"\"\n    Kickstarter = \"\"\n    Patreon = \"\"\n    Steam = \"\"\n    Twitch = \"\"\n    Strava = \"\"\n    Skype = \"\"\n    Whatsapp = \"\"\n    Zhihu = \"dan-ke-58-70\"\n    Douban = \"\"\n    Angellist = \"\"\n    Slidershare = \"\"\n    Jsfiddle = \"\"\n    Deviantart = \"\"\n    Behance = \"\"\n    Dribbble = \"\"\n    Wordpress = \"\"\n    Vine = \"\"\n    Googlescholar = \"\"\n    Researchgate = \"\"\n    Mastodon = \"\"\n    Thingiverse = \"\"\n    Devto = \"\"\n    Gitea = \"\"\n    XMPP = \"\"\n    Matrix = \"\"\n    Bilibili = \"\"\n    Discord = \"\"\n    DiscordInvite = \"\"\n    Lichess = \"\"\n    ORCID = \"\"\n    Pleroma = \"\"\n    Kaggle = \"\"\n    Email = \"li.tw.ten@gmail.com\"\n    Phone = \"\"\n    RSS = \"\"\n\n  # Page global config\n  # 文章页面全局配置\n  [params.page]\n    # whether to hide a page from home page\n    # 是否在主页隐藏一篇文章\n    hiddenFromHomePage = false\n    # whether to hide a page from search results\n    # 是否在搜索结果中隐藏一篇文章\n    hiddenFromSearch = false\n    # whether to enable twemoji\n    # 是否使用 twemoji\n    twemoji = false\n    # whether to enable lightgallery\n    # 是否使用 lightgallery\n    lightgallery = false\n    # whether to enable the ruby extended syntax\n    # 是否使用 ruby 扩展语法\n    ruby = true\n    # whether to enable the fraction extended syntax\n    # 是否使用 fraction 扩展语法\n    fraction = true\n    # whether to enable the fontawesome extended syntax\n    # 是否使用 fontawesome 扩展语法\n    fontawesome = true\n    # whether to show link to Raw Markdown content of the content\n    # 是否显示原始 Markdown 文档内容的链接\n    linkToMarkdown = true\n    # whether to show the full text content in RSS\n    # 是否在 RSS 中显示全文内容\n    rssFullText = false\n    # Table of the contents config\n    # 目录配置\n    [params.page.toc]\n      # whether to enable the table of the contents\n      # 是否使用目录\n      enable = true\n      # whether to keep the static table of the contents in front of the post\n      # 是否保持使用文章前面的静态目录\n      keepStatic = false\n      # whether to make the table of the contents in the sidebar automatically collapsed\n      # 是否使侧边目录自动折叠展开\n      auto = true\n    # Code config\n    # 代码配置\n    [params.page.code]\n      # whether to show the copy button of the code block\n      # 是否显示代码块的复制按钮\n      copy = true\n      # the maximum number of lines of displayed code by default\n      # 默认展开显示的代码行数\n      maxShownLines = 50\n    # KaTeX mathematical formulas config (KaTeX https://katex.org/)\n    # KaTeX 数学公式配置 (KaTeX https://katex.org/)\n    [params.page.math]\n      enable = false\n      # default inline delimiter is $ ... $ and \\( ... \\)\n      # 默认行内定界符是 $ ... $ 和 \\( ... \\)\n      inlineLeftDelimiter = \"\"\n      inlineRightDelimiter = \"\"\n      # default block delimiter is $$ ... $$, \\[ ... \\], \\begin{equation} ... \\end{equation} and some other functions\n      # 默认块定界符是 $$ ... $$, \\[ ... \\],  \\begin{equation} ... \\end{equation} 和一些其它的函数\n      blockLeftDelimiter = \"\"\n      blockRightDelimiter = \"\"\n      # KaTeX extension copy_tex\n      # KaTeX 插件 copy_tex\n      copyTex = true\n      # KaTeX extension mhchem\n      # KaTeX 插件 mhchem\n      mhchem = true\n    # Mapbox GL JS config (Mapbox GL JS https://docs.mapbox.com/mapbox-gl-js)\n    # Mapbox GL JS 配置 (Mapbox GL JS https://docs.mapbox.com/mapbox-gl-js)\n    [params.page.mapbox]\n      # access token of Mapbox GL JS\n      # Mapbox GL JS 的 access token\n      accessToken = \"\"\n      # style for the light theme\n      # 浅色主题的地图样式\n      lightStyle = \"mapbox://styles/mapbox/light-v10?optimize=true\"\n      # style for the dark theme\n      # 深色主题的地图样式\n      darkStyle = \"mapbox://styles/mapbox/dark-v10?optimize=true\"\n      # whether to add NavigationControl (https://docs.mapbox.com/mapbox-gl-js/api/#navigationcontrol)\n      # 是否添加 NavigationControl (https://docs.mapbox.com/mapbox-gl-js/api/#navigationcontrol)\n      navigation = true\n      # whether to add GeolocateControl (https://docs.mapbox.com/mapbox-gl-js/api/#geolocatecontrol)\n      # 是否添加 GeolocateControl (https://docs.mapbox.com/mapbox-gl-js/api/#geolocatecontrol)\n      geolocate = true\n      # whether to add ScaleControl (https://docs.mapbox.com/mapbox-gl-js/api/#scalecontrol)\n      # 是否添加 ScaleControl (https://docs.mapbox.com/mapbox-gl-js/api/#scalecontrol)\n      scale = true\n      # whether to add FullscreenControl (https://docs.mapbox.com/mapbox-gl-js/api/#fullscreencontrol)\n      # 是否添加 FullscreenControl (https://docs.mapbox.com/mapbox-gl-js/api/#fullscreencontrol)\n      fullscreen = true\n    # Social share links in post page\n    # 文章页面的分享信息设置\n    [params.page.share]\n      enable = true\n      Twitter = true\n      Facebook = true\n      Linkedin = false\n      Whatsapp = false\n      Pinterest = false\n      Tumblr = false\n      HackerNews = true\n      Reddit = false\n      VK = false\n      Buffer = false\n      Xing = false\n      Line = true\n      Instapaper = false\n      Pocket = false\n      Flipboard = false\n      Weibo = true\n      Blogger = false\n      Baidu = false\n      Odnoklassniki = false\n      Evernote = false\n      Skype = false\n      Trello = false\n      Mix = false\n    # Comment config\n    # 评论系统设置\n    [params.page.comment]\n      enable = false\n      # Disqus comment config (https://disqus.com/)\n      # Disqus 评论系统设置 (https://disqus.com/)\n      [params.page.comment.disqus]\n        enable = false\n        # Disqus shortname to use Disqus in posts\n        # Disqus 的 shortname，用来在文章中启用 Disqus 评论系统\n        shortname = \"\"\n      # Gitalk comment config (https://github.com/gitalk/gitalk)\n      # Gitalk 评论系统设置 (https://github.com/gitalk/gitalk)\n      [params.page.comment.gitalk]\n        enable = false\n        owner = \"\"\n        repo = \"\"\n        clientId = \"\"\n        clientSecret = \"\"\n      # Valine comment config (https://github.com/xCss/Valine)\n      # Valine 评论系统设置 (https://github.com/xCss/Valine)\n      [params.page.comment.valine]\n        enable = false\n        appId = \"\"\n        appKey = \"\"\n        placeholder = \"\"\n        avatar = \"mp\"\n        meta= \"\"\n        pageSize = 10\n        # automatically adapt the current theme i18n configuration when empty\n        # 为空时自动适配当前主题 i18n 配置\n        lang = \"\"\n        visitor = true\n        recordIP = true\n        highlight = true\n        enableQQ = false\n        serverURLs = \"\"\n        # emoji data file name, default is \"google.yml\"\n        # [\"apple.yml\", \"google.yml\", \"facebook.yml\", \"twitter.yml\"]\n        # located in \"themes/LoveIt/assets/lib/valine/emoji/\" directory\n        # you can store your own data files in the same path under your project:\n        # \"assets/lib/valine/emoji/\"\n        # emoji 数据文件名称, 默认是 \"google.yml\"\n        # [\"apple.yml\", \"google.yml\", \"facebook.yml\", \"twitter.yml\"]\n        # 位于 \"themes/LoveIt/assets/lib/valine/emoji/\" 目录\n        # 可以在你的项目下相同路径存放你自己的数据文件:\n        # \"assets/lib/valine/emoji/\"\n        emoji = \"\"\n      # Facebook comment config (https://developers.facebook.com/docs/plugins/comments)\n      # Facebook 评论系统设置 (https://developers.facebook.com/docs/plugins/comments)\n      [params.page.comment.facebook]\n        enable = false\n        width = \"100%\"\n        numPosts = 10\n        appId = \"\"\n        # automatically adapt the current theme i18n configuration when empty\n        # 为空时自动适配当前主题 i18n 配置\n        languageCode = \"\"\n      # Telegram comments config (https://comments.app/)\n      # Telegram comments 评论系统设置 (https://comments.app/)\n      [params.page.comment.telegram]\n        enable = false\n        siteID = \"\"\n        limit = 5\n        height = \"\"\n        color = \"\"\n        colorful = true\n        dislikes = false\n        outlined = false\n      # Commento comment config (https://commento.io/)\n      # Commento comment 评论系统设置 (https://commento.io/)\n      [params.page.comment.commento]\n        enable = false\n      # utterances comment config (https://utteranc.es/)\n      # utterances comment 评论系统设置 (https://utteranc.es/)\n      [params.page.comment.utterances]\n        enable = false\n        # owner/repo\n        repo = \"\"\n        issueTerm = \"pathname\"\n        label = \"\"\n        lightTheme = \"github-light\"\n        darkTheme = \"github-dark\"\n      # giscus comment config (https://giscus.app/)\n      # giscus comment 评论系统设置 (https://giscus.app/zh-CN)\n      [params.page.comment.giscus]\n        # You can refer to the official documentation of giscus to use the following configuration.\n        # 你可以参考官方文档来使用下列配置\n        enable = false\n        repo = \"\"\n        repoId = \"\"\n        category = \"Announcements\"\n        categoryId = \"\"\n        # automatically adapt the current theme i18n configuration when empty\n        # 为空时自动适配当前主题 i18n 配置\n        lang = \"\"\n        mapping = \"pathname\"\n        reactionsEnabled = \"1\"\n        emitMetadata = \"0\"\n        inputPosition = \"bottom\"\n        lazyLoading = false\n        lightTheme = \"light\"\n        darkTheme = \"dark\"\n    # Third-party library config\n    # 第三方库配置\n    [params.page.library]\n      [params.page.library.css]\n        # someCSS = \"some.css\"\n        # located in \"assets/\" 位于 \"assets/\"\n        # Or 或者\n        # someCSS = \"https://cdn.example.com/some.css\"\n      [params.page.library.js]\n        # someJavascript = \"some.js\"\n        # located in \"assets/\" 位于 \"assets/\"\n        # Or 或者\n        # someJavascript = \"https://cdn.example.com/some.js\"\n    # Page SEO config\n    # 页面 SEO 配置\n    [params.page.seo]\n      # image URL\n      # 图片 URL\n      images = []\n      # Publisher info\n      # 出版者信息\n      [params.page.seo.publisher]\n        name = \"\"\n        logoUrl = \"\"\n\n  # TypeIt config\n  # TypeIt 配置\n  [params.typeit]\n    # typing speed between each step (measured in milliseconds)\n    # 每一步的打字速度 (单位是毫秒)\n    speed = 100\n    # blinking speed of the cursor (measured in milliseconds)\n    # 光标的闪烁速度 (单位是毫秒)\n    cursorSpeed = 1000\n    # character used for the cursor (HTML format is supported)\n    # 光标的字符 (支持 HTML 格式)\n    cursorChar = \"|\"\n    # cursor duration after typing finishing (measured in milliseconds, \"-1\" means unlimited)\n    # 打字结束之后光标的持续时间 (单位是毫秒, \"-1\" 代表无限大)\n    duration = -1\n\n  # Site verification code for Google/Bing/Yandex/Pinterest/Baidu\n  # 网站验证代码，用于 Google/Bing/Yandex/Pinterest/Baidu\n  [params.verification]\n    google = \"\"\n    bing = \"\"\n    yandex = \"\"\n    pinterest = \"\"\n    baidu = \"\"\n\n  # Site SEO config\n  # 网站 SEO 配置\n  [params.seo]\n    # image URL\n    # 图片 URL\n    image = \"\"\n    # thumbnail URL\n    # 缩略图 URL\n    thumbnailUrl = \"\"\n\n  # Analytics config\n  # 网站分析配置\n  [params.analytics]\n    enable = true\n    # Google Analytics\n    [params.analytics.google]\n      id = \"G-BK52F3N7KL\"\n      # whether to anonymize IP\n      # 是否匿名化用户 IP\n      anonymizeIP = true\n    # Fathom Analytics\n    [params.analytics.fathom]\n      id = \"\"\n      # server url for your tracker if you're self hosting\n      # 自行托管追踪器时的主机路径\n      server = \"\"\n    # Plausible Analytics\n    [params.analytics.plausible]\n      dataDomain = \"\"\n    # Yandex Metrica\n    [params.analytics.yandexMetrica]\n      id = \"\"\n\n  # Cookie consent config\n  # Cookie 许可配置\n  [params.cookieconsent]\n    enable = false\n    # text strings used for Cookie consent banner\n    # 用于 Cookie 许可横幅的文本字符串\n    [params.cookieconsent.content]\n      message = \"\"\n      dismiss = \"\"\n      link = \"\"\n\n  # CDN config for third-party library files\n  # 第三方库文件的 CDN 设置\n  [params.cdn]\n    # CDN data file name, disabled by default\n    # [\"jsdelivr.yml\"]\n    # located in \"themes/LoveIt/assets/data/cdn/\" directory\n    # you can store your own data files in the same path under your project:\n    # \"assets/data/cdn/\"\n    # CDN 数据文件名称, 默认不启用\n    # [\"jsdelivr.yml\"]\n    # 位于 \"themes/LoveIt/assets/data/cdn/\" 目录\n    # 可以在你的项目下相同路径存放你自己的数据文件:\n    # \"assets/data/cdn/\"\n    data = \"jsdelivr.yml\"\n\n  # Compatibility config\n  # 兼容性设置\n  [params.compatibility]\n    # whether to use Polyfill.io to be compatible with older browsers\n    # 是否使用 Polyfill.io 来兼容旧式浏览器\n    polyfill = false\n    # whether to use object-fit-images to be compatible with older browsers\n    # 是否使用 object-fit-images 来兼容旧式浏览器\n    objectFit = false\n\n[outputs]\n  home = [\"HTML\", \"RSS\", \"JSON\"]\n"
  },
  {
    "path": "content/posts/ch1.md",
    "content": "---\ntitle: \"第 1 章 JavaScript 概述\"\ndate: 2020-11-02T22:18:42+08:00\n---\n\nJavaScript is the programming language of the web. The overwhelming majority of websites use JavaScript, and all modern web browsers—on desktops, tablets, and phones—include JavaScript interpreters, making JavaScript the most-deployed programming language in history. Over the last decade, Node.js has enabled JavaScript programming outside of web browsers, and the dramatic success of Node means that JavaScript is now also the most-used programming language among software developers. Whether you’re starting from scratch or are already using JavaScript professionally, this book will help you master the language.\n\n> JavaScript 是 web 的编程语言。绝大多数的网站都使用 JavaScript，所有的现代网络浏览器——台式机、平板电脑和手机——都包括 JavaScript 解释器，这使得 JavaScript 成为历史上部署最多的编程语言。在过去的十年中，Node.js 使得 JavaScript 编程可以在 web 浏览器之外进行，Node 的巨大成功意味着 JavaScript 现在也是软件开发人员最常用的编程语言。无论你是从零开始还是已经开始专业地使用 JavaScript，这本书都会帮助你掌握这门语言。\n\nIf you are already familiar with other programming languages, it may help you to know that JavaScript is a high-level, dynamic, interpreted programming language that is well-suited to object-oriented and functional programming styles. JavaScript’s variables are untyped. Its syntax is loosely based on Java, but the languages are otherwise unrelated. JavaScript derives its first-class functions from Scheme and its prototype-based inheritance from the little-known language Self. But you do not need to know any of those languages, or be familiar with those terms, to use this book and learn JavaScript.\n\n> 如果您已经熟悉了其他编程语言，那么了解 JavaScript 是一种高级的、动态的、解释的编程语言可能会对您有所帮助，它非常适合面向对象和函数式编程风格。JavaScript 的变量是无类型的。它的语法不严格地讲基于 Java，但是这两种语言在其他方面是无关的。JavaScript 的 first-class 类型函数衍生于 Scheme，从鲜为人知的 Self 继承基于原型的函数。但您不需要了解任何这些语言，或熟悉这些术语，以使用这本书和学习 JavaScript。\n\nThe name “JavaScript” is quite misleading. Except for a superficial syntactic resemblance, JavaScript is completely different from the Java programming language. And JavaScript has long since outgrown its scripting-language roots to become a robust and efficient general-purpose language suitable for serious software engineering and projects with huge codebases.\n\n> “JavaScript”这个名称很容易引起误解。除了表面上的语法相似之外，JavaScript 与 Java 编程语言完全不同。而且 JavaScript 早已超越了它的脚本语言根基，成为一种健壮、高效的通用语言，适合于具有巨大代码库的严肃软件工程和项目。\n\n#### JAVASCRIPT: NAMES, VERSIONS, AND MODES\n\nJavaScript was created at Netscape in the early days of the web, and technically, “JavaScript” is a trademark licensed from Sun Microsystems (now Oracle) used to describe Netscape’s (now Mozilla’s) implementation of the language. Netscape submitted the language for standardization to ECMA—the European Computer Manufacturer’s Association—and because of trademark issues, the standardized version of the language was stuck with the awkward name “ECMAScript.” In practice, everyone just calls the language JavaScript. This book uses the name “ECMAScript” and the abbreviation “ES” to refer to the language standard and to versions of that standard.\n\n> JavaScript 是在网络早期由网景公司创建的，从技术上讲，“JavaScript”是 Sun Microsystems（现在的 Oracle）授权的商标，用来描述 Netscape 公司（现在的Mozilla 公司）对该语言的实现。Netscape 公司将这种语言提交给欧洲计算机制造商协会（ecma）进行标准化，但由于商标问题，这种语言的标准化版本只能使用一个尴尬的名字“ECMAScript”。实际上，每个人都称这种语言为 JavaScript。本书使用名称“ECMAScript”和缩写“ES”来表示该语言标准和该标准的版本。\n\nFor most of the 2010s, version 5 of the ECMAScript standard has been supported by all web browsers. This book treats ES5 as the compatibility baseline and no longer discusses earlier versions of the language. ES6 was released in 2015 and added major new features—including class and module syntax—that changed JavaScript from a scripting language into a serious, general-purpose language suitable for large-scale software engineering. Since ES6, the ECMAScript specification has moved to a yearly release cadence, and versions of the language—ES2016, ES2017, ES2018, ES2019, and ES2020—are now identified by year of release.\n\n> 对于2010年代的大多数版本，所有 web 浏览器都支持 ECMAScript 标准的第5版。本书将 ES5 作为兼容性基线，不再讨论该语言的早期版本。ES6 于2015年发布，增加了主要的新特性（包括类和模块语法），使 JavaScript 从一种脚本语言变成了一种严肃的、适用于大规模软件工程的通用语言。自 ES6 以来，ECMAScript 规范已经以每年发布一次为基调，该语言的版本——es2016、ES2017、ES2018、ES2019和es2020——现在以发布年份来确定。\n\nAs JavaScript evolved, the language designers attempted to correct flaws in the early (pre-ES5) versions. In order to maintain backward compatibility, it is not possible to remove legacy features, no matter how flawed. But in ES5 and later, programs can opt in to JavaScript’s strict mode in which a number of early language mistakes have been corrected. The mechanism for opting in is the “use strict” directive described in §5.6.3. That section also summarizes the differences between legacy JavaScript and strict JavaScript. In ES6 and later, the use of new language features often implicitly invokes strict mode. For example, if you use the ES6 class keyword or create an ES6 module, then all the code within the class or module is automatically strict, and the old, flawed features are not available in those contexts. This book will cover the legacy features of JavaScript but is careful to point out that they are not available in strict mode.\n\n> 随着 JavaScript 的发展，语言设计者试图纠正早期版本（es5 之前）的缺陷。为了保持向后兼容性，不可能删除遗留特性，无论其缺陷有多大。但在 ES5 及以后版本中，程序可以选择使用 JavaScript 的严格模式，在这种模式中，早期的一些语言错误已经得到了纠正。选择加入的机制是 §5.6.3 中描述的“严格使用”指令。这一节还总结了传统 JavaScript 和严格 JavaScript 之间的区别。在 ES6 及以后版本中，使用新的语言特性通常会隐式地调用 strict 模式。例如，如果您使用 ES6 类关键字或创建 ES6 模块，那么类或模块中的所有代码将自动严格，并且在这些上下文中不能使用旧的、有缺陷的特性。这本书将涵盖 JavaScript 的遗留特性，但小心地指出，它们在严格模式下是不可用的。\n\nTo be useful, every language must have a platform, or standard library, for performing things like basic input and output. The core JavaScript language defines a minimal API for working with numbers, text, arrays, sets, maps, and so on, but does not include any input or output functionality. Input and output (as well as more sophisticated features, such as networking, storage, and graphics) are the responsibility of the “host environment” within which JavaScript is embedded.\n\n> 每种语言都必须有一个平台或标准库来执行基本输入和输出之类的操作。核心 JavaScript 语言定义了一个极小的 API 处理数字、文本、数组、集合、映射等，但不包括任何输入或输出功能。输入和输出（以及更复杂的特性，如网络、存储和图形）由嵌入 JavaScript 的“宿主”负责。\n\nThe original host environment for JavaScript was a web browser, and this is still the most common execution environment for JavaScript code. The web browser environment allows JavaScript code to obtain input from the user’s mouse and keyboard and by making HTTP requests. And it allows JavaScript code to display output to the user with HTML and CSS.\n\n> JavaScript 的原始宿主环境是一个 web 浏览器，这仍然是 JavaScript 代码最常见的执行环境。web 浏览器环境允许 JavaScript 代码通过发送 HTTP 请求从用户的鼠标和键盘获取输入。它允许 JavaScript 代码用 HTML 和 CSS 向用户显示输出。\n\nSince 2010, another host environment has been available for JavaScript code. Instead of constraining JavaScript to work with the APIs provided by a web browser, Node gives JavaScript access to the entire operating system, allowing JavaScript programs to read and write files, send and receive data over the network, and make and serve HTTP requests. Node is a popular choice for implementing web servers and also a convenient tool for writing simple utility scripts as an alternative to shell scripts.\n\n> 从2010年开始，另一个宿主已经可以用于 JavaScript 代码。Node 没有限制 JavaScript 使用 web 浏览器提供的 api，而是允许 JavaScript 访问整个操作系统，允许 JavaScript 程序读和写文件，通过网络发送和接收数据，以及发出和服务 HTTP 请求。Node 是实现 web 服务器的常用选择，也是编写简单实用程序脚本的方便工具，可以作为 shell 脚本的替代。\n\nMost of this book is focused on the JavaScript language itself. Chapter 11 documents the JavaScript standard library, Chapter 15 introduces the web browser host environment, and Chapter 16 introduces the Node host environment.\n\n> 这本书的大部分内容都集中在 JavaScript 语言本身。第 11 章介绍了 JavaScript 标准库，第 15 章介绍了 web 浏览器宿主，第 16 章介绍了 Node 宿主。\n\nThis book covers low-level fundamentals first, and then builds on those to more advanced and higher-level abstractions. The chapters are intended to be read more or less in order. But learning a new programming language is never a linear process, and describing a language is not linear either: each language feature is related to other features, and this book is full of cross-references—sometimes backward and sometimes forward—to related material. This introductory chapter makes a quick first pass through the language, introducing key features that will make it easier to understand the in-depth treatment in the chapters that follow. If you are already a practicing JavaScript programmer, you can probably skip this chapter. (Although you might enjoy reading Example 1-1 at the end of the chapter before you move on.)\n\n> 这本书首先介绍了底层基础知识，然后在这些基础上构建更高级和更高级别的抽象。这些章节的目的是让读者或多或少地按顺序阅读。但是学习一种新的编程语言从来都不是一个线性的过程，描述一种语言也不是线性的：每一种语言的特性都与其他特性相关，这本书充满了交叉引用——有时向后，有时向前的相关材料。这一介绍性章节对该语言进行了一次快速的介绍，并介绍了一些关键特性，这些特性将使后续章节的深入处理更容易理解。如果你已经是一个实践 JavaScript 程序员，你可以跳过这一章。（尽管你可能会喜欢阅读本章末尾的示例 1-1，然后再继续阅读。）\n\n## 1.1 Exploring JavaScript\n\nWhen learning a new programming language, it’s important to try the examples in the book, then modify them and try them again to test your understanding of the language. To do that, you need a JavaScript interpreter.\n\n> 在学习一门新的编程语言时，试一试书中的示例是很重要的，然后修改它们，再试一次来测试你对这门语言的理解。为此，需要一个 JavaScript 解释器。\n\nThe easiest way to try out a few lines of JavaScript is to open up the web developer tools in your web browser (with F12, Ctrl-Shift-I, or Command-Option-I) and select the Console tab. You can then type code at the prompt and see the results as you type. Browser developer tools often appear as panes at the bottom or right of the browser window, but you can usually detach them as separate windows (as pictured in Figure 1-1), which is often quite convenient.\n\n> 尝试几行 JavaScript 的最简单方法是在web浏览器中打开 web developer 工具（使用 F12、Ctrl-Shift-I 或 Command-Option-I）并选择 Console 选项卡。然后可以在提示符处键入代码，并在键入时查看结果。浏览器开发人员工具通常以窗格的形式出现在浏览器窗口的底部或右侧，但是您通常可以将它们分离为单独的窗口（如图1-1所示），这通常非常方便。\n\n<Figures figure=\"1-1\">The JavaScript console in Firefox’s Developer Tools</Figures>\n\nAnother way to try out JavaScript code is to download and install Node from https://nodejs.org. Once Node is installed on your system, you can simply open a Terminal window and type node to begin an interactive JavaScript session like this one:\n\n> 另一种尝试 JavaScript 代码的方法是从[https://nodejs.org](https://nodejs.org)下载并安装 Node。一旦在你的系统上安装了 Node，你可以简单地打开一个终端窗口，输入 Node 来开始一个交互式的 JavaScript 会话，就像这样：\n\n```sh\n$ node\nWelcome to Node.js v12.13.0.\nType \".help\" for more information.\n> .help\n.break    Sometimes you get stuck, this gets you out\n.clear    Alias for .break\n.editor   Enter editor mode\n.exit     Exit the repl\n.help     Print this help message\n.load     Load JS from a file into the REPL session\n.save     Save all evaluated commands in this REPL session to a file\n\nPress ^C to abort current expression, ^D to exit the repl\n> let x = 2, y = 3;\nundefined\n> x + y\n5\n> (x === 2) && (y === 3)\ntrue\n> (x > 3) || (y < 3)\nfalse\n```\n## 1.2 Hello World\n\nWhen you are ready to start experimenting with longer chunks of code, these line-by-line interactive environments may no longer be suitable, and you will probably prefer to write your code in a text editor. From there, you can copy and paste to the JavaScript console or into a Node session. Or you can save your code to a file (the traditional filename extension for JavaScript code is .js) and then run that file of JavaScript code with Node:\n\n> 当您准备开始尝试更长的代码块时，这些逐行交互环境可能不再适合，您可能更喜欢在文本编辑器中编写代码。从那里，您可以复制并粘贴到JavaScript控制台或节点会话中。或者你可以将你的代码保存到一个文件中（传统的 JavaScript 代码的文件名扩展名是 .js），然后用 Node 运行该 JavaScript 代码文件:\n\n```sh\n$ node snippet.js\n```\n\nIf you use Node in a noninteractive manner like this, it won’t automatically print out the value of all the code you run, so you’ll have to do that yourself. You can use the function console.log() to display text and other JavaScript values in your terminal window or in a browser’s developer tools console. So, for example, if you create a hello.js file containing this line of code:\n\n> 如果像这样以非交互的方式使用 Node，它不会自动打印出您运行的所有代码的值，因此您必须自己执行。可以使用 console.log() 函数在终端窗口或浏览器的 developer tools 控制台中显示文本和其他 JavaScript 值。因此，例如，如果你创建一个 hello.js 文件，其中包含这行代码:\n\n```js\nconsole.log(\"Hello World!\");\n```\n\nand execute the file with node hello.js, you’ll see the message “Hello World!” printed out.\n\n> 并执行 `node hello.js` ，你会看到消息“Hello World!”打印出来。\n\nIf you want to see that same message printed out in the JavaScript console of a web browser, create a new file named hello.html, and put this text in it:\n\n> 如果您想在 web 浏览器的 JavaScript 控制台中看到同样的消息打印出来，那么创建一个名为 hello.html 的文件，并把这段文字放进去：\n\n```js\n<script src=\"hello.js\"></script>\n```\n\nThen load hello.html into your web browser using a `file://` URL like this one:\n\n> 然后使用 `file://` URL将 hello.html 加载到浏览器中，如下图所示：\n\n```\nfile:///Users/username/javascript/hello.html\n```\n\nOpen the developer tools window to see the greeting in the console.\n\n> 打开 developer tools 窗口，在控制台中查看问候语。\n\n## 1.3 A Tour of JavaScript\n\nThis section presents a quick introduction, through code examples, to the JavaScript language. After this introductory chapter, we dive into JavaScript at the lowest level: Chapter 2 explains things like JavaScript comments, semicolons, and the Unicode character set. Chapter 3 starts to get more interesting: it explains JavaScript variables and the values you can assign to those variables.\n\n> 本节通过代码示例快速介绍 JavaScript 语言。在这一介绍性章节之后，我们将深入到 JavaScript 的底层:第 2 章说明了 JavaScript 注释、分号和 Unicode 字符集等内容。第 3 章开始变得更有趣：它说明了 JavaScript 变量和你可以为这些变量赋值的值。\n\nHere’s some sample code to illustrate the highlights of those two chapters:\n\n> 下面是一些示例代码，演示这两章的重点内容：\n\n```js\n// Anything following double slashes is an English-language comment.\n// Read the comments carefully: they explain the JavaScript code.\n\n// A variable is a symbolic name for a value.\n// Variables are declared with the let keyword:\nlet x;                     // Declare a variable named x.\n\n// Values can be assigned to variables with an = sign\nx = 0;                     // Now the variable x has the value 0\nx                          // => 0: A variable evaluates to its value.\n\n// JavaScript supports several types of values\nx = 1;                     // Numbers.\nx = 0.01;                  // Numbers can be integers or reals.\nx = \"hello world\";         // Strings of text in quotation marks.\nx = 'JavaScript';          // Single quote marks also delimit strings.\nx = true;                  // A Boolean value.\nx = false;                 // The other Boolean value.\nx = null;                  // Null is a special value that means \"no value.\"\nx = undefined;             // Undefined is another special value like null.\n```\n\nTwo other very important types that JavaScript programs can manipulate are objects and arrays. These are the subjects of Chapters 6 and 7, but they are so important that you’ll see them many times before you reach those chapters:\n\n> JavaScript 程序可以操作的另外两种非常重要的类型是对象和数组。这些是第 6 章和第 7 章的主题，但它们非常重要，在你读到这些章节之前，你会多次看到它们：\n\n```js\n// JavaScript's most important datatype is the object.\n// An object is a collection of name/value pairs, or a string to value map.\nlet book = {               // Objects are enclosed in curly braces.\n    topic: \"JavaScript\",   // The property \"topic\" has value \"JavaScript.\"\n    edition: 7             // The property \"edition\" has value 7\n};                         // The curly brace marks the end of the object.\n\n// Access the properties of an object with . or []:\nbook.topic                 // => \"JavaScript\"\nbook[\"edition\"]            // => 7: another way to access property values.\nbook.author = \"Flanagan\";  // Create new properties by assignment.\nbook.contents = {};        // {} is an empty object with no properties.\n\n// Conditionally access properties with ?. (ES2020):\nbook.contents?.ch01?.sect1 // => undefined: book.contents has no ch01 property.\n\n// JavaScript also supports arrays (numerically indexed lists) of values:\nlet primes = [2, 3, 5, 7]; // An array of 4 values, delimited with [ and ].\nprimes[0]                  // => 2: the first element (index 0) of the array.\nprimes.length              // => 4: how many elements in the array.\nprimes[primes.length-1]    // => 7: the last element of the array.\nprimes[4] = 9;             // Add a new element by assignment.\nprimes[4] = 11;            // Or alter an existing element by assignment.\nlet empty = [];            // [] is an empty array with no elements.\nempty.length               // => 0\n\n// Arrays and objects can hold other arrays and objects:\nlet points = [             // An array with 2 elements.\n    {x: 0, y: 0},          // Each element is an object.\n    {x: 1, y: 1}\n];\nlet data = {                 // An object with 2 properties\n    trial1: [[1,2], [3,4]],  // The value of each property is an array.\n    trial2: [[2,3], [4,5]]   // The elements of the arrays are arrays.\n};\n```\n\n#### COMMENT SYNTAX IN CODE EXAMPLES\n\nYou may have noticed in the preceding code that some of the comments begin with an arrow (=>). These show the value produced by the code before the comment and are my attempt to emulate an interactive JavaScript environment like a web browser console in a printed book.\n\n> 您可能已经注意到，在前面的代码中，一些注释以箭头开头（=>）。这些代码显示了注释之前代码产生的值，我试图模拟交互式 JavaScript 环境，就像印刷书籍中的 web 浏览器控制台一样。\n\nThose // => comments also serve as an assertion, and I’ve written a tool that tests the code and verifies that it produces the value specified in the comment. This should help, I hope, to reduce errors in the book.\n\n> 那些 // => 注释也用作断言，我编写了一个工具来测试代码并验证它是否生成注释中指定的值。我希望这有助于减少书中的错误。\n\nThere are two related styles of comment/assertion. If you see a comment of the form // a == 42, it means that after the code before the comment runs, the variable a will have the value 42. If you see a comment of the form // !, it means that the code on the line before the comment throws an exception (and the rest of the comment after the exclamation mark usually explains what kind of exception is thrown).\n\n> 注释和断言有两种相关的风格。如果您看到 `// a == 42` 形式的注释，这意味着在注释运行之前的代码之后，变量a的值将是42。如果您看到 `// !` 形式的注释，这意味着注释前一行的代码抛出一个异常（感叹号之后的注释的其余部分通常解释抛出的是哪种异常）。\n\nYou’ll see these comments used throughout the book.\n\n> 您将在整本书中看到这些注释。\n\nThe syntax illustrated here for listing array elements within square braces or mapping object property names to property values inside curly braces is known as an initializer expression, and it is just one of the topics of Chapter 4. An expression is a phrase of JavaScript that can be evaluated to produce a value. For example, the use of . and [] to refer to the value of an object property or array element is an expression.\n\n> 这里演示的在方括号内列出数组元素或在花括号内将对象属性名映射到属性值的语法称为初始化表达式，它只是第 4 章的主题之一。表达式是 JavaScript 的一个短语，可以计算它来产生一个值。例如，的使用。和 [] 引用对象属性或数组元素的值是一个表达式。\n\nOne of the most common ways to form expressions in JavaScript is to use operators:\n\n> 在 JavaScript 中形成表达式最常见的方法之一是使用运算符：\n\n```js\n// Operators act on values (the operands) to produce a new value.\n// Arithmetic operators are some of the simplest:\n3 + 2                      // => 5: addition\n3 - 2                      // => 1: subtraction\n3 * 2                      // => 6: multiplication\n3 / 2                      // => 1.5: division\npoints[1].x - points[0].x  // => 1: more complicated operands also work\n\"3\" + \"2\"                  // => \"32\": + adds numbers, concatenates strings\n\n// JavaScript defines some shorthand arithmetic operators\nlet count = 0;             // Define a variable\ncount++;                   // Increment the variable\ncount--;                   // Decrement the variable\ncount += 2;                // Add 2: same as count = count + 2;\ncount *= 3;                // Multiply by 3: same as count = count * 3;\ncount                      // => 6: variable names are expressions, too.\n\n// Equality and relational operators test whether two values are equal,\n// unequal, less than, greater than, and so on. They evaluate to true or false.\nlet x = 2, y = 3;          // These = signs are assignment, not equality tests\nx === y                    // => false: equality\nx !== y                    // => true: inequality\nx < y                      // => true: less-than\nx <= y                     // => true: less-than or equal\nx > y                      // => false: greater-than\nx >= y                     // => false: greater-than or equal\n\"two\" === \"three\"          // => false: the two strings are different\n\"two\" > \"three\"            // => true: \"tw\" is alphabetically greater than \"th\"\nfalse === (x > y)          // => true: false is equal to false\n\n// Logical operators combine or invert boolean values\n(x === 2) && (y === 3)     // => true: both comparisons are true. && is AND\n(x > 3) || (y < 3)         // => false: neither comparison is true. || is OR\n!(x === y)                 // => true: ! inverts a boolean value\n```\n\nIf JavaScript expressions are like phrases, then JavaScript statements are like full sentences. Statements are the topic of Chapter 5. Roughly, an expression is something that computes a value but doesn’t do anything: it doesn’t alter the program state in any way. Statements, on the other hand, don’t have a value, but they do alter the state. You’ve seen variable declarations and assignment statements above. The other broad category of statement is control structures, such as conditionals and loops. You’ll see examples below, after we cover functions.\n\n> 如果 JavaScript 表达式像短语，那么 JavaScript 语句就像完整的句子。这部分是第 5 章的主题。粗略地说，表达式不做任何事情只计算一个值：它不以任何方式改变程序状态。另一方面，语句没有值，但是它们可以改变状态。您已经在上面看到了变量声明和赋值语句。语句的另一大类是控制结构，比如条件语句和循环。在介绍函数之后，您将看到下面的示例。\n\nA function is a named and parameterized block of JavaScript code that you define once, and can then invoke over and over again. Functions aren’t covered formally until Chapter 8, but like objects and arrays, you’ll see them many times before you get to that chapter. Here are some simple examples:\n\n> 函数是一个已命名和参数化的 JavaScript 代码块，您只定义一次，然后可以反复调用它。函数直到第 8 章才正式介绍，但就像对象和数组一样，在进入这一章之前，您将多次看到它们。下面是一些简单的例子：\n\n```js\n// Functions are parameterized blocks of JavaScript code that we can invoke.\nfunction plus1(x) {        // Define a function named \"plus1\" with parameter \"x\"\n    return x + 1;          // Return a value one larger than the value passed in\n}                          // Functions are enclosed in curly braces\n\nplus1(y)                   // => 4: y is 3, so this invocation returns 3+1\n\nlet square = function(x) { // Functions are values and can be assigned to vars\n    return x * x;          // Compute the function's value\n};                         // Semicolon marks the end of the assignment.\n\nsquare(plus1(y))           // => 16: invoke two functions in one expression\n```\n\nIn ES6 and later, there is a shorthand syntax for defining functions. This concise syntax uses => to separate the argument list from the function body, so functions defined this way are known as arrow functions. Arrow functions are most commonly used when you want to pass an unnamed function as an argument to another function. The preceding code looks like this when rewritten to use arrow functions:\n\n> 在 ES6 及以后版本中，有一种用于定义函数的快捷语法。这种简洁的语法使用 => 将参数列表与函数体分开，因此以这种方式定义的函数称为箭头函数。当您希望将一个未命名的函数作为参数传递给另一个函数时，最常用的是箭头函数。前面的代码看起来像这样，当重写使用箭头函数：\n\n```js\nconst plus1 = x => x + 1;   // The input x maps to the output x + 1\nconst square = x => x * x;  // The input x maps to the output x * x\nplus1(y)                    // => 4: function invocation is the same\nsquare(plus1(y))            // => 16\n```\n\nWhen we use functions with objects, we get methods:\n\n> 当我们使用函数和对象时，我们得到方法：\n\n```js\n// When functions are assigned to the properties of an object, we call\n// them \"methods.\"  All JavaScript objects (including arrays) have methods:\nlet a = [];                // Create an empty array\na.push(1,2,3);             // The push() method adds elements to an array\na.reverse();               // Another method: reverse the order of elements\n\n// We can define our own methods, too. The \"this\" keyword refers to the object\n// on which the method is defined: in this case, the points array from earlier.\npoints.dist = function() { // Define a method to compute distance between points\n    let p1 = this[0];      // First element of array we're invoked on\n    let p2 = this[1];      // Second element of the \"this\" object\n    let a = p2.x-p1.x;     // Difference in x coordinates\n    let b = p2.y-p1.y;     // Difference in y coordinates\n    return Math.sqrt(a*a + // The Pythagorean theorem\n                     b*b); // Math.sqrt() computes the square root\n};\npoints.dist()              // => Math.sqrt(2): distance between our 2 points\n```\n\nNow, as promised, here are some functions whose bodies demonstrate common JavaScript control structure statements:\n\n> 现在，如前所述，下面是一些函数，它们的主体演示了常见的 JavaScript 控制结构语句：\n\n```js\n// JavaScript statements include conditionals and loops using the syntax\n// of C, C++, Java, and other languages.\nfunction abs(x) {          // A function to compute the absolute value.\n    if (x >= 0) {          // The if statement...\n        return x;          // executes this code if the comparison is true.\n    }                      // This is the end of the if clause.\n    else {                 // The optional else clause executes its code if\n        return -x;         // the comparison is false.\n    }                      // Curly braces optional when 1 statement per clause.\n}                          // Note return statements nested inside if/else.\nabs(-10) === abs(10)       // => true\n\nfunction sum(array) {      // Compute the sum of the elements of an array\n    let sum = 0;           // Start with an initial sum of 0.\n    for(let x of array) {  // Loop over array, assigning each element to x.\n        sum += x;          // Add the element value to the sum.\n    }                      // This is the end of the loop.\n    return sum;            // Return the sum.\n}\nsum(primes)                // => 28: sum of the first 5 primes 2+3+5+7+11\n\nfunction factorial(n) {    // A function to compute factorials\n    let product = 1;       // Start with a product of 1\n    while(n > 1) {         // Repeat statements in {} while expr in () is true\n        product *= n;      // Shortcut for product = product * n;\n        n--;               // Shortcut for n = n - 1\n    }                      // End of loop\n    return product;        // Return the product\n}\nfactorial(4)               // => 24: 1*4*3*2\n\nfunction factorial2(n) {   // Another version using a different loop\n    let i, product = 1;    // Start with 1\n    for(i=2; i <= n; i++)  // Automatically increment i from 2 up to n\n        product *= i;      // Do this each time. {} not needed for 1-line loops\n    return product;        // Return the factorial\n}\nfactorial2(5)              // => 120: 1*2*3*4*5\n```\n\nJavaScript supports an object-oriented programming style, but it is significantly different than “classical” object-oriented programming languages. Chapter 9 covers object-oriented programming in JavaScript in detail, with lots of examples. Here is a very simple example that demonstrates how to define a JavaScript class to represent 2D geometric points. Objects that are instances of this class have a single method, named distance(), that computes the distance of the point from the origin:\n\n> JavaScript 支持面向对象的编程风格，但它与“经典的”面向对象编程语言有显著的不同。第 9 章详细介绍了 JavaScript 的面向对象编程，并提供了很多示例。下面是一个非常简单的示例，演示了如何定义一个 JavaScript 类来表示 2D 几何点。作为这个类的实例的对象有一个名为 distance() 的方法，它计算点到原点的距离：\n\n```js\nclass Point {              // By convention, class names are capitalized.\n    constructor(x, y) {    // Constructor function to initialize new instances.\n        this.x = x;        // This keyword is the new object being initialized.\n        this.y = y;        // Store function arguments as object properties.\n    }                      // No return is necessary in constructor functions.\n\n    distance() {           // Method to compute distance from origin to point.\n        return Math.sqrt(  // Return the square root of x² + y².\n            this.x * this.x +  // this refers to the Point object on which\n            this.y * this.y    // the distance method is invoked.\n        );\n    }\n}\n\n// Use the Point() constructor function with \"new\" to create Point objects\nlet p = new Point(1, 1);   // The geometric point (1,1).\n\n// Now use a method of the Point object p\np.distance()               // => Math.SQRT2\n```\n\nThis introductory tour of JavaScript’s fundamental syntax and capabilities ends here, but the book continues with self-contained chapters that cover additional features of the language:\n\n> 关于 JavaScript 基本语法和能力的介绍到此结束，但这本书继续以自成体系的章节介绍该语言的其他特性:\n\nChapter 10, Modules\n\nShows how JavaScript code in one file or script can use JavaScript functions and classes defined in other files or scripts.\n\n> 演示一个文件或脚本中的 JavaScript 代码如何使用在其他文件或脚本中定义的 JavaScript 函数和类。\n\nChapter 11, The JavaScript Standard Library\n\nCovers the built-in functions and classes that are available to all JavaScript programs. This includes important data stuctures like maps and sets, a regular expression class for textual pattern matching, functions for serializing JavaScript data structures, and much more.\n\n> 介绍所有 JavaScript 程序可用的内置函数和类。这包括重要的数据结构，如映射和集合、用于文本模式匹配的正则表达式类、用于序列化 JavaScript 数据结构的函数等等。\n\nChapter 12, Iterators and Generators\n\nExplains how the for/of loop works and how you can make your own classes iterable with for/of. It also covers generator functions and the yield statement.\n\n> 解释 for/of 循环是如何工作的，以及如何使用 for/of 使您自己的类可迭代。它还包括生成器函数和 yield 语句。\n\nChapter 13, Asynchronous JavaScript\n\nThis chapter is an in-depth exploration of asynchronous programming in JavaScript, covering callbacks and events, Promise-based APIs, and the async and await keywords. Although the core JavaScript language is not asynchronous, asynchronous APIs are the default in both web browsers and Node, and this chapter explains the techniques for working with those APIs.\n\n> 本章对 JavaScript 中的异步编程进行了深入的探索，涵盖了回调和事件、基于承诺的 api 以及异步和等待关键字。尽管核心 JavaScript 语言不是异步的，异步 api 在 web 浏览器和节点中都是默认的，本章解释了使用这些 api 的技术。\n\nChapter 14, Metaprogramming\n\nIntroduces a number of advanced features of JavaScript that may be of interest to programmers writing libraries of code for other JavaScript programmers to use.\n\n> 介绍了 JavaScript 的许多高级特性，编写代码库供其他 JavaScript 程序员使用的程序员可能会对这些特性感兴趣。\n\nChapter 15, JavaScript in Web Browsers\n\nIntroduces the web browser host environment, explains how web browsers execute JavaScript code, and covers the most important of the many APIs defined by web browsers. This is by far the longest chapter in the book.\n\n> 介绍 web 浏览器宿主，解释 web 浏览器如何执行 JavaScript 代码，并涵盖 web 浏览器定义的许多 api 中最重要的一个。这是书中最长的一章。\n\nChapter 16, Server-Side JavaScript with Node\n\nIntroduces the Node host environment, covering the fundamental programming model and the data structures and APIs that are most important to understand.\n\n> 介绍 Node 宿主，涵盖基本编程模型以及需要理解的最重要的数据结构和 api。\n\nChapter 17, JavaScript Tools and Extensions\n\nCovers tools and language extensions that are worth knowing about because they are widely used and may make you a more productive programmer.\n\n> 涵盖了值得了解的工具和语言扩展，因为它们被广泛使用，可能使您成为更有生产力的程序员。\n\n## 1.4 Example: Character Frequency Histograms\n\nThis chapter concludes with a short but nontrivial JavaScript program. Example 1-1 is a Node program that reads text from standard input, computes a character frequency histogram from that text, and then prints out the histogram. You could invoke the program like this to analyze the character frequency of its own source code:\n\n> 本章以一个简短但不平凡的 JavaScript 程序结束。示例 1-1 是一个节点程序，它从标准输入中读取文本，根据文本计算字符频率直方图，然后打印该直方图。你可以调用这样的程序来分析字符频率自己的源代码：\n\n```sh\n$ node charfreq.js < charfreq.js\nT: ########### 11.22%\nE: ########## 10.15%\nR: ####### 6.68%\nS: ###### 6.44%\nA: ###### 6.16%\nN: ###### 5.81%\nO: ##### 5.45%\nI: ##### 4.54%\nH: #### 4.07%\nC: ### 3.36%\nL: ### 3.20%\nU: ### 3.08%\n/: ### 2.88%\n```\n\nThis example uses a number of advanced JavaScript features and is intended to demonstrate what real-world JavaScript programs can look like. You should not expect to understand all of the code yet, but be assured that all of it will be explained in the chapters that follow.\n\n> 这个示例使用了许多高级 JavaScript 特性，旨在演示真实的 JavaScript 程序是什么样的。你不应该期望理解所有的代码，但是要确保所有的代码将在下面的章节中被解释。\n\nExample 1-1. Computing character frequency histograms with JavaScript\n\n```js\n/**\n * This Node program reads text from standard input, computes the frequency\n * of each letter in that text, and displays a histogram of the most\n * frequently used characters. It requires Node 12 or higher to run.\n *\n * In a Unix-type environment you can invoke the program like this:\n *    node charfreq.js < corpus.txt\n */\n\n// This class extends Map so that the get() method returns the specified\n// value instead of null when the key is not in the map\nclass DefaultMap extends Map {\n    constructor(defaultValue) {\n        super();                          // Invoke superclass constructor\n        this.defaultValue = defaultValue; // Remember the default value\n    }\n\n    get(key) {\n        if (this.has(key)) {              // If the key is already in the map\n            return super.get(key);        // return its value from superclass.\n        }\n        else {\n            return this.defaultValue;     // Otherwise return the default value\n        }\n    }\n}\n\n// This class computes and displays letter frequency histograms\nclass Histogram {\n    constructor() {\n        this.letterCounts = new DefaultMap(0);  // Map from letters to counts\n        this.totalLetters = 0;                  // How many letters in all\n    }\n\n    // This function updates the histogram with the letters of text.\n    add(text) {\n        // Remove whitespace from the text, and convert to upper case\n        text = text.replace(/\\s/g, \"\").toUpperCase();\n\n        // Now loop through the characters of the text\n        for(let character of text) {\n            let count = this.letterCounts.get(character); // Get old count\n            this.letterCounts.set(character, count+1);    // Increment it\n            this.totalLetters++;\n        }\n    }\n\n    // Convert the histogram to a string that displays an ASCII graphic\n    toString() {\n        // Convert the Map to an array of [key,value] arrays\n        let entries = [...this.letterCounts];\n\n        // Sort the array by count, then alphabetically\n        entries.sort((a,b) => {              // A function to define sort order.\n            if (a[1] === b[1]) {             // If the counts are the same\n                return a[0] < b[0] ? -1 : 1; // sort alphabetically.\n            } else {                         // If the counts differ\n                return b[1] - a[1];          // sort by largest count.\n            }\n        });\n\n        // Convert the counts to percentages\n        for(let entry of entries) {\n            entry[1] = entry[1] / this.totalLetters*100;\n        }\n\n        // Drop any entries less than 1%\n        entries = entries.filter(entry => entry[1] >= 1);\n\n        // Now convert each entry to a line of text\n        let lines = entries.map(\n            ([l,n]) => `${l}: ${\"#\".repeat(Math.round(n))} ${n.toFixed(2)}%`\n        );\n\n        // And return the concatenated lines, separated by newline characters.\n        return lines.join(\"\\n\");\n    }\n}\n\n// This async (Promise-returning) function creates a Histogram object,\n// asynchronously reads chunks of text from standard input, and adds those chunks to\n// the histogram. When it reaches the end of the stream, it returns this histogram\nasync function histogramFromStdin() {\n    process.stdin.setEncoding(\"utf-8\"); // Read Unicode strings, not bytes\n    let histogram = new Histogram();\n    for await (let chunk of process.stdin) {\n        histogram.add(chunk);\n    }\n    return histogram;\n}\n\n// This one final line of code is the main body of the program.\n// It makes a Histogram object from standard input, then prints the histogram.\nhistogramFromStdin().then(histogram => { console.log(histogram.toString()); });\n```\n\n## 1.5 Summary\n\nThis book explains JavaScript from the bottom up. This means that we start with low-level details like comments, identifiers, variables, and types; then build to expressions, statements, objects, and functions; and then cover high-level language abstractions like classes and modules. I take the word definitive in the title of this book seriously, and the coming chapters explain the language at a level of detail that may feel off-putting at first. True mastery of JavaScript requires an understanding of the details, however, and I hope that you will make time to read this book cover to cover. But please don’t feel that you need to do that on your first reading. If you find yourself feeling bogged down in a section, simply skip to the next. You can come back and master the details once you have a working knowledge of the language as a whole.\n\n> 这本书从下至上地说明了 JavaScript。这意味着我们从底层细节开始，比如注释、标识符、变量和类型；然后构建表达式、语句、对象和函数；然后介绍高级语言抽象，比如类和模块。我在这本书的标题中认真地使用了“权威”这个词，接下来的章节会详细地解释这种语言，一开始可能会让人感到不快。然而，真正掌握 JavaScript 需要了解细节，我希望您能抽出时间从头到尾阅读这本书。但请不要觉得在第一次阅读时就需要这样做。如果你发现自己在某个章节陷入困境，直接跳到下一个章节。一旦你对语言有了整体的应用知识，你可以回过头来掌握细节。"
  },
  {
    "path": "content/posts/ch10.md",
    "content": "---\ntitle: \"第 10 章 模块\"\ndate: 2020-11-02T22:18:33+08:00\n---\n\nThe goal of modular programming is to allow large programs to be assembled using modules of code from disparate authors and sources and for all of that code to run correctly even in the presence of code that the various module authors did not anticipate. As a practical matter, modularity is mostly about encapsulating or hiding private implementation details and keeping the global namespace tidy so that modules cannot accidentally modify the variables, functions, and classes defined by other modules.\n\n> 模块化编程的目标是允许使用来自不同作者和源的代码模块来组装大型程序，并且即使出现了不同模块作者没有预料到的代码，所有这些代码也能正确运行。作为一个实际问题，模块化主要是关于封装或隐藏私有实现细节和保持全局名称空间整洁，以便模块不会意外地修改其他模块定义的变量、函数和类。\n\nUntil recently, JavaScript had no built-in support for modules, and programmers working on large code bases did their best to use the weak modularity available through classes, objects, and closures. Closure-based modularity, with support from code-bundling tools, led to a practical form of modularity based on a require() function, which was adopted by Node. require()-based modules are a fundamental part of the Node programming environment but were never adopted as an official part of the JavaScript language. Instead, ES6 defines modules using import and export keywords. Although import and export have been part of the language for years, they were only implemented by web browsers and Node relatively recently. And, as a practical matter, JavaScript modularity still depends on code-bundling tools.\n\n> 直到最近，JavaScript 还没有对模块的内置支持，在大型代码库上工作的程序员尽力使用类、对象和闭包的弱模块性。基于闭包的模块化在代码捆绑工具的支持下，实际使用中形成了一种基于 require() 函数的模块化形式，Node 采用了这种形式。基于 require() 的模块是 Node 编程环境的基本部分，但从未被作为 JavaScript 语言的正式部分采用。相反，ES6 使用 import 和 import 关键字定义模块。虽然 import 和 export 已经成为语言的一部分很多年了，但是它们只是最近才被 web 浏览器和 Node 实现。而且，作为一个实际问题，JavaScript 模块化仍然依赖于代码捆绑工具。\n\nThe sections that follow cover:\n\n> 以下各节包括:\n\n- Do-it-yourself modules with classes, objects, and closures\n- Node modules using require()\n- ES6 modules using export, import, and import()\n\n---\n\n> - 使用类、对象和闭包自己做模块\n> - 使用 require() 的 Node 模块\n> - 使用 export、import 和 import() 的ES6模块\n\n## 10.1 Modules with Classes, Objects, and Closures\n\nThough it may be obvious, it is worth pointing out that one of the important features of classes is that they act as modules for their methods. Think back to Example 9-8. That example defined a number of different classes, all of which had a method named has(). But you would have no problem writing a program that used multiple set classes from that example: there is no danger that the implementation of has() from SingletonSet will overwrite the has() method of BitSet, for example.\n\n> 尽管这可能很明显，但值得指出的是，类的重要特性之一是它们充当其方法的模块。回想一下示例 9-8。该示例定义了许多不同的类，所有这些类都有一个名为 has() 的方法。但是，在编写使用该示例中的多个 set 类的程序时没有问题：例如，SingletonSet 的 has() 实现不会覆盖 BitSet 的 has() 方法。\n\nThe reason that the methods of one class are independent of the methods of other, unrelated classes is that the methods of each class are defined as properties of independent prototype objects. The reason that classes are modular is that objects are modular: defining a property in a JavaScript object is a lot like declaring a variable, but adding properties to objects does not affect the global namespace of a program, nor does it affect the properties of other objects. JavaScript defines quite a few mathematical functions and constants, but instead of defining them all globally, they are grouped as properties of a single global Math object. This same technique could have been used in Example 9-8. Instead of defining global classes with names like SingletonSet and BitSet, that example could have been written to define only a single global Sets object, with properties referencing the various classes. Users of this Sets library could then refer to the classes with names like Sets.Singleton and Sets.Bit.\n\n> 一个类的方法独立于其他不相关类的方法的原因是，每个类的方法都被定义为独立原型对象的属性。类是模块化的原因是对象是模块化的：在 JavaScript 对象中定义属性非常类似于声明变量，但是向对象添加属性不会影响程序的全局命名空间，也不会影响其他对象的属性。JavaScript 定义了很多数学函数和常量，但它们不是全局定义的，而是分组为 Math 全局对象的单个属性。同样的技术也可以用在示例 9-8 中。不使用 SingletonSet 和 BitSet 这样的名称定义全局类，这个示例可以编写为只有一个 Sets 全局对象，Sets 的属性引用各种类。然后，用户可以使用这个 Sets 库通过 Sets.Singleton 和 Sets.Bit 的名称来获取类的引用。\n\nUsing classes and objects for modularity is a common and useful technique in JavaScript programming, but it doesn’t go far enough. In particular, it doesn’t offer us any way to hide internal implementation details inside the module. Consider Example 9-8 again. If we were writing that example as a module, maybe we would have wanted to keep the various abstract classes internal to the module, only making the concrete subclasses available to users of the module. Similarly, in the BitSet class, the _valid() and _has() methods are internal utilities that should not really be exposed to users of the class. And BitSet.bits and BitSet.masks are implementation details that would be better off hidden.\n\n> 使用类和对象实现模块化是 JavaScript 编程中常见而有用的技术，但这还不够。特别是，它没有提供任何方法来隐藏模块内部的实现细节。再次考虑示例 9-8。如果我们将该示例作为一个模块来编写，也许我们会希望将各种抽象类保留在模块内部，只让具体的子类对模块的用户可用。同样，在 BitSet 类中，_valid() 和 _has() 方法是内部实用程序，不应该向类的用户公开它们。BitSet.bits 和 BitSet.masks 是实现细节，最好隐藏起来。\n\nAs we saw in §8.6, local variables and nested functions declared within a function are private to that function. This means that we can use immediately invoked function expressions to achieve a kind of modularity by leaving the implementation details and utility functions hidden within the enclosing function but making the public API of the module the return value of the function. In the case of the BitSet class, we might structure the module like this:\n\n> 正如我们在 §8.6 中看到的，在函数中声明的局部变量和嵌套函数是该函数私有的。这意味着我们可以使用立即调用函数表达式来实现一种模块化，方法是将实现细节和实用函数隐藏在封装的函数中，而将模块的公共 API 作为函数的返回值。在 BitSet 类的情况下，我们可以像这样构造模块:\n\n```js\nconst BitSet = (function() { // Set BitSet to the return value of this function\n    // Private implementation details here\n    function isValid(set, n) { ... }\n    function has(set, byte, bit) { ... }\n    const BITS = new Uint8Array([1, 2, 4, 8, 16, 32, 64, 128]);\n    const MASKS = new Uint8Array([~1, ~2, ~4, ~8, ~16, ~32, ~64, ~128]);\n\n    // The public API of the module is just the BitSet class, which we define\n    // and return here. The class can use the private functions and constants\n    // defined above, but they will be hidden from users of the class\n    return class BitSet extends AbstractWritableSet {\n        // ... implementation omitted ...\n    };\n}());\n```\n\nThis approach to modularity becomes a little more interesting when the module has more than one item in it. The following code, for example, defines a mini statistics module that exports mean() and stddev() functions while leaving the implementation details hidden:\n\n> 当模块中有多个条目时，这种模块化方法就变得更有趣了。例如，下面的代码定义了一个迷你统计模块，它导出 mean() 和 stddev() 函数，同时隐藏实现细节:\n\n```js\n// This is how we could define a stats module\nconst stats = (function() {\n    // Utility functions private to the module\n    const sum = (x, y) => x + y;\n    const square = x => x * x;\n\n    // A public function that will be exported\n    function mean(data) {\n        return data.reduce(sum)/data.length;\n    }\n\n    // A public function that we will export\n    function stddev(data) {\n        let m = mean(data);\n        return Math.sqrt(\n            data.map(x => x - m).map(square).reduce(sum)/(data.length-1)\n        );\n    }\n\n    // We export the public function as properties of an object\n    return { mean, stddev };\n}());\n\n// And here is how we might use the module\nstats.mean([1, 3, 5, 7, 9])   // => 5\nstats.stddev([1, 3, 5, 7, 9]) // => Math.sqrt(10)\n```\n### 10.1.1 Automating Closure-Based Modularity\nNote that it is a fairly mechanical process to transform a file of JavaScript code into this kind of module by inserting some text at the beginning and end of the file. All that is needed is some convention for the file of JavaScript code to indicate which values are to be exported and which are not.\n\n> 请注意，通过在文件的开头和结尾插入一些文本来将 JavaScript 代码文件转换为这种模块是相当机械的过程。所需要做的只是为 JavaScript 代码文件提供一些约定，以指示要导出的值和不导出的值。\n\nImagine a tool that takes a set of files, wraps the content of each of those files within an immediately invoked function expression, keeps track of the return value of each function, and concatenates everything into one big file. The result might look something like this:\n\n> 想象一下一个工具，它需要一组文件，将每个文件的内容包装在立即调用的函数表达式中，跟踪每个函数的返回值，并将所有内容连接到一个大文件中。结果可能看起来像这样：\n\n```js\nconst modules = {};\nfunction require(moduleName) { return modules[moduleName]; }\n\nmodules[\"sets.js\"] = (function() {\n    const exports = {};\n\n    // The contents of the sets.js file go here:\n    exports.BitSet = class BitSet { ... };\n\n    return exports;\n}());\n\nmodules[\"stats.js\"] = (function() {\n    const exports = {};\n\n    // The contents of the stats.js file go here:\n    const sum = (x, y) => x + y;\n    const square = x = > x * x;\n    exports.mean = function(data) { ... };\n    exports.stddev = function(data) { ... };\n\n    return exports;\n}());\n```\nWith modules bundled up into a single file like the one shown in the preceding example, you can imagine writing code like the following to make use of those modules:\n\n> 如上一个示例所示，将模块捆绑到单个文件中，可以想象编写如下代码来利用这些模块：\n\n```js\n// Get references to the modules (or the module content) that we need\nconst stats = require(\"stats.js\");\nconst BitSet = require(\"sets.js\").BitSet;\n\n// Now write code using those modules\nlet s = new BitSet(100);\ns.insert(10);\ns.insert(20);\ns.insert(30);\nlet average = stats.mean([...s]); // average is 20\n```\nThis code is a rough sketch of how code-bundling tools (such as webpack and Parcel) for web browsers work, and it’s also a simple introduction to the require() function like the one used in Node programs.\n\n> 这段代码概述了用于网络浏览器的代码捆绑工具（例如 webpack 和 Parcel）的工作方式，并且是对 require() 函数（如 Node 程序中使用的函数）的简单介绍。\n\n## 10.2 Modules in Node\nIn Node programming, it is normal to split programs into as many files as seems natural. These files of JavaScript code are assumed to all live on a fast filesystem. Unlike web browsers, which have to read files of JavaScript over a relatively slow network connection, there is no need or benefit to bundling a Node program into a single JavaScript file.\n\nIn Node, each file is an independent module with a private namespace. Constants, variables, functions, and classes defined in one file are private to that file unless the file exports them. And values exported by one module are only visible in another module if that module explicitly imports them.\n\nNode modules import other modules with the require() function and export their public API by setting properties of the Exports object or by replacing the module.exportsobject entirely.\n\n### 10.2.1 Node Exports\nNode defines a global exports object that is always defined. If you are writing a Node module that exports multiple values, you can simply assign them to the properties of this object:\n```js\nconst sum = (x, y) => x + y;\nconst square = x => x * x;\n\nexports.mean = data => data.reduce(sum)/data.length;\nexports.stddev = function(d) {\n    let m = exports.mean(d);\n    return Math.sqrt(d.map(x => x - m).map(square).reduce(sum)/(d.length-1));\n};\n```\nOften, however, you want to define a module that exports only a single function or class rather than an object full of functions or classes. To do this, you simply assign the single value you want to export to module.exports:\n```js\nmodule.exports = class BitSet extends AbstractWritableSet {\n    // implementation omitted\n};\n```\nThe default value of module.exports is the same object that exports refers to. In the previous stats module, we could have assigned the mean function to module.exports.mean instead of exports.mean. Another approach with modules like the stats module is to export a single object at the end of the module rather than exporting functions one by one as you go:\n```js\n// Define all the functions, public and private\nconst sum = (x, y) => x + y;\nconst square = x => x * x;\nconst mean = data => data.reduce(sum)/data.length;\nconst stddev = d => {\n    let m = mean(d);\n    return Math.sqrt(d.map(x => x - m).map(square).reduce(sum)/(d.length-1));\n};\n\n// Now export only the public ones\nmodule.exports = { mean, stddev };\n```\n### 10.2.2 Node Imports\nA Node module imports another module by calling the require() function. The argument to this function is the name of the module to be imported, and the return value is whatever value (typically a function, class, or object) that module exports.\n\nIf you want to import a system module built in to Node or a module that you have installed on your system via a package manager, then you simply use the unqualified name of the module, without any “/” characters that would turn it into a filesystem path:\n```js\n// These modules are built in to Node\nconst fs = require(\"fs\");           // The built-in filesystem module\nconst http = require(\"http\");       // The built-in HTTP module\n\n// The Express HTTP server framework is a third-party module.\n// It is not part of Node but has been installed locally\nconst express = require(\"express\");\n```\nWhen you want to import a module of your own code, the module name should be the path to the file that contains that code, relative to the current module’s file. It is legal to use absolute paths that begin with a / character, but typically, when importing modules that are part of your own program, the module names will begin with ./ or sometimes ../ to indicate that they are relative to the current directory or the parent directory. For example:\n```js\nconst stats = require('./stats.js');\nconst BitSet = require('./utils/bitset.js');\n```\n(You can also omit the .js suffix on the files you’re importing and Node will still find the files, but it is common to see these file extensions explicitly included.)\n\nWhen a module exports just a single function or class, all you have to do is require it. When a module exports an object with multiple properties, you have a choice: you can import the entire object, or just import the specific properties (using destructuring assignment) of the object that you plan to use. Compare these two approaches:\n```js\n// Import the entire stats object, with all of its functions\nconst stats = require('./stats.js');\n\n// We've got more functions than we need, but they're neatly\n// organized into a convenient \"stats\" namespace.\nlet average = stats.mean(data);\n\n// Alternatively, we can use idiomatic destructuring assignment to import\n// exactly the functions we want directly into the local namespace:\nconst { stddev } = require('./stats.js');\n\n// This is nice and succinct, though we lose a bit of context\n// without the 'stats' prefix as a namspace for the stddev() function.\nlet sd = stddev(data);\n```\n### 10.2.3 Node-Style Modules on the Web\nModules with an Exports object and a require() function are built in to Node. But if you’re willing to process your code with a bundling tool like webpack, then it is also possible to use this style of modules for code that is intended to run in web browsers. Until recently, this was a very common thing to do, and you may see lots of web-based code that still does it.\n\n> Node 中内置了带有 Exports 对象和 require() 函数的模块。 但是，如果要使用诸如 webpack 之类的捆绑工具来处理代码，则也可以将这种样式的模块用于要在网络浏览器中运行的代码。直到最近，这还是一种非常常用的做法，可能会看到很多基于 Web 的代码仍然这样做。\n\nNow that JavaScript has its own standard module syntax, however, developers who use bundlers are more likely to use the official JavaScript modules with import and export statements.\n\n> 现在，JavaScript 具有自己的标准模块语法，但是使用捆绑程序的开发人员更喜欢将正式的 JavaScript 模块与 import 和 export 语句一起使用。\n\n## 10.3 Modules in ES6\nES6 adds import and export keywords to JavaScript and finally supports real modularity as a core language feature. ES6 modularity is conceptually the same as Node modularity: each file is its own module, and constants, variables, functions, and classes defined within a file are private to that module unless they are explicitly exported. Values that are exported from one module are available for use in modules that explicitly import them. ES6 modules differ from Node modules in the syntax used for exporting and importing and also in the way that modules are defined in web browsers. The sections that follow explain these things in detail.\n\n> ES6 给 JavaScript 添加了 import 和 export 关键字，并且最终支持真正的模块化，将其作为核心语言特性。ES6 模块化概念上和 Node 的模块化相同：每个文件是它们自己的模块，定义在文件中的常量、变量、函数和类是模块私有成员，除非它们是被显示导出。\n\nFirst, though, note that ES6 modules are also different from regular JavaScript “scripts” in some important ways. The most obvious difference is the modularity itself: in regular scripts, top-level declarations of variables, functions, and classes go into a single global context shared by all scripts. With modules, each file has its own private context and can use the import and export statements, which is the whole point, after all. But there are other differences between modules and scripts as well. Code inside an ES6 module (like code inside any ES6 class definition) is automatically in strict mode (see §5.6.3). This means that, when you start using ES6 modules, you’ll never have to write \"use strict\" again. And it means that code in modules cannot use the with statement or the arguments object or undeclared variables. ES6 modules are even slightly stricter than strict mode: in strict mode, in functions invoked as functions, this is undefined. In modules, this is undefined even in top-level code. (By contrast, scripts in web browsers and Node set this to the global object.)\n\n> 首先，请注意，ES6 模块在某些重要方面也与常规 JavaScript “脚本”不同。最明显的区别是模块化本身：在常规脚本中，变量，函数和类的顶级声明在所有脚本共享的一个全局上下文中。模块每个文件都有其自己的专用上下文，并且可以使用 import 和 export 语句。但是模块和脚本之间也存在其他差异。ES6 模块内的代码（类似 ES6 类内定义的代码）将自动进入严格模式（请参见 §5.6.3）。这意味着，当使用 ES6 模块时，无需再写“use strict”。这意味着模块中的代码不能使用 with 语句或 arguments 对象或未声明的变量。ES6 模块甚至比严格模式稍微严格：在严格模式下，在作为函数调用的函数中，this 是 undefined。在模块中，this 在顶层代码中也是 undefined。（相比之下，Web 浏览器和 Node 中的脚本将 this 设置为全局对象。）\n\n#### ES6 MODULES ON THE WEB AND IN NODE\nES6 modules have been in use on the web for years with the help of code bundlers like webpack, which combine independent modules of JavaScript code into large, non-modular bundles suitable for inclusion into web pages. At the time of this writing, however, ES6 modules are finally supported natively by all web browsers other than Internet Explorer. When used natively, ES6 modules are added into HTML pages with a special `<script type=\"module\">` tag, described later in this chapter.\n\n> ES6模块在诸如 webpack 之类的代码打包器的帮助下已在 Web 上使用多年，该打包器将 JavaScript 代码的独立模块组合成适合于包含在网页中的大型非模块化捆绑包。但是，在撰写本文时，除 Internet Explorer 之外，所有 Web 浏览器都支持 ES6 模块。本地使用时，ES6 模块通过特殊的 `<script type=\"module\">` 标签添加到HTML页面中，本章稍后将进行介绍。\n\nAnd meanwhile, having pioneered JavaScript modularity, Node finds itself in the awkward position of having to support two not entirely compatible module systems. Node 13 supports ES6 modules, but for now, the vast majority of Node programs still use Node modules.\n\n> 同时，由于拥有 JavaScript 模块化的先驱，Node 处于必须支持两个不完全兼容的模块系统的尴尬境地。Node 13 支持 ES6 模块，但是到目前为止，绝大多数 Node 程序仍在使用 Node 模块。\n\n### 10.3.1 ES6 Exports\nTo export a constant, variable, function, or class from an ES6 module, simply add the keyword export before the declaration:\n\n> 要从 ES6 模块导出常量，变量，函数或类，只需在声明之前添加关键字 export：\n\n```js\nexport const PI = Math.PI;\n\nexport function degreesToRadians(d) { return d * PI / 180; }\n\nexport class Circle {\n    constructor(r) { this.r = r; }\n    area() { return PI * this.r * this.r; }\n}\n```\nAs an alternative to scattering export keywords throughout your module, you can define your constants, variables, functions, and classes as you normally would, with no export statement, and then (typically at the end of your module) write a single export statement that declares exactly what is exported in a single place. So instead of writing three individual exports in the preceding code, we could have equivalently written a single line at the end:\n\n> 作为在整个模块中散播 export 关键字的替代方法，可以像平常一样定义常量、变量、函数和类，无需导出语句，然后（通常在模块末尾）编写一个导出语句，在一个位置精确的声明要导出的所有内容。因此，我们可以在末尾写入一行代码，而不是在上述代码中编写三个单独的导出：\n\n```js\nexport { Circle, degreesToRadians, PI };\n```\nThis syntax looks like the export keyword followed by an object literal (using shorthand notation). But in this case, the curly braces do not actually define an object literal. This export syntax simply requires a comma-separated list of identifiers within curly braces.\n\n> 这种语法看起来像导出关键字，后跟对象字面量（使用速记符号）。但在这种情况下，花括号实际上并不是定义对象字面量。此导出语法只需要在大括号中的标识符列表用逗号分隔。\n\nIt is common to write modules that export only one value (typically a function or class), and in this case, we usually use export default instead of export:\n\n> 编写只导出一个值（通常是一个函数或类）的模块是很常见的，在这种情况下，我们通常使用 export default 而不是 export：\n\n```js\nexport default class BitSet {\n    // implementation omitted\n}\n```\nDefault exports are slightly easier to import than non-default exports, so when there is only one exported value, using export default makes things easier for the modules that use your exported value.\n\n> 默认导出比非默认导出更容易导入，因此，当只有一个导出值时，使用默认导出可以使模块的使用更容易。\n\nRegular exports with export can only be done on declarations that have a name. Default exports with export default can export any expression including anonymous function expressions and anonymous class expressions. This means that if you use export default, you can export object literals. So unlike the export syntax, if you see curly braces after export default, it really is an object literal that is being exported.\n\n> 带 export 的常规导出只能在具有名称的声明上进行。export default 默认导出可以导出任何表达式，包括匿名函数表达式和匿名类表达式。这意味着，如果使用默认导出，则可以导出对象字面量。因此，与导出语法不同，如果在默认导出后看到花括号，则它实际上是要导出的对象字面量。\n\nIt is legal, but somewhat uncommon, for modules to have a set of regular exports and also a default export. If a module has a default export, it can only have one.\n\n> 同时有 export 和 export default 的模块是合法的，但是不常用。如果模块的默认导出最多仅有一个。\n\nFinally, note that the export keyword can only appear at the top level of your JavaScript code. You may not export a value from within a class, function, loop, or conditional. (This is an important feature of the ES6 module system and enables static analysis: a modules export will be the same on every run, and the symbols exported can be determined before the module is actually run.)\n\n> 最后，请注意 export 关键字只能出现在 JavaScript 代码的顶层。 不能从类，函数，循环或条件内导出值。（这是 ES6 模块系统的重要特性，并且可以进行静态分析：每次运行时模块导出都是相同的，并且可以在模块实际运行之前确定导出的符号。）\n\n### 10.3.2 ES6 Imports\nYou import values that have been exported by other modules with the import keyword. The simplest form of import is used for modules that define a default export:\n\n> 使用 import 关键字导入其他模块导出的值。最简单的导入形式是导入默认导出定义的模块：\n\n```js\nimport BitSet from './bitset.js';\n```\nThis is the import keyword, followed by an identifier, followed by the from keyword, followed by a string literal that names the module whose default export we are importing. The default export value of the specified module becomes the value of the specified identifier in the current module.\n\n> import 关键字后面跟一个标识符，然后是 from 关键字，后接要导入的默认导出字符串字面量模块名称。模块指定的默认导出值将编程当前模块内指定的标识符的值。\n\nThe identifier to which the imported value is assigned is a constant, as if it had been declared with the const keyword. Like exports, imports can only appear at the top level of a module and are not allowed within classes, functions, loops, or conditionals. By near-universal convention, the imports needed by a module are placed at the start of the module. Interestingly, however, this is not required: like function declarations, imports are “hoisted” to the top, and all imported values are available for any of the module’s code runs.\n\n> 赋导入值的标识符是一个常量，就像它使用了 const 关键字声明一样。和导出一样，只能在模块的顶层导入，不能在类、函数、循环或条件中导入。根据近似通用规约，需要在模块的开头进行模块导入。但有趣的是，这不是必须的：就像函数声明，导入“提前”到顶部，所有的导入值在整个模块代码中运行时可以使用。\n\nThe module from which a value is imported is specified as a constant string literal in single quotes or double quotes. (You may not use a variable or other expression whose value is a string, and you may not use a string within backticks because template literals can interpolate variables and do not always have constant values.) In web browsers, this string is interpreted as a URL relative to the location of the module that is doing the importing. (In Node, or when using a bundling tool, the string is interpreted as a filename relative to the current module, but this makes little difference in practice.) A module specifier string must be an absolute path starting with “/”, or a relative path starting with “./” or “../”, or a complete URL a with protocol and hostname. The ES6 specification does not allow unqualified module specifier strings like “util.js” because it is ambiguous whether this is intended to name a module in the same directory as the current one or some kind of system module that is installed in some special location. (This restriction against “bare module specifiers” is not honored by code-bundling tools like webpack, which can easily be configured to find bare modules in a library directory that you specify.) A future version of the language may allow “bare module specifiers,” but for now, they are not allowed. If you want to import a module from the same directory as the current one, simply place “./” before the module name and import from “./util.js” instead of “util.js”.\n\n> 被导入的模块用单引号或双引号括上的字符串字面量常量指定。（不可以使用变量或其他值为字符串的表达式，也不可以使用带反引号的字符串，因为模板字面量可以插入变量，它不总是一个常量值。）在 Web 浏览器中，字符串像 URL 一样描述导入模块的相对位置。（在 Node中，或者使用代码捆绑工具时，字符串描述一个对于当前模块的相对文件名，这在事件中略有不同。）以“/”开始的模块指定字符串时绝对路径，“./”和“../”开头的是相对路径，或者是带有协议和主机名的完整 URL。ES6 明确规定不允许使用诸如“util.js”这样的不合规范的字符串，因为它是否是相对路径还是绝对路径上有歧义。（诸如 webpack 之类的代码捆绑工具不能满足对“裸模块说明符”的这种限制，可以将其轻松配置为在指定的库中查找裸模块。）未来版本 JavaScript 可能允许使用“裸模块说明符”，但目前不允许这样做。 如果要从与当前目录相同的目录中导入模块，只需在模块名称前放置“./”，然后从“./util.js”而不是“ util.js”导入。\n\nSo far, we’ve only considered the case of importing a single value from a module that uses export default. To import values from a module that exports multiple values, we use a slightly different syntax:\n\n> 到目前为止，我们仅考虑了从使用默认导出的模块中导入单个值的情况。要从导出多个值的模块导入值，我们使用略有不同的语法：\n\n```js\nimport { mean, stddev } from \"./stats.js\";\n```\nRecall that default exports do not need to have a name in the module that defines them. Instead, we provide a local name when we import those values. But non-default exports of a module do have names in the exporting module, and when we import those values, we refer to them by those names. The exporting module can export any number of named value. An import statement that references that module can import any subset of those values simply by listing their names within curly braces. The curly braces make this kind of import statement look something like a destructuring assignment, and destructuring assignment is actually a good analogy for what this style of import is doing. The identifiers within curly braces are all hoisted to the top of the importing module and behave like constants.\n\n> 回想一下，默认导出不需要在定义它们的模块中具有名称。而是在导入这些值时提供一个本地名称。但是，模块的非默认导出在导出模块中具有名称，并且当我们导入这些值时，我们通过这些名称来引用它们。导出模块可以导出任意数量的命名值。导入语句引用模块，可以简单地通过在花括号中列出它们的名称来导入这些名称对应的值的任何子集。花括号使这种导入语句看起来像是一个解构赋值，而解构赋值实际上是很好的案例来解释导入的这种风格。花括号中的标识符都被提前到导入模块的顶部，并且行为如同常量。\n\nStyle guides sometimes recommend that you explicitly import every symbol that your module will use. When importing from a module that defines many exports, however, you can easily import everything with an import statement like this:\n\n> 有时，风格指南建议显示导入模块将使用的每个符号。但是，从定义许多导出的模块进行导入时，可以使用如下的 import 语句轻松导入所有内容：\n\n```js\nimport * as stats from \"./stats.js\";\n```\nAn import statement like this creates an object and assigns it to a constant named stats. Each of the non-default exports of the module being imported becomes a property of this stats object. Non-default exports always have names, and those are used as property names within the object. Those properties are effectively constants: they cannot be overwritten or deleted. With the wildcard import shown in the previous example, the importing module would use the imported mean() and stddev() functions through the stats object, invoking them as stats.mean() and stats.stddev().\n\n> 像这样的导入语句创建一个对象并将其赋值给一个名为 stats 的常量。模块中每一个非默认导出被导入成 stats 对象的一个属性。非默认导出始终有名称，在对象中它们被用作属性名。那些属性实际上是常量：它们不能被重写或删除。通过前面例子中展示的通配符导入，导入的模块通过 stats 对象使用 mean() 和 stddev() 函数，用 stats.mean() 和 stats.stddev() 来调用它们。\n\nModules typically define either one default export or multiple named exports. It is legal, but somewhat uncommon, for a module to use both export and export default. But when a module does that, you can import both the default value and the named values with an import statement like this:\n\n> 模块通常定义一个默认导出或者多个命名导出。模块内同时使用导出和默认导出是合法的，但是不常见。但是当一个模块这样做了，可以通过下面这种方式将默认值和命名值通过一个导入语句导入：\n\n```js\nimport Histogram, { mean, stddev } from \"./histogram-stats.js\";\n```\nSo far, we’ve seen how to import from modules with a default export and from modules with non-default or named exports. But there is one other form of the import statement that is used with modules that have no exports at all. To include a no-exports module into your program, simply use the import keyword with the module specifier:\n\n> 到目前为止，我们已经了解了如何从具有默认导出的模块以及具有非默认或命名导出的模块导入。但是 import 语句还有另一种形式，可以用于没有导出的模块。要将无导出模块包含到程序中，只需使用 import 关键字和模块说明符：\n\n```js\nimport \"./analytics.js\";\n```\nA module like this runs the first time it is imported. (And subsequent imports do nothing.) A module that just defines functions is only useful if it exports at least one of those functions. But if a module runs some code, then it can be useful to import even without symbols. An analytics module for a web application might run code to register various event handlers and then use those event handlers to send telemetry data back to the server at appropriate times. The module is self-contained and does not need to export anything, but we still need to import it so that it does actually run as part of our program.\n\n> 这样的模块在首次运行时导入。（并且随后的导入不执行任何操作。）仅定义函数的模块只有在导出至少一个函数时才有用。但是，如果模块运行一些代码，那么即使没有符号也可以导入。Web 应用程序的分析模块可能会运行代码来注册各种事件处理器，然后使用这些事件处理程序在适当的时间将遥测数据发送回服务器。该模块是独立的，不需要导出任何东西，但是我们仍然需要导入它，以便它实际上可以作为程序的一部分运行。\n\nNote that you can use this import-nothing import syntax even with modules that do have exports. If a module defines useful behavior independent of the values it exports, and if your program does not need any of those exported values, you can still import the module . just for that default behavior.\n\n> 请注意，即使对于具有导出的模块，也可以使用不导入任何内容的导入语法。如果模块定义的有用行为与导出的值无关，并且程序不需要任何导出的值，则仍可以用 . 导入模块，这仅用于默认导出。\n\n### 10.3.3 Imports and Exports with Renaming\nIf two modules export two different values using the same name and you want to import both of those values, you will have to rename one or both of the values when you import it. Similarly, if you want to import a value whose name is already in use in your module, you will need to rename the imported value. You can use the as keyword with named imports to rename them as you import them:\n\n> 如果两个模块使用相同的名称导出两个不同的值，并且要导入这两个值，则在导入时必须重命名一个或两个值。同样，如果模块要导入值的名称已在模块中使用，则需要重命名导入的值。可以在命名导入中使用 as 关键字，以在导入它们时重命名它们：\n\n```js\nimport { render as renderImage } from \"./imageutils.js\";\nimport { render as renderUI } from \"./ui.js\";\n```\nThese lines import two functions into the current module. The functions are both named render() in the modules that define them but are imported with the more descriptive and disambiguating names renderImage() and renderUI().\n\n> 这些行将两个功能导入当前模块。这些函数在定义它们的模块中都被命名为 render()，但使用更具描述性和消除歧义性的 renderImage() 和 renderUI() 名称导入。\n\nRecall that default exports do not have a name. The importing module always chooses the name when importing a default export. So there is no need for a special syntax for renaming in that case.\n\n> 回想一下，默认导出没有名称。导入默认导出时，导入模块始终选择名称。因此，在这种情况下，不需要特殊的语法来重命名。\n\nHaving said that, however, the possibility of renaming on import provides another way of importing from modules that define both a default export and named exports. Recall the “./histogram-stats.js” module from the previous section. Here is another way to import both the default and named exports of that module:\n\n> 话虽如此，导入重命名的可能性提供另外一种导入，从模块导入同时定义默认导出和命名导出。回顾上一节中的“ ./histogram-stats.js”模块。这是同时导入该模块的默认导出和命名导出的另一种方法：\n\n```js\nimport { default as Histogram, mean, stddev } from \"./histogram-stats.js\";\n```\nIn this case, the JavaScript keyword default serves as a placeholder and allows us to indicate that we want to import and provide a name for the default export of the module.\n\n> 在这种情况下，JavaScript 关键字 default 用作占位符，并允许我们指示我们要导入并为模块的默认导出提供名称。\n\nIt is also possible to rename values as you export them, but only when using the curly brace variant of the export statement. It is not common to need to do this, but if you chose short, succinct names for use inside your module, you might prefer to export your values with more descriptive names that are less likely to conflict with other modules. As with imports, you use the as keyword to do this:\n\n> 也可以在导出时重命名值，但仅在使用 export 语句的花括号时才可以。这样做通常并不常见，但是如果选择在模块内部使用简短的简短名称，则可能更喜欢使用描述性较强的名称导出值，这些名称不太可能与其他模块发生冲突。与导入一样，可以使用 as 关键字执行此操作：\n\n```js\nexport {\n    layout as calculateLayout,\n    render as renderLayout\n};\n```\nKeep in mind that, although the curly braces look something like object literals, they are not, and the export keyword expects a single identifier before the as, not an expression. This means, unfortunately, that you cannot use export renaming like this:\n\n> 请记住，尽管花括号看起来像对象字面量，但实际上并非如此，并且 export 关键字期望在 as 之前有一个标识符，而不是表达式。不幸的是，这意味着不能像这样使用导出重命名：\n\n```js\nexport { Math.sin as sin, Math.cos as cos }; // SyntaxError\n```\n### 10.3.4 Re-Exports\nThroughout this chapter, we’ve discussed a hypothetical “./stats.js” module that exports mean() and stddev() functions. If we were writing such a module and we thought that many users of the module would want only one function or the other, then we might want to define mean() in a “./stats/mean.js” module and define stddev() in “./stats/stddev.js”. That way, programs only need to import exactly the functions they need and are not bloated by importing code they do not need.\n\n> 在本章中，我们讨论了一个假定的“./stats.js”模块，该模块导出了 mean() 和 stddev() 函数。如果我们正在编写这样一个模块，并且该模块的用户只想要一个函数或另一个函数，那么我们可能想在“./stats/mean.js”模块中定义 mean()，并定义在“./stats/stddev.js”模块中的 stddev()。这样，程序仅需要完全导入所需的功能，而不会因导入不需要的代码而肿。\n\nEven if we had defined these statistical functions in individual modules, however, we might expect that there would be plenty of programs that want both functions and would appreciate a convenient “./stats.js” module from which they could import both on one line.\n\n> 但是，即使我们在单个模块中定义了这些统计函数，我们也可能有很多程序需要这两个功能，并且希望使用一行代码方便的导入“./stats.js”模块。\n\nGiven that the implementations are now in separate files, defining this “./stat.js” module is simple:\n\n> 鉴于实现现在位于单独的文件中，因此定义这样的“./stat.js”模块会非常简单：\n\n```js\nimport { mean } from \"./stats/mean.js\";\nimport { stddev } from \"./stats/stddev.js\";\nexport { mean, stdev };\n```\nES6 modules anticipate this use case and provide a special syntax for it. Instead of importing a symbol simply to export it again, you can combine the import and the export steps into a single “re-export” statement that uses the export keyword and the from keyword:\n\n> ES6 模块预见了这种使用场景，并为此提供了一种特殊的语法。可以使用 export 和 from 关键字合并导入和导出到一个单独的“再导出”语句中，而不是简单地再次导入即可导入符号：\n\n```js\nexport { mean } from \"./stats/mean.js\";\nexport { stddev } from \"./stats/stddev.js\";\n```\nNote that the names mean and stddev are not actually used in this code. If we are not being selective with a re-export and simply want to export all of the named values from another module, we can use a wildcard:\n\n> 请注意，此代码中并未实际使用名称 mean 和 stddev。如果我们不选择再导出，而只是想从另一个模块中导出所有命名值，则可以使用通配符：\n\n```js\nexport * from \"./stats/mean.js\";\nexport * from \"./stats/stddev.js\";\n```\nRe-export syntax allows renaming with as just as regular import and export statements do. Suppose we wanted to re-export the mean() function but also define average() as another name for the function. We could do that like this:\n\n> 再导出语法允许重命名，就像常规的导入和导出语句一样。 假设我们要再导出 mean() 函数，但还要将该函数定义为 average()。 我们可以这样做：\n\n```js\nexport { mean, mean as average } from \"./stats/mean.js\";\nexport { stddev } from \"./stats/stddev.js\";\n```\nAll of the re-exports in this example assume that the “./stats/mean.js” and “./stats/stddev.js” modules export their functions using export instead of export default. In fact, however, since these are modules with only a single export, it would have made sense to define them with export default. If we had done so, then the re-export syntax is a little more complicated because it needs to define a name for the unnamed default exports. We can do that like this:\n\n> 所有的再导出示例中，“./stats/mean.js”和“./stats/stddev.js”模块都使用导出而不是默认导出来导出它们的函数。但是，实际上，由于这些模块仅具有单个导出，因此使用导出默认定义它们是更明智的。如果我们这样做，那么再导出语法会稍微复杂一点，因为它需要为未命名的默认导出定义一个名称。 我们可以这样做：\n\n```js\nexport { default as mean } from \"./stats/mean.js\";\nexport { default as stddev } from \"./stats/stddev.js\";\n```\nIf you want to re-export a named symbol from another module as the default export of your module, you could do an import followed by an export default, or you could combine the two statements like this:\n\n> 如果要从另一个模块中再导出命名符号作为模块的默认导出，则可以先进行导入，然后再默认导出，或者可以将以下两个语句组合在一起：\n\n```js\n// Import the mean() function from ./stats.js and make it the\n// default export of this module\nexport { mean as default } from \"./stats.js\"\n```\nAnd finally, to re-export the default export of another module as the default export of your module (though it is unclear why you would want to do this, since users could simply import the other module directly), you can write:\n\n> 最后，要将另一个模块的默认导出再导出为模块的默认导出（尽管不清楚为什么要这样做，因为用户可以直接导入另一个模块），可以这样编写：\n\n```js\n// The average.js module simply re-exports the stats/mean.js default export\nexport { default } from \"./stats/mean.js\"\n```\n### 10.3.5 JavaScript Modules on the Web\nThe preceding sections have described ES6 modules and their import and export declarations in a somewhat abstract manner. In this section and the next, we’ll be discussing how they actually work in web browsers, and if you are not already an experienced web developer, you may find the rest of this chapter easier to understand after you have read Chapter 15.\n\n> 前面的章节以某种抽象的方式描述了 ES6 模块及其导入和导出声明。在本节和下一部分中，我们将讨论它们在 Web 浏览器中的实际工作方式，如果您还不是经验丰富的 Web 开发人员，则在阅读第 15 章之后，可能会发现本章的其余部分更容易理解。\n\nAs of early 2020, production code using ES6 modules is still generally bundled with a tool like webpack. There are trade-offs to doing this, [^1] but on the whole, code bundling tends to give better performance. That may well change in the future as network speeds grow and browser vendors continue to optimize their ES6 module implementations.\n\n> 截至 2020 年初，使用 ES6 模块的生产代码通常仍与 webpack 之类的工具捆绑在一起。 这样做是有折衷的 [^1]，但是总的来说，代码捆绑往往会提供更好的性能。随着网络速度的增长以及浏览器供应商继续优化其 ES6 模块的实现，将来这种情况可能会发生很大的变化。\n\nEven though bundling tools may still be desirable in production, they are no longer required in development since all current browsers provide native support for JavaScript modules. Recall that modules use strict mode by default, this does not refer to a global object, and top-level declarations are not shared globally by default. Since modules must be executed differently than legacy non-module code, their introduction requires changes to HTML as well as JavaScript. If you want to natively use import directives in a web browser, you must tell the web browser that your code is a module by using a `<script type=\"module\">` tag.\n\n> 即使在生产中仍可能需要捆绑工具，但由于当前所有的浏览器都为 JavaScript 模块提供了本机支持，因此在开发中不再需要捆绑工具。回想一下，默认情况下模块使用严格模式，this 不引用全局对象，并且顶级声明默认不全局共享。由于模块的执行方式必须不同于传统的非模块代码，因此其引入 requires 对 HTML 和 JavaScript 进行更改。如果要在 Web 浏览器本地使用 import 指令，则必须通过使用 `<script type =“ module”>` 标记来告知 Web 浏览器您的代码是模块。\n\nOne of the nice features of ES6 modules is that each module has a static set of imports. So given a single starting module, a web browser can load all of its imported modules and then load all of the modules imported by that first batch of modules, and so on, until a complete program has been loaded. We’ve seen that the module specifier in an import statement can be treated as a relative URL. A `<script type=\"module\">` tag marks the starting point of a modular program. None of the modules it imports are expected to be in `<script>` tags, however: instead, they are loaded on demand as regular JavaScript files and are executed in strict mode as regular ES6 modules. Using a `<script type=\"module\">` tag to define the main entry point for a modular JavaScript program can be as simple as this:\n```js\n<script type=\"module\">import \"./main.js\";</script>\n```\nCode inside an inline `<script type=\"module\">` tag is an ES6 module, and as such can use the export statement. There is not any point in doing so, however, because the HTML `<script>` tag syntax does not provide any way to define a name for inline modules, so even if such a module does export a value, there is no way for another module to import it.\n\nScripts with the type=\"module\" attribute are loaded and executed like scripts with the defer attribute. Loading of the code begins as soon as the HTML parser encounters the `<script>` tag (in the case of modules, this code-loading step may be a recursive process that loads multiple JavaScript files). But code execution does not begin until HTML parsing is complete. And once HTML parsing is complete, scripts (both modular and non) are executed in the order in which they appear in the HTML document.\n\nYou can modify the execution time of modules with the async attribute, which works the same way for modules that it does for regular scripts. An async module will execute as soon as the code is loaded, even if HTML parsing is not complete and even if this changes the relative ordering of the scripts.\n\nWeb browsers that support `<script type=\"module\">` must also support `<script nomodule>`. Browsers that are module-aware ignore any script with the nomodule attribute and will not execute it. Browsers that do not support modules will not recognize the nomodule attribute, so they will ignore it and run the script. This provides a powerful technique for dealing with browser compatibility issues. Browsers that support ES6 modules also support other modern JavaScript features like classes, arrow functions, and the for/of loop. If you write modern JavaScript and load it with `<script type=\"module\">`, you know that it will only be loaded by browsers that can support it. And as a fallback for IE11 (which, in 2020, is effectively the only remaining browser that does not support ES6), you can use tools like Babel and webpack to transform your code into non-modular ES5 code, then load that less-efficient transformed code via `<script nomodule>`.\n\nAnother important difference between regular scripts and module scripts has to do with cross-origin loading. A regular `<script>` tag will load a file of JavaScript code from any server on the internet, and the internet’s infrastructure of advertising, analytics, and tracking code depends on that fact. But `<script type=\"module\">` provides an opportunity to tighten this up, and modules can only be loaded from the same origin as the containing HTML document or when proper CORS headers are in place to securely allow cross-origin loads. An unfortunate side effect of this new security restriction is that it makes it difficult to test ES6 modules in development mode using file: URLs. When using ES6 modules, you will likely need to set up a static web server for testing.\n\nSome programmers like to use the filename extension .mjs to distinguish their modular JavaScript files from their regular, non-modular JavaScript files with the traditional .js extension. For the purposes of web browsers and `<script>` tags, the file extension is actually irrelevant. (The MIME type is relevant, however, so if you use .mjs files, you may need to configure your web server to serve them with the same MIME type as .js files.) Node’s support for ES6 does use the filename extension as a hint to distinguish which module system is used by each file it loads. So if you are writing ES6 modules and want them to be usable with Node, then it may be helpful to adopt the .mjs naming convention.\n\n### 10.3.6 Dynamic Imports with import()\nWe’ve seen that the ES6 import and export directives are completely static and enable JavaScript interpreters and other JavaScript tools to determine the relationships between modules with simple text analysis while the modules are being loaded without having to actually execute any of the code in the modules. With statically imported modules, you are guaranteed that the values you import into a module will be ready for use before any of the code in your module begins to run.\n\nOn the web, code has to be transferred over a network instead of being read from the filesystem. And once transfered, that code is often executed on mobile devices with relatively slow CPUs. This is not the kind of environment where static module imports—which require an entire program to be loaded before any of it runs—make a lot of sense.\n\nIt is common for web applications to initially load only enough of their code to render the first page displayed to the user. Then, once the user has some preliminary content to interact with, they can begin to load the often much larger amount of code needed for the rest of the web app. Web browsers make it easy to dynamically load code by using the DOM API to inject a new `<script>` tag into the current HTML document, and web apps have been doing this for many years.\n\nAlthough dynamic loading has been possible for a long time, it has not been part of the language itself. That changes with the introduction of import() in ES2020 (as of early 2020, dynamic import is supported by all browsers that support ES6 modules). You pass a module specifier to import() and it returns a Promise object that represents the asynchronous process of loading and running the specified module. When the dynamic import is complete, the Promise is “fulfilled” (see Chapter 13 for complete details on asynchronous programming and Promises) and produces an object like the one you would get with the import * as form of the static import statement.\n\nSo instead of importing the “./stats.js” module statically, like this:\n```js\nimport * as stats from \"./stats.js\";\nwe might import it and use it dynamically, like this:\n\nimport(\"./stats.js\").then(stats => {\n    let average = stats.mean(data);\n})\n```\nOr, in an async function (again, you may need to read Chapter 13 before you’ll understand this code), we can simplify the code with await:\n```js\nasync analyzeData(data) {\n    let stats = await import(\"./stats.js\");\n    return {\n        average: stats.mean(data),\n        stddev: stats.stddev(data)\n    };\n}\n```\nThe argument to import() should be a module specifier, exactly like one you’d use with a static import directive. But with import(), you are not constrained to use a constant string literal: any expression that evaluates to a string in the proper form will do.\n\nDynamic import() looks like a function invocation, but it actually is not. Instead, import() is an operator and the parentheses are a required part of the operator syntax. The reason for this unusual bit of syntax is that import() needs to be able to resolve module specifiers as URLs relative to the currently running module, and this requires a bit of implementation magic that would not be legal to put in a JavaScript function. The function versus operator distinction rarely makes a difference in practice, but you’ll notice it if you try writing code like console.log(import); or let require = import;.\n\nFinally, note that dynamic import() is not just for web browsers. Code-packaging tools like webpack can also make good use of it. The most straightforward way to use a code bundler is to tell it the main entry point for your program and let it find all the static import directives and assemble everything into one large file. By strategically using dynamic import() calls, however, you can break that one monolithic bundle up into a set of smaller bundles that can be loaded on demand.\n\n### 10.3.7 import.meta.url\nThere is one final feature of the ES6 module system to discuss. Within an ES6 module (but not within a regular `<script> `or a Node module loaded with require()), the special syntax import.meta refers to an object that contains metadata about the currently executing module. The url property of this object is the URL from which the module was loaded. (In Node, this will be a file:// URL.)\n\nThe primary use case of import.meta.url is to be able to refer to images, data files, or other resources that are stored in the same directory as (or relative to) the module. The URL() constructor makes it easy to resolve a relative URL against an absolute URL like import.meta.url. Suppose, for example, that you have written a module that includes strings that need to be localized and that the localization files are stored in an l10n/ directory, which is in the same directory as the module itself. Your module could load its strings using a URL created with a function, like this:\n```js\nfunction localStringsURL(locale) {\n    return new URL(`l10n/${locale}.json`, import.meta.url);\n}\n```\n## 10.4 Summary\nThe goal of modularity is to allow programmers to hide the implementation details of their code so that chunks of code from various sources can be assembled into large programs without worrying that one chunk will overwrite functions or variables of another. This chapter has explained three different JavaScript module systems:\n\n- In the early days of JavaScript, modularity could only be achieved through the clever use of immediately invoked function expressions.\n- Node added its own module system on top of the JavaScript language. Node modules are imported with require() and define their exports by setting properties of the Exports object, or by setting the module.exports property.\n- In ES6, JavaScript finally got its own module system with import and export keywords, and ES2020 is adding support for dynamic imports with import().\n\n---\n\n1. For example: web apps that have frequent incremental updates and users who make frequent return visits may find that using small modules instead of large bundles can result in better average load times because of better utilization of the user’s browser cache."
  },
  {
    "path": "content/posts/ch11.md",
    "content": "---\ntitle: \"第 11 章 JavaScript 标准库\"\ndate: 2020-11-02T22:18:32+08:00\n---\n\nSome datatypes, such as numbers and strings (Chapter 3), objects (Chapter 6), and arrays (Chapter 7) are so fundamental to JavaScript that we can consider them to be part of the language itself. This chapter covers other important but less fundamental APIs that can be thought of as defining the “standard library” for JavaScript: these are useful classes and functions that are built in to JavaScript and available to all JavaScript programs in both web browsers and in Node.[^1]\n\n> 一些数据类型，如数字和字符串（第 3 章）、对象（第 6 章）和数组（第 7 章）是 JavaScript 的基础，我们可以将它们视为语言本身的一部分。这一章涵盖了其他重要但不那么基础的 api，这些 api 可以被认为是定义 JavaScript 的“标准库”：这些是 JavaScript 内建的有用的类和函数，在 web 浏览器和 Node 中的所有 JavaScript 程序中都可以使用。 [^1]\n\nThe sections of this chapter are independent of one another, and you can read them in any order. They cover:\n\n> 本章的各个章节是相互独立的，你可以以任何顺序阅读它们。他们包括:\n\n- The Set and Map classes for representing sets of values and mappings from one set of values to another set of values.\n- Array-like objects known as TypedArrays that represent arrays of binary data, along with a related class for extracting values from non-array binary data.\n- Regular expressions and the RegExp class, which define textual patterns and are useful for text processing. This section also covers regular expression syntax in detail.\n- The Date class for representing and manipulating dates and times.\n- The Error class and its various subclasses, instances of which are thrown when errors occur in JavaScript programs.\n- The JSON object, whose methods support serialization and deserialization of JavaScript data structures composed of objects, arrays, strings, numbers, and booleans.\n- The Intl object and the classes it defines that can help you localize your JavaScript programs.\n- The Console object, whose methods output strings in ways that are particularly useful for debugging programs and logging the behavior of those programs.\n- The URL class, which simplifies the task of parsing and manipulating URLs. This section also covers global functions for encoding and decoding URLs and their component parts.\n- setTimeout() and related functions for specifying code to be executed after a specified interval of time has elapsed.\n\n---\n\n> - Set 和 Map 类，用于表示一组值以及从一组值到另一组值的映射。\n> - 类数组对象，称为 TypedArrays，表示二进制数据的数组，以及从非数组二进制数据中提取值的相关类。\n> - 正则表达式和 RegExp 类，它们定义文本模型，对文本处理很有用。本节还详细介绍了正则表达式语法。\n> - 用于表示和操作日期和时间的 Date 类。\n> - Error 类及其各种子类，在 JavaScript 程序中发生错误时抛出它们的实例。\n> - JSON 对象，其方法支持由对象、数组、字符串、数字和布尔值组成的 JavaScript 数据结构的序列化和反序列化。\n> - Intl 对象和它定义的类可以帮助你本地化你的 JavaScript 程序。\n> - Console 对象，其方法输出字符串的方式对调试程序和记录这些程序的行为特别有用。\n> - URL 类，它简化了解析和操纵 URL 的任务。本节还介绍了用于编码和解码 url 及其组件的全局函数。\n> - setTimeout() 和相关函数，用于指定在指定的时间间隔后执行的代码。\n\nSome of the sections in this chapter—notably, the sections on typed arrays and regular expressions—are quite long because there is significant background information you need to understand before you can use those types effectively. Many of the other sections, however, are short: they simply introduce a new API and show some examples of its use.\n\n> 本章中的一些部分（特别是关于类型化数组和正则表达式的部分）相当长，因为在有效使用这些类型之前，需要了解它们重要的背景信息。但是，其他部分都很简短：它们只是介绍了一个新的 API 并展示了一些使用它的示例。\n\n## 11.1 Sets and Maps\n\nJavaScript’s Object type is a versatile data structure that can be used to map strings (the object’s property names) to arbitrary values. And when the value being mapped to is something fixed like true, then the object is effectively a set of strings.\n\n> JavaScript 的对象类型是一种通用的数据结构，可用于将字符串（对象的属性名称）映射到任意值。当被映射到的值是固定的，比如 true，那么对象实际上是一组字符串。\n\nObjects are actually used as maps and sets fairly routinely in JavaScript programming, but this is limited by the restriction to strings and complicated by the fact that objects normally inherit properties with names like “toString”, which are not typically intended to be part of the map or set.\n\n> 在 JavaScript 编程中，对象通常被用作映射和集合，但这受到字符串的限制，而且由于对象通常继承诸如“toString”之类的属性而变得复杂，这些属性通常不准备成为映射或集合的一部分。\n\nFor this reason, ES6 introduces true Set and Map classes, which we’ll cover in the sub-sections that follow.\n\n> 因此，ES6引入了 true Set 和 Map 类，我们将在后面的子节中介绍它们。\n\n### 11.1.1 The Set Class\n\nA set is a collection of values, like an array is. Unlike arrays, however, sets are not ordered or indexed, and they do not allow duplicates: a value is either a member of a set or it is not a member; it is not possible to ask how many times a value appears in a set.\n\n> set 是值的集合，就像数组。但不同于数组，set 没有被排序或被索引，并且它们不允许重复：值要么是集合的成员要么不是集合的成员；不能知道一个值在集合中出现多少次。\n\nCreate a Set object with the Set() constructor:\n\n> 使用 Set() 创建 Set 对象：\n\n```js\nlet s = new Set();       // A new, empty set\nlet t = new Set([1, s]); // A new set with two members\n```\nThe argument to the Set() constructor need not be an array: any iterable object (including other Set objects) is allowed:\n\n> Set() 构造函数的实参不必是数组：允许任何可迭代的对象（包括其他 Set 对象）：\n\n```js\nlet t = new Set(s);                  // A new set that copies the elements of s.\nlet unique = new Set(\"Mississippi\"); // 4 elements: \"M\", \"i\", \"s\", and \"p\"\n```\nThe size property of a set is like the length property of an array: it tells you how many values the set contains:\n\n> set 的 size 属性类似于数组的 length 属性：它告诉你该集合包含多少个值：\n\n```js\nunique.size        // => 4\n```\nSets don’t need to be initialized when you create them. You can add and remove elements at any time with add(), delete(), and clear(). Remember that sets cannot contain duplicates, so adding a value to a set when it already contains that value has no effect:\n\n> 创建 set 时无需初始化。可以随时使用add()、delete() 和 clear() 添加和删除元素。请记住，集合不能包含重复项，因此添加已经包含在该 set 中的值是一个无效操作：\n\n```js\nlet s = new Set();  // Start empty\ns.size              // => 0\ns.add(1);           // Add a number\ns.size              // => 1; now the set has one member\ns.add(1);           // Add the same number again\ns.size              // => 1; the size does not change\ns.add(true);        // Add another value; note that it is fine to mix types\ns.size              // => 2\ns.add([1,2,3]);     // Add an array value\ns.size              // => 3; the array was added, not its elements\ns.delete(1)         // => true: successfully deleted element 1\ns.size              // => 2: the size is back down to 2\ns.delete(\"test\")    // => false: \"test\" was not a member, deletion failed\ns.delete(true)      // => true: delete succeeded\ns.delete([1,2,3])   // => false: the array in the set is different\ns.size              // => 1: there is still that one array in the set\ns.clear();          // Remove everything from the set\ns.size              // => 0\n```\nThere are a few important points to note about this code:\n\n> 关于此代码，有几点要注意：\n\nThe add() method takes a single argument; if you pass an array, it adds the array itself to the set, not the individual array elements. add() always returns the set it is invoked on, however, so if you want to add multiple values to a set, you can used chained method calls like s.add('a').add('b').add('c');.\n\n> add() 方法有一个实参。如果传递数组，它将数组本身而不是单个数组元素添加到 set 中。 add() 始终返回调用它的 set，因此，如果要向 set 添加多个值，则可以使用方法链调用，如s.add('a').add('b').add('C');。\n\nThe delete() method also only deletes a single set element at a time. Unlike add(), however, delete() returns a boolean value. If the value you specify was actually a member of the set, then delete() removes it and returns true. Otherwise, it does nothing and returns false.\n\n> delete() 方法一次也只删除一个 set 元素。与 add() 不同，delete() 返回一个布尔值。如果指定的值是 set 的成员，则 delete() 会将其删除并返回 true。否则，它不执行任何操作并返回 false。\n\nFinally, it is very important to understand that set membership is based on strict equality checks, like the === operator performs. A set can contain both the number 1 and the string \"1\", because it considers them to be distinct values. When the values are objects (or arrays or functions), they are also compared as if with ===. This is why we were unable to delete the array element from the set in this code. We added an array to the set and then tried to remove that array by passing a different array (albeit with the same elements) to the delete() method. In order for this to work, we would have had to pass a reference to exactly the same array.\n\n> 最后，set 成员关系是基于严格的相等性检查（如 === 运算符执行），这是非常重要的。一个 set 可以同时包含数字 1 和字符串 \"1\"，因为它认为它们是不同的值。当值是对象（或数组或函数）时，它们也将用 === 进行比较。这就是此代码集中为什么我们无法从删除数组元素的原因。我们向 set 中添加了一个数组，然后尝试通过将另一个数组（尽管具有相同的元素）传递给 delete() 方法来删除该数组。为了使它起作用，我们必须传递完全相同的数组引用。\n\n#### NOTE\nPython programmers take note: this is a significant difference between JavaScript and Python sets. Python sets compare members for equality, not identity, but the trade-off is that Python sets only allow immutable members, like tuples, and do not allow lists and dicts to be added to sets.\n\n> Python 程序员请注意：JavaScript 和 Python 的 set 之间有个很大的差异。 Python set 比较成员是为了相等，而不是标识，但是要权衡的是 Python set 只允许不可变成员（例如元组），并且不允许将 list 和 dict 添加到集合中。（todo）\n\nIn practice, the most important thing we do with sets is not to add and remove elements from them, but to check to see whether a specified value is a member of the set. We do this with the has() method:\n\n> 实际上，我们对 set 进行的最重要的操作不是在元素中添加和删除元素，而是检查指定的值是否是集合的成员。我们使用 has() 方法做到这一点：\n\n```js\nlet oneDigitPrimes = new Set([2,3,5,7]);\noneDigitPrimes.has(2)    // => true: 2 is a one-digit prime number\noneDigitPrimes.has(3)    // => true: so is 3\noneDigitPrimes.has(4)    // => false: 4 is not a prime\noneDigitPrimes.has(\"5\")  // => false: \"5\" is not even a number\n```\nThe most important thing to understand about sets is that they are optimized for membership testing, and no matter how many members the set has, the has() method will be very fast. The includes() method of an array also performs membership testing, but the time it takes is proportional to the size of the array, and using an array as a set can be much, much slower than using a real Set object.\n\n> 关于 set 最重要的要了解的是，它们已针对成员资格测试进行了优化，并且无论 set 有多少个成员，has() 方法都将非常快。 数组的 includes() 方法也执行成员资格测试，但是花费的时间与数组的大小成正比，并且将数组作为 set 使用比使用真正的 Set 对象要慢得多。\n\nThe Set class is iterable, which means that you can use a for/of loop to enumerate all of the elements of a set:\n\n> Set 类是可迭代的，这意味着您可以使用 for/of 循环枚举集合中的所有元素：\n\n```js\nlet sum = 0;\nfor(let p of oneDigitPrimes) { // Loop through the one-digit primes\n    sum += p;                  // and add them up\n}\nsum                            // => 17: 2 + 3 + 5 + 7\n```\nBecause Set objects are iterable, you can convert them to arrays and argument lists with the ... spread operator:\n\n> 因为 Set 对象是可迭代的，所以可以使用 ... 展开运算符将它们转换为数组和实参列表：\n\n```js\n[...oneDigitPrimes]         // => [2,3,5,7]: the set converted to an Array\nMath.max(...oneDigitPrimes) // => 7: set elements passed as function arguments\n```\nSets are often described as “unordered collections.” This isn’t exactly true for the JavaScript Set class, however. A JavaScript set is unindexed: you can’t ask for the first or third element of a set the way you can with an array. But the JavaScript Set class always remembers the order that elements were inserted in, and it always uses this order when you iterate a set: the first element inserted will be the first one iterated (assuming you haven’t deleted it first), and the most recently inserted element will be the last one iterated. [^2]\n\n> Set 通常被称为“无序集合”。 但是，对于 JavaScript 的 Set 类而言，并非完全如此。JavaScript Set 未索引：无法像使用数组那样获取 Set 的第一个或第三个元素。 但是 JavaScript Set 类始终记住插入元素的顺序，并且在遍历集合时始终使用此顺序：插入的第一个元素将是第一个遍历的元素（假定没有先删除它），并且，最后插入的元素将是最后一个遍历的元素。\n\nIn addition to being iterable, the Set class also implements a forEach() method that is similar to the array method of the same name:\n\n> 除了可迭代之外，Set 类还实现了一个 forEach() 方法，该方法类似于同名的 array 方法：\n\n```js\nlet product = 1;\noneDigitPrimes.forEach(n => { product *= n; });\nproduct     // => 210: 2 * 3 * 5 * 7\n```\nThe forEach() of an array passes array indexes as the second argument to the function you specify. Sets don’t have indexes, so the Set class’s version of this method simply passes the element value as both the first and second argument.\n\n> 数组的 forEach() 将数组索引作为指定函数的第二个实参传递。集合没有索引，因此此方法的 Set 类版本仅将元素值作为第一个和第二个实参传递。\n\n### 11.1.2 The Map Class\nA Map object represents a set of values known as keys, where each key has another value associated with (or “mapped to”) it. In a sense, a map is like an array, but instead of using a set of sequential integers as the keys, maps allow us to use arbitrary values as “indexes.” Like arrays, maps are fast: looking up the value associated with a key will be fast (though not as fast as indexing an array) no matter how large the map is.\n\n> Map 对象代表一组称为键的值，其中每个键具有与其关联（或“映射到”）的另一个值。从某种意义上讲，映射就像一个数组，但是映射不是使用一组连续的整数作为键，而是允许我们使用任意值作为“索引”。像数组一样，映射是快速的：无论映射有多大查找与键关联的值都很快（尽管不如索引数组快）。\n\nCreate a new map with the Map() constructor:\n\n> 用 Map() 构造函数闯将一个 map：\n\n```js\nlet m = new Map();  // Create a new, empty map\nlet n = new Map([   // A new map initialized with string keys mapped to numbers\n    [\"one\", 1],\n    [\"two\", 2]\n]);\n```\nThe optional argument to the Map() constructor should be an iterable object that yields two element [key, value] arrays. In practice, this means that if you want to initialize a map when you create it, you’ll typically write out the desired keys and associated values as an array of arrays. But you can also use the Map() constructor to copy other maps or to copy the property names and values from an existing object:\n\n> Map() 构造函数的可选实参应该是一个可迭代的对象，该对象可产生两个元素 [key，value] 数组。 实际上，这意味着如果要在创建 map 时初始化，通常会将所需的键和映射的值写为数组的数组。 但是，也可以使用 Map() 构造函数复制其他 map，或者从现有对象复制属性名称和值：\n\n```js\nlet copy = new Map(n); // A new map with the same keys and values as map n\nlet o = { x: 1, y: 2}; // An object with two properties\nlet p = new Map(Object.entries(o)); // Same as new map([[\"x\", 1], [\"y\", 2]])\n```\nOnce you have created a Map object, you can query the value associated with a given key with get() and can add a new key/value pair with set(). Remember, though, that a map is a set of keys, each of which has an associated value. This is not quite the same as a set of key/value pairs. If you call set() with a key that already exists in the map, you will change the value associated with that key, not add a new key/value mapping. In addition to get() and set(), the Map class also defines methods that are like Set methods: use has() to check whether a map includes the specified key; use delete() to remove a key (and its associated value) from the map; use clear() to remove all key/value pairs from the map; and use the size property to find out how many keys a map contains.\n\n> 创建 Map 对象后，可以用 get() 查询给定键的值，并可以用 set() 添加新的键值对。但是请记住，map 是一组键，每个键映射一个值。这与一组键值对不太相同。如果调用 set() 添加一个 map 中已存在的键的键值对，会改变这个键映射的值，而不是添加一个新的键值对映射。除了 get() 和 set()，Map 类也定义了类似于 Set 的方法：使用 has() 判断 map 是否包含指定键；使用 delete() 删除 map 中的一个键（和它映射的值）；使用 clear() 来移除 map 中所有的键值对；使用 size 属性知道 map 中包含多少个键。\n\n```js\nlet m = new Map();   // Start with an empty map\nm.size               // => 0: empty maps have no keys\nm.set(\"one\", 1);     // Map the key \"one\" to the value 1\nm.set(\"two\", 2);     // And the key \"two\" to the value 2.\nm.size               // => 2: the map now has two keys\nm.get(\"two\")         // => 2: return the value associated with key \"two\"\nm.get(\"three\")       // => undefined: this key is not in the set\nm.set(\"one\", true);  // Change the value associated with an existing key\nm.size               // => 2: the size doesn't change\nm.has(\"one\")         // => true: the map has a key \"one\"\nm.has(true)          // => false: the map does not have a key true\nm.delete(\"one\")      // => true: the key existed and deletion succeeded\nm.size               // => 1\nm.delete(\"three\")    // => false: failed to delete a nonexistent key\nm.clear();           // Remove all keys and values from the map\n```\nLike the add() method of Set, the set() method of Map can be chained, which allows maps to be initialized without using arrays of arrays:\n\n> 像 Set 的 add() 方法一样，Map 的 set() 方法也可以链式使用，这允许在不使用数组的情况下初始化 map：\n\n```js\nlet m = new Map().set(\"one\", 1).set(\"two\", 2).set(\"three\", 3);\nm.size        // => 3\nm.get(\"two\")  // => 2\n```\nAs with Set, any JavaScript value can be used as a key or a value in a Map. This includes null, undefined, and NaN, as well as reference types like objects and arrays. And as with the Set class, Map compares keys by identity, not by equality, so if you use an object or array as a key, it will be considered different from every other object and array, even those with exactly the same properties or elements:\n\n> 与 Set 一样，任何 JavaScript 的值都可以被用作 Map 的键或值。包括 null、undefined 和 NaN，也包括像对象和数组这种引用类型。并且和 Set 类一样，Map 通过标识来比较，不是通过相等，所以如果想要使用对象或数组作为键，它们会被认为不同于任何其他的对象和数组，即使那些属性或元素完全相同的引用：\n\n```js\nlet m = new Map();   // Start with an empty map.\nm.set({}, 1);        // Map one empty object to the number 1.\nm.set({}, 2);        // Map a different empty object to the number 2.\nm.size               // => 2: there are two keys in this map\nm.get({})            // => undefined: but this empty object is not a key\nm.set(m, undefined); // Map the map itself to the value undefined.\nm.has(m)             // => true: m is a key in itself\nm.get(m)             // => undefined: same value we'd get if m wasn't a key\n```\nMap objects are iterable, and each iterated value is a two-element array where the first element is a key and the second element is the value associated with that key. If you use the spread operator with a Map object, you’ll get an array of arrays like the ones that we passed to the Map() constructor. And when iterating a map with a for/of loop, it is idiomatic to use destructuring assignment to assign the key and value to separate variables:\n\n> Map 是可迭代对象，每一个迭代值是两个元素数组，第一个元素是键，第二个元素是键映射的值。如果对 Map 对象使用展开运算符，会得到一个数组的数组，就像传递给 Map() 构造函数的实参。当使用 for/of 循环遍历一个 map 时，常使用解构赋值将键和值赋值给展开变量：\n\n```js\nlet m = new Map([[\"x\", 1], [\"y\", 2]]);\n[...m]    // => [[\"x\", 1], [\"y\", 2]]\n\nfor(let [key, value] of m) {\n    // On the first iteration, key will be \"x\" and value will be 1\n    // On the second iteration, key will be \"y\" and value will be 2\n}\n```\nLike the Set class, the Map class iterates in insertion order. The first key/value pair iterated will be the one least recently added to the map, and the last pair iterated will be the one most recently added.\n\n> 与 Set 类一样，Map 类按照插入顺序进行遍历。遍历的第一个键值对将是最早添加到 map 中的，而遍历的最后一个键值对将是最新添加的。\n\nIf you want to iterate just the keys or just the associated values of a map, use the keys() and values() methods: these return iterable objects that iterate keys and values, in insertion order. (The entries() method returns an iterable object that iterates key/value pairs, but this is exactly the same as iterating the map directly.)\n\n> 如果只想遍历 map 中的键或者映射的值，使用 keys() 和 values() 方法：它们返回按照插入顺序排列的键或值可迭代对象。（entries() 方法返回一个迭代对象，该对象迭代键值对，但这与直接迭代 map 完全相同。）\n\n```js\n[...m.keys()]     // => [\"x\", \"y\"]: just the keys\n[...m.values()]   // => [1, 2]: just the values\n[...m.entries()]  // => [[\"x\", 1], [\"y\", 2]]: same as [...m]\n```\nMap objects can also be iterated using the forEach() method that was first implemented by the Array class.\n\n> Map 对象也可以使用 forEach() 方法进行遍历。\n\n```js\nm.forEach((value, key) => {  // note value, key NOT key, value\n    // On the first invocation, value will be 1 and key will be \"x\"\n    // On the second invocation, value will be 2 and key will be \"y\"\n});\n```\nIt may seem strange that the value parameter comes before the key parameter in the code above, since with for/of iteration, the key comes first. As noted at the start of this section, you can think of a map as a generalized array in which integer array indexes are replaced with arbitrary key values. The forEach() method of arrays passes the array element first and the array index second, so, by analogy, the forEach() method of a map passes the map value first and the map key second.\n\n> 上面的代码中 value 参数位于 key 参数之前，这似乎很奇怪，因为对于 for/of 迭代，key 放在前面。如本节开头所述，可以将映射视为通用数组，其中整数数组索引被替换为任意键值。数组的 forEach() 方法首先传递数组元素，然后传递数组索引，因此，以此类推，map 的 forEach() 方法首先传递 map 的值，然后传递 map 键。\n\n### 11.1.3 WeakMap and WeakSet\nThe WeakMap class is a variant (but not an actual subclass) of the Map class that does not prevent its key values from being garbage collected. Garbage collection is the process by which the JavaScript interpreter reclaims the memory of objects that are no longer “reachable” and cannot be used by the program. A regular map holds “strong” references to its key values, and they remain reachable through the map, even if all other references to them are gone. The WeakMap, by contrast, keeps “weak” references to its key values so that they are not reachable through the WeakMap, and their presence in the map does not prevent their memory from being reclaimed.\n\n> WeakMap 类是 Map 类的变体（但不是真正的子类），它不会阻止其键值被垃圾回收。垃圾回收是 JavaScript 解释器回收不再“可访问”并且无法由程序使用的对象的内存的过程。常规 map 保留对其键值的“强”引用，即使对它们的所有其他引用都已消失，它们仍然可以通过映射访问。相比之下，WeakMap 保留对其键值的“弱”引用，以使它们无法通过 WeakMap 获得，并且它们在 map 中的存在也不会阻止对其内存的回收。\n\nThe WeakMap() constructor is just like the Map() constructor, but there are some significant differences between WeakMap and Map:\n\n> WeakMap() 构造函数类似 Map() 构造函数，但是有一些重要的不同：\n\nWeakMap keys must be objects or arrays; primitive values are not subject to garbage collection and cannot be used as keys.\n\n> WeakMap 键必须是对象或数组；原始值不受垃圾回收的限制，不能用作键。\n\nWeakMap implements only the get(), set(), has(), and delete() methods. In particular, WeakMap is not iterable and does not define keys(), values(), or forEach(). If WeakMap was iterable, then its keys would be reachable and it wouldn’t be weak.\n\n> WeakMap 仅实现 get()、set()、has() 和 delete() 方法。 特别是，WeakMap 是不可迭代的，并且未定义 keys()、values() 或 forEach()。如果 WeakMap 是可迭代的，则其键将是可访问的，这让它不会“弱”。\n\nSimilarly, WeakMap does not implement the size property because the size of a WeakMap could change at any time as objects are garbage collected.\n\n> 同样，WeakMap 也不实现 size 属性，因为随着对象被垃圾回收，WeakMap 的大小可能随时更改。\n\nThe intended use of WeakMap is to allow you to associate values with objects without causing memory leaks. Suppose, for example, that you are writing a function that takes an object argument and needs to perform some time-consuming computation on that object. For efficiency, you’d like to cache the computed value for later reuse. If you use a Map object to implement the cache, you will prevent any of the objects from ever being reclaimed, but by using a WeakMap, you avoid this problem. (You can often achieve a similar result using a private Symbol property to cache the computed value directly on the object. See §6.10.3.)\n\n> WeakMap 的预期用途是允许将值与对象相关联而不会引起内存泄漏。例如，假设正在编写一个带有对象实参的函数，并且需要对该对象执行一些耗时的计算。为了提高效率，希望将计算出的值进行缓存以备后用。如果使用 Map 对象实现缓存，则将防止回收任何对象，但是通过使用 WeakMap，可以避免此问题。（通常可以使用私有的 Symbol 属性将计算的值直接缓存在对象上，从而获得相似的结果。请参见 §6.10.3。）\n\nWeakSet implements a set of objects that does not prevent those objects from being garbage collected. The WeakSet() constructor works like the Set() constructor, but WeakSet objects differ from Set objects in the same ways that WeakMap objects differ from Map objects:\n\n> WeakSet 实现了一组对象，这些对象不会阻止垃圾回收这些对象。 WeakSet() 构造函数的工作方式类似于 Set() 构造函数，但 WeakSet 对象与 Set 对象的区别与 WeakMap 对象与 Map 对象的区别相同：\n\nWeakSet does not allow primitive values as members.\n\n> WeakSet 不允许原始值作为成员。\n\nWeakSet implements only the add(), has(), and delete() methods and is not iterable.\n\n> WeakSet 仅实现 add()、has() 和 delete() 方法，并且不可迭代。\n\nWeakSet does not have a size property.\n\n> WeakSet 没有 size 属性。\n\nWeakSet is not frequently used: its use cases are like those for WeakMap. If you want to mark (or “brand”) an object as having some special property or type, for example, you could add it to a WeakSet. Then, elsewhere, when you want to check for that property or type, you can test for membership in that WeakSet. Doing this with a regular set would prevent all marked objects from being garbage collected, but this is not a concern when using WeakSet.\n\n> WeakSet 并不经常使用：其用例类似于 WeakMap 的用例。例如，如果要标记（或“烙印”）对象具有某些特殊属性或类型，则可以将其添加到 WeakSet 中。然后，在其他位置，当要检查该属性或类型时，可以测试该 WeakSet 中的成员身份。使用常规 Set 执行此操作将防止所有标记的对象被垃圾回收，但这在使用 WeakSet 时不必担心。\n\n## 11.2 Typed Arrays and Binary Data\nRegular JavaScript arrays can have elements of any type and can grow or shrink dynamically. JavaScript implementations perform lots of optimizations so that typical uses of JavaScript arrays are very fast. Nevertheless, they are still quite different from the array types of lower-level languages like C and Java. Typed arrays, which are new in ES6 [^3] are much closer to the low-level arrays of those languages. Typed arrays are not technically arrays (Array.isArray() returns false for them), but they implement all of the array methods described in §7.8 plus a few more of their own. They differ from regular arrays in some very important ways, however:\n\n> 常规 JavaScript 数组可以具有任何类型的元素，并且可以动态增长或收缩。JavaScript 实现执行了大量优化，因此 JavaScript 数组的典型用法非常快。但是，它们仍然与诸如 C 和 Java 的低级语言的数组类型有很大不同。 ES6 [^3] 中新增的类型化数组更接近那些语言的低级数组。从类型上讲，类型化数组不是数组（Array.isArray() 为其返回false），但是它们实现了 §7.8 中描述的所有数组方法以及它们自己的其他一些方法。但是，它们在某些非常重要的方面与常规数组有所不同： \n\nThe elements of a typed array are all numbers. Unlike regular JavaScript numbers, however, typed arrays allow you to specify the type (signed and unsigned integers and IEEE-754 floating point) and size (8 bits to 64 bits) of the numbers to be stored in the array.\n\n> 类型化数组的元素都是数字。 但是，与常规 JavaScript 数字不同，类型化数组允许指定要存储在数组中的数字的类型（有符号和无符号整数以及 IEEE-754 浮点数）和大小（8 位至 64 位）。 \n\nYou must specify the length of a typed array when you create it, and that length can never change.\n\n> 创建类型化数组时，必须指定其长度，并且该长度永远不会改变。 \n\nThe elements of a typed array are always initialized to 0 when the array is created.\n\n> 创建数组时，类型化数组的元素始终会初始化为 0。\n\n### 11.2.1 Typed Array Types\nJavaScript does not define a TypedArray class. Instead, there are 11 kinds of typed arrays, each with a different element type and constructor:\n> JavaScript 没有定义 TypedArray 类。相反，有11种类型化数组，每种类型具有不同的元素类型和构造函数： \n\nConstructor\tNumeric type\n\nInt8Array()\n\nsigned bytes\n\nUint8Array()\n\nunsigned bytes\n\nUint8ClampedArray()\n\nunsigned bytes without rollover\n\nInt16Array()\n\nsigned 16-bit short integers\n\nUint16Array()\n\nunsigned 16-bit short integers\n\nInt32Array()\n\nsigned 32-bit integers\n\nUint32Array()\n\nunsigned 32-bit integers\n\nBigInt64Array()\n\nsigned 64-bit BigInt values (ES2020)\n\nBigUint64Array()\n\nunsigned 64-bit BigInt values (ES2020)\n\nFloat32Array()\n\n32-bit floating-point value\n\nFloat64Array()\n\n64-bit floating-point value: a regular JavaScript number\n\nThe types whose names begin with Int hold signed integers, of 1, 2, or 4 bytes (8, 16, or 32 bits). The types whose names begin with Uint hold unsigned integers of those same lengths. The “BigInt” and “BigUint” types hold 64-bit integers, represented in JavaScript as BigInt values (see §3.2.5). The types that begin with Float hold floating-point numbers. The elements of a Float64Array are of the same type as regular JavaScript numbers. The elements of a Float32Array have lower precision and a smaller range but require only half the memory. (This type is called float in C and Java.)\n\nUint8ClampedArray is a special-case variant on Uint8Array. Both of these types hold unsigned bytes and can represent numbers between 0 and 255. With Uint8Array, if you store a value larger than 255 or less than zero into an array element, it “wraps around,” and you get some other value. This is how computer memory works at a low level, so this is very fast. Uint8ClampedArray does some extra type checking so that, if you store a value greater than 255 or less than 0, it “clamps” to 255 or 0 and does not wrap around. (This clamping behavior is required by the HTML `<canvas>` element’s low-level API for manipulating pixel colors.)\n\nEach of the typed array constructors has a BYTES_PER_ELEMENT property with the value 1, 2, 4, or 8, depending on the type.\n\n### 11.2.2 Creating Typed Arrays\nThe simplest way to create a typed array is to call the appropriate constructor with one numeric argument that specifies the number of elements you want in the array:\n```js\nlet bytes = new Uint8Array(1024);    // 1024 bytes\nlet matrix = new Float64Array(9);    // A 3x3 matrix\nlet point = new Int16Array(3);       // A point in 3D space\nlet rgba = new Uint8ClampedArray(4); // A 4-byte RGBA pixel value\nlet sudoku = new Int8Array(81);      // A 9x9 sudoku board\n```\nWhen you create typed arrays in this way, the array elements are all guaranteed to be initialized to 0, 0n, or 0.0. But if you know the values you want in your typed array, you can also specify those values when you create the array. Each of the typed array constructors has static from() and of() factory methods that work like Array.from() and Array.of():\n```js\nlet white = Uint8ClampedArray.of(255, 255, 255, 0);  // RGBA opaque white\n```\nRecall that the Array.from() factory method expects an array-like or iterable object as its first argument. The same is true for the typed array variants, except that the iterable or array-like object must also have numeric elements. Strings are iterable, for example, but it would make no sense to pass them to the from() factory method of a typed array.\n\nIf you are just using the one-argument version of from(), you can drop the .from and pass your iterable or array-like object directly to the constructor function, which behaves exactly the same. Note that both the constructor and the from() factory method allow you to copy existing typed arrays, while possibly changing the type:\n```js\nlet ints = Uint32Array.from(white);  // The same 4 numbers, but as ints\n```\nWhen you create a new typed array from an existing array, iterable, or array-like object, the values may be truncated in order to fit the type constraints of your array. There are no warnings or errors when this happens:\n```js\n// Floats truncated to ints, longer ints truncated to 8 bits\nUint8Array.of(1.23, 2.99, 45000) // => new Uint8Array([1, 2, 200])\n```\nFinally, there is one more way to create typed arrays that involves the ArrayBuffer type. An ArrayBuffer is an opaque reference to a chunk of memory. You can create one with the constructor; just pass in the number of bytes of memory you’d like to allocate:\n```js\nlet buffer = new ArrayBuffer(1024*1024);\nbuffer.byteLength   // => 1024*1024; one megabyte of memory\n```\nThe ArrayBuffer class does not allow you to read or write any of the bytes that you have allocated. But you can create typed arrays that use the buffer’s memory and that do allow you to read and write that memory. To do this, call the typed array constructor with an ArrayBuffer as the first argument, a byte offset within the array buffer as the second argument, and the array length (in elements, not in bytes) as the third argument. The second and third arguments are optional. If you omit both, then the array will use all of the memory in the array buffer. If you omit only the length argument, then your array will use all of the available memory between the start position and the end of the array. One more thing to bear in mind about this form of the typed array constructor: arrays must be memory aligned, so if you specify a byte offset, the value should be a multiple of the size of your type. The Int32Array() constructor requires a multiple of four, for example, and the Float64Array() requires a multiple of eight.\n\nGiven the ArrayBuffer created earlier, you could create typed arrays like these:\n```js\nlet asbytes = new Uint8Array(buffer);          // Viewed as bytes\nlet asints = new Int32Array(buffer);           // Viewed as 32-bit signed ints\nlet lastK = new Uint8Array(buffer, 1023*1024); // Last kilobyte as bytes\nlet ints2 = new Int32Array(buffer, 1024, 256); // 2nd kilobyte as 256 integers\n```\nThese four typed arrays offer four different views into the memory represented by the ArrayBuffer. It is important to understand that all typed arrays have an underlying ArrayBuffer, even if you do not explicitly specify one. If you call a typed array constructor without passing a buffer object, a buffer of the appropriate size will be automatically created. As described later, the buffer property of any typed array refers to its underlying ArrayBuffer object. The reason to work directly with ArrayBuffer objects is that sometimes you may want to have multiple typed array views of a single buffer.\n\n### 11.2.3 Using Typed Arrays\nOnce you have created a typed array, you can read and write its elements with regular square-bracket notation, just as you would with any other array-like object:\n```js\n// Return the largest prime smaller than n, using the sieve of Eratosthenes\nfunction sieve(n) {\n    let a = new Uint8Array(n+1);         // a[x] will be 1 if x is composite\n    let max = Math.floor(Math.sqrt(n));  // Don't do factors higher than this\n    let p = 2;                           // 2 is the first prime\n    while(p <= max) {                    // For primes less than max\n        for(let i = 2*p; i <= n; i += p) // Mark multiples of p as composite\n            a[i] = 1;\n        while(a[++p]) /* empty */;       // The next unmarked index is prime\n    }\n    while(a[n]) n--;                     // Loop backward to find the last prime\n    return n;                            // And return it\n}\n```\nThe function here computes the largest prime number smaller than the number you specify. The code is exactly the same as it would be with a regular JavaScript array, but using Uint8Array() instead of Array() makes the code run more than four times faster and use eight times less memory in my testing.\n\nTyped arrays are not true arrays, but they re-implement most array methods, so you can use them pretty much just like you’d use regular arrays:\n```js\nlet ints = new Int16Array(10);       // 10 short integers\nints.fill(3).map(x=>x*x).join(\"\")    // => \"9999999999\"\n```\nRemember that typed arrays have fixed lengths, so the length property is read-only, and methods that change the length of the array (such as push(), pop(), unshift(), shift(), and splice()) are not implemented for typed arrays. Methods that alter the contents of an array without changing the length (such as sort(), reverse(), and fill()) are implemented. Methods like map() and slice() that return new arrays return a typed array of the same type as the one they are called on.\n\n### 11.2.4 Typed Array Methods and Properties\nIn addition to standard array methods, typed arrays also implement a few methods of their own. The set() method sets multiple elements of a typed array at once by copying the elements of a regular or typed array into a typed array:\n```js\nlet bytes = new Uint8Array(1024);        // A 1K buffer\nlet pattern = new Uint8Array([0,1,2,3]); // An array of 4 bytes\nbytes.set(pattern);      // Copy them to the start of another byte array\nbytes.set(pattern, 4);   // Copy them again at a different offset\nbytes.set([0,1,2,3], 8); // Or just copy values direct from a regular array\nbytes.slice(0, 12)       // => new Uint8Array([0,1,2,3,0,1,2,3,0,1,2,3])\n```\nThe set() method takes an array or typed array as its first argument and an element offset as its optional second argument, which defaults to 0 if left unspecified. If you are copying values from one typed array to another, the operation will likely be extremely fast.\n\nTyped arrays also have a subarray method that returns a portion of the array on which it is called:\n```js\nlet ints = new Int16Array([0,1,2,3,4,5,6,7,8,9]);       // 10 short integers\nlet last3 = ints.subarray(ints.length-3, ints.length);  // Last 3 of them\nlast3[0]       // => 7: this is the same as ints[7]\n```\nsubarray() takes the same arguments as the slice() method and seems to work the same way. But there is an important difference. slice() returns the specified elements in a new, independent typed array that does not share memory with the original array. subarray() does not copy any memory; it just returns a new view of the same underlying values:\n```js\nints[9] = -1;  // Change a value in the original array and...\nlast3[2]       // => -1: it also changes in the subarray\n```\nThe fact that the subarray() method returns a new view of an existing array brings us back to the topic of ArrayBuffers. Every typed array has three properties that relate to the underlying buffer:\n```js\nlast3.buffer                 // The ArrayBuffer object for a typed array\nlast3.buffer === ints.buffer // => true: both are views of the same buffer\nlast3.byteOffset             // => 14: this view starts at byte 14 of the buffer\nlast3.byteLength             // => 6: this view is 6 bytes (3 16-bit ints) long\nlast3.buffer.byteLength      // => 20: but the underlying buffer has 20 bytes\n```\nThe buffer property is the ArrayBuffer of the array. byteOffset is the starting position of the array’s data within the underlying buffer. And byteLength is the length of the array’s data in bytes. For any typed array, a, this invariant should always be true:\n```js\na.length * a.BYTES_PER_ELEMENT === a.byteLength  // => true\n```\nArrayBuffers are just opaque chunks of bytes. You can access those bytes with typed arrays, but an ArrayBuffer is not itself a typed array. Be careful, however: you can use numeric array indexing with ArrayBuffers just as you can with any JavaScript object. Doing so does not give you access to the bytes in the buffer, but it can cause confusing bugs:\n```js\nlet bytes = new Uint8Array(8);\nbytes[0] = 1;           // Set the first byte to 1\nbytes.buffer[0]         // => undefined: buffer doesn't have index 0\nbytes.buffer[1] = 255;  // Try incorrectly to set a byte in the buffer\nbytes.buffer[1]         // => 255: this just sets a regular JS property\nbytes[1]                // => 0: the line above did not set the byte\n```\nWe saw previously that you can create an ArrayBuffer with the ArrayBuffer() constructor and then create typed arrays that use that buffer. Another approach is to create an initial typed array, then use the buffer of that array to create other views:\n```js\nlet bytes = new Uint8Array(1024);            // 1024 bytes\nlet ints = new Uint32Array(bytes.buffer);    // or 256 integers\nlet floats = new Float64Array(bytes.buffer); // or 128 doubles\n```\n### 11.2.5 DataView and Endianness\nTyped arrays allow you to view the same sequence of bytes in chunks of 8, 16, 32, or 64 bits. This exposes the “endianness”: the order in which bytes are arranged into longer words. For efficiency, typed arrays use the native endianness of the underlying hardware. On little-endian systems, the bytes of a number are arranged in an ArrayBuffer from least significant to most significant. On big-endian platforms, the bytes are arranged from most significant to least significant. You can determine the endianness of the underlying platform with code like this:\n```js\n// If the integer 0x00000001 is arranged in memory as 01 00 00 00, then\n// we're on a little-endian platform. On a big-endian platform, we'd get\n// bytes 00 00 00 01 instead.\nlet littleEndian = new Int8Array(new Int32Array([1]).buffer)[0] === 1;\n```\nToday, the most common CPU architectures are little-endian. Many network protocols, and some binary file formats, require big-endian byte ordering, however. If you’re using typed arrays with data that came from the network or from a file, you can’t just assume that the platform endianness matches the byte order of the data. In general, when working with external data, you can use Int8Array and Uint8Array to view the data as an array of individual bytes, but you should not use the other typed arrays with multibyte word sizes. Instead, you can use the DataView class, which defines methods for reading and writing values from an ArrayBuffer with explicitly specified byte ordering:\n```js\n// Assume we have a typed array of bytes of binary data to process. First,\n// we create a DataView object so we can flexibly read and write\n// values from those bytes\nlet view = new DataView(bytes.buffer,\n                        bytes.byteOffset,\n                        bytes.byteLength);\n\nlet int = view.getInt32(0);     // Read big-endian signed int from byte 0\nint = view.getInt32(4, false);  // Next int is also big-endian\nint = view.getUint32(8, true);  // Next int is little-endian and unsigned\nview.setUint32(8, int, false);  // Write it back in big-endian format\n```\nDataView defines 10 get methods for each of the 10 typed array classes (excluding Uint8ClampedArray). They have names like getInt16(), getUint32(), getBigInt64(), and getFloat64(). The first argument is the byte offset within the ArrayBuffer at which the value begins. All of these getter methods, other than getInt8() and getUint8(), accept an optional boolean value as their second argument. If the second argument is omitted or is false, big-endian byte ordering is used. If the second argument is true, little-endian ordering is used.\n\nDataView also defines 10 corresponding Set methods that write values into the underlying ArrayBuffer. The first argument is the offset at which the value begins. The second argument is the value to write. Each of the methods, except setInt8() and setUint8(), accepts an optional third argument. If the argument is omitted or is false, the value is written in big-endian format with the most significant byte first. If the argument is true, the value is written in little-endian format with the least significant byte first.\n\nTyped arrays and the DataView class give you all the tools you need to process binary data and enable you to write JavaScript programs that do things like decompressing ZIP files or extracting metadata from JPEG files.\n\n## 11.3 Pattern Matching with Regular Expressions\nA regular expression is an object that describes a textual pattern. The JavaScript RegExp class represents regular expressions, and both String and RegExp define methods that use regular expressions to perform powerful pattern-matching and search-and-replace functions on text. In order to use the RegExp API effectively, however, you must also learn how to describe patterns of text using the regular expression grammar, which is essentially a mini programming language of its own. Fortunately, the JavaScript regular expression grammar is quite similar to the grammar used by many other programming languages, so you may already be familiar with it. (And if you are not, the effort you invest in learning JavaScript regular expressions will probably be useful to you in other programming contexts as well.)\n\n> 正则表达式是描述文本模型的对象。JavaScript RegExp 类表示正则表达式，而 String 和 RegExp 都定义了使用正则表达式对文本执行强大的模式匹配和搜索替换功能的方法。但是，为了有效地使用 RegExp API，还必须学习如何使用正则表达式语法描述文本模型，该语法本质上是其自身的一种小型编程语言。幸运的是，JavaScript 正则表达式语法与许多其他编程语言所使用的语法非常相似，因此您可能已经很熟悉它。（如果不是这样，则在学习 JavaScript 正则表达式上投入的精力也可能有助于在你在其他编程环境使用。） \n\nThe subsections that follow describe the regular expression grammar first, and then, after explaining how to write regular expressions, they explain how you can use them with methods of the String and RegExp classes.\n\n> 接下来的小节首先描述正则表达式语法，然后在解释了如何编写正则表达式之后，它们解释了如何将其与 String 和 RegExp 类的方法一起使用。 \n\n### 11.3.1 Defining Regular Expressions\nIn JavaScript, regular expressions are represented by RegExp objects. RegExp objects may be created with the RegExp() constructor, of course, but they are more often created using a special literal syntax. Just as string literals are specified as characters within quotation marks, regular expression literals are specified as characters within a pair of slash (/) characters. Thus, your JavaScript code may contain lines like this:\n\n> 在 JavaScript 中，正则表达式以 RegExp 对象呈现。当然，RegExp 对象可能会使用 RegExp() 构造函数创建，但是经常会使用一个特殊的字面量语法来创建。和字符串文字被指定为引号内的字符一样，正则表达式文字被指定为一对斜杠 (/) 字符内的字符。因此你的 Javascript 代码可能会有这样的一行:\n\n```JavaScript\nlet pattern = /s$/;\n```\n\nThis line creates a new RegExp object and assigns it to the variable pattern. This particular RegExp object matches any string that ends with the letter “s.” This regular expression could have equivalently been defined with the RegExp() constructor, like this:\n\n> 这行创建了一个新的 RegExp 对象并且将变量模型赋值给它。这个特殊的 RegExp 对象匹配任何以字母“s”结尾的字符串。这个正则表达式等效于如下用 RegExp() 构造函数定义的对象：\n\n```JavaScript\nlet pattern = new RegExp(\"s$\");\n```\n\nRegular-expression pattern specifications consist of a series of characters. Most characters, including all alphanumeric characters, simply describe characters to be matched literally. Thus, the regular expression /java/ matches any string that contains the substring “java”. Other characters in regular expressions are not matched literally but have special significance. For example, the regular expression /s$/ contains two characters. The first, “s”, matches itself literally. The second, “$”, is a special meta-character that matches the end of a string. Thus, this regular expression matches any string that contains the letter “s” as its last character.\n\n> 正则表达式规定由一系列符号表示。大多数字符包括所有字母数字字符，都只是按字面描述要匹配的字符。但是正则表达式 `/java/` 匹配任何包含“java”的字符串。但是，正则表达式中的其他字符具有特殊意义，不是字面匹配的。例如，在正则表达式 `/s$/` 中包含两个字符。第一个“s”匹配它自身的字面量。第二个“$”，是一个特殊的元字符（meta-character）匹配的是一个字符串的结尾。因此这个正则表达式匹配的是以“s”结尾的字符串。\n\nAs we’ll see, regular expressions can also have one or more flag characters that affect how they work. Flags are specified following the second slash character in RegExp literals, or as a second string argument to the RegExp() constructor. If we wanted to match strings that end with “s” or “S”, for example, we could use the i flag with our regular expression to indicate that we want case-insensitive matching:\n\nlet pattern = /s$/i;\nThe following sections describe the various characters and meta-characters used in JavaScript regular expressions.\n\nLITERAL CHARACTERS\nAll alphabetic characters and digits match themselves literally in regular expressions. JavaScript regular expression syntax also supports certain nonalphabetic characters through escape sequences that begin with a backslash `(\\)`. For example, the sequence \\n matches a literal newline character in a string. Table 11-1 lists these characters.\n\nTable 11-1. Regular-expression literal characters\nCharacter\tMatches\nAlphanumeric character\n\nItself\n\n\\0\n\nThe NUL character (\\u0000)\n\n\\t\n\nTab (\\u0009)\n\n\\n\n\nNewline (\\u000A)\n\n\\v\n\nVertical tab (\\u000B)\n\n\\f\n\nForm feed (\\u000C)\n\n\\r\n\nCarriage return (\\u000D)\n\n\\xnn\n\nThe Latin character specified by the hexadecimal number nn; for example, \\x0A is the same as \\n.\n\n\\uxxxx\n\nThe Unicode character specified by the hexadecimal number xxxx; for example, \\u0009 is the same as \\t.\n\n\\u{n}\n\nThe Unicode character specified by the codepoint n, where n is one to six hexadecimal digits between 0 and 10FFFF. Note that this syntax is only supported in regular expressions that use the u flag.\n\n\\cX\n\nThe control character ^X; for example, \\cJ is equivalent to the newline character \\n.\n\nA number of punctuation characters have special meanings in regular expressions. They are:\n```\n^ $ . * + ? = ! : | \\ / ( ) [ ] { }\n```\nThe meanings of these characters are discussed in the sections that follow. Some of these characters have special meaning only within certain contexts of a regular expression and are treated literally in other contexts. As a general rule, however, if you want to include any of these punctuation characters literally in a regular expression, you must precede them with a `\\`. Other punctuation characters, such as quotation marks and @, do not have special meaning and simply match themselves literally in a regular expression.\n\nIf you can’t remember exactly which punctuation characters need to be escaped with a backslash, you may safely place a backslash before any punctuation character. On the other hand, note that many letters and numbers have special meaning when preceded by a backslash, so any letters or numbers that you want to match literally should not be escaped with a backslash. To include a backslash character literally in a regular expression, you must escape it with a backslash, of course. For example, the following regular expression matches any string that includes a backslash: `/\\\\/`. (And if you use the RegExp() constructor, keep in mind that any backslashes in your regular expression need to be doubled, since strings also use backslashes as an escape character.)\n\nCHARACTER CLASSES\nIndividual literal characters can be combined into character classes by placing them within square brackets. A character class matches any one character that is contained within it. Thus, the regular expression `/[abc]/` matches any one of the letters a, b, or c. Negated character classes can also be defined; these match any character except those contained within the brackets. A negated character class is specified by placing a caret (^) as the first character inside the left bracket. The RegExp `/[^abc]/` matches any one character other than a, b, or c. Character classes can use a hyphen to indicate a range of characters. To match any one lowercase character from the Latin alphabet, use `/[a-z]/`, and to match any letter or digit from the Latin alphabet, use `/[a-zA-Z0-9]/.` (And if you want to include an actual hyphen in your character class, simply make it the last character before the right bracket.)\n\nBecause certain character classes are commonly used, the JavaScript regular-expression syntax includes special characters and escape sequences to represent these common classes. For example, \\s matches the space character, the tab character, and any other Unicode whitespace character; \\S matches any character that is not Unicode whitespace. Table 11-2 lists these characters and summarizes character-class syntax. (Note that several of these character-class escape sequences match only ASCII characters and have not been extended to work with Unicode characters. You can, however, explicitly define your own Unicode character classes; for example, `/[\\u0400-\\u04FF]/` matches any one Cyrillic character.)\n\nTable 11-2. Regular expression character classes\nCharacter\tMatches\n[...]\n\nAny one character between the brackets.\n\n[^...]\n\nAny one character not between the brackets.\n\n.\n\nAny character except newline or another Unicode line terminator. Or, if the RegExp uses the s flag, then a period matches any character, including line terminators.\n\n\\w\n\nAny ASCII word character. Equivalent to [a-zA-Z0-9_].\n\n\\W\n\nAny character that is not an ASCII word character. Equivalent to [^a-zA-Z0-9_].\n\n\\s\n\nAny Unicode whitespace character.\n\n\\S\n\nAny character that is not Unicode whitespace.\n\n\\d\n\nAny ASCII digit. Equivalent to [0-9].\n\n\\D\n\nAny character other than an ASCII digit. Equivalent to [^0-9].\n\n[\\b]\n\nA literal backspace (special case).\n\nNote that the special character-class escapes can be used within square brackets. \\s matches any whitespace character, and \\d matches any digit, so `/[\\s\\d]/` matches any one whitespace character or digit. Note that there is one special case. As you’ll see later, the \\b escape has a special meaning. When used within a character class, however, it represents the backspace character. Thus, to represent a backspace character literally in a regular expression, use the character class with one element: `/[\\b]/`.\n\nUNICODE CHARACTER CLASSES\nIn ES2018, if a regular expression uses the u flag, then character classes \\p{...} and its negation \\P{...} are supported. (As of early 2020, this is implemented by Node, Chrome, Edge, and Safari, but not Firefox.) These character classes are based on properties defined by the Unicode standard, and the set of characters they represent may change as Unicode evolves.\n\nThe \\d character class matches only ASCII digits. If you want to match one decimal digit from any of the world’s writing systems, you can use /\\p{Decimal_Number}/u. And if you want to match any one character that is not a decimal digit in any language, you can capitalize the p and write \\P{Decimal_Number}. If you want to match any number-like character, including fractions and roman numerals, you can use \\p{Number}. Note that “Decimal_Number” and “Number” are not specific to JavaScript or to regular expression grammar: it is the name of a category of characters defined by the Unicode standard.\n\nThe \\w character class only works for ASCII text, but with \\p, we can approximate an internationalized version like this:\n```js\n/[\\p{Alphabetic}\\p{Decimal_Number}\\p{Mark}]/u\n```\n(Though to be fully compatible with the complexity of the world’s languages, we really need to add in the categories “Connector_Punctuation” and “Join_Control” as well.)\n\nAs a final example, the \\p syntax also allows us to define regular expressions that match characters from a particular alphabet or script:\n```js\nlet greekLetter = /\\p{Script=Greek}/u;\nlet cyrillicLetter = /\\p{Script=Cyrillic}/u;\n```\n#### REPETITION\nWith the regular expression syntax you’ve learned so far, you can describe a two-digit number as `/\\d\\d/` and a four-digit number as `/\\d\\d\\d\\d/`. But you don’t have any way to describe, for example, a number that can have any number of digits or a string of three letters followed by an optional digit. These more complex patterns use regular expression syntax that specifies how many times an element of a regular expression may be repeated.\n\nThe characters that specify repetition always follow the pattern to which they are being applied. Because certain types of repetition are quite commonly used, there are special characters to represent these cases. For example, + matches one or more occurrences of the previous pattern.\n\nTable 11-3 summarizes the repetition syntax.\n\nTable 11-3. Regular expression repetition characters\nCharacter\tMeaning\n{n,m}\n\nMatch the previous item at least n times but no more than m times.\n\n{n,}\n\nMatch the previous item n or more times.\n\n{n}\n\nMatch exactly n occurrences of the previous item.\n\n?\n\nMatch zero or one occurrences of the previous item. That is, the previous item is optional. Equivalent to {0,1}.\n\n+\n\nMatch one or more occurrences of the previous item. Equivalent to {1,}.\n\n*\n\nMatch zero or more occurrences of the previous item. Equivalent to {0,}.\n\nThe following lines show some examples:\n```js\nlet r = /\\d{2,4}/; // Match between two and four digits\nr = /\\w{3}\\d?/;    // Match exactly three word characters and an optional digit\nr = /\\s+java\\s+/;  // Match \"java\" with one or more spaces before and after\nr = /[^(]*/;       // Match zero or more characters that are not open parens\n```\nNote that in all of these examples, the repetition specifiers apply to the single character or character class that precedes them. If you want to match repetitions of more complicated expressions, you’ll need to define a group with parentheses, which are explained in the following sections.\n\nBe careful when using the * and ? repetition characters. Since these characters may match zero instances of whatever precedes them, they are allowed to match nothing. For example, the regular expression /a*/ actually matches the string “bbbb” because the string contains zero occurrences of the letter a!\n\n#### NON-GREEDY REPETITION\nThe repetition characters listed in Table 11-3 match as many times as possible while still allowing any following parts of the regular expression to match. We say that this repetition is “greedy.” It is also possible to specify that repetition should be done in a non-greedy way. Simply follow the repetition character or characters with a question mark: ??, +?, *?, or even {1,5}?. For example, the regular expression /a+/ matches one or more occurrences of the letter a. When applied to the string “aaa”, it matches all three letters. But /a+?/ matches one or more occurrences of the letter a, matching as few characters as necessary. When applied to the same string, this pattern matches only the first letter a.\n\nUsing non-greedy repetition may not always produce the results you expect. Consider the pattern /a+b/, which matches one or more a’s, followed by the letter b. When applied to the string “aaab”, it matches the entire string. Now let’s use the non-greedy version: /a+?b/. This should match the letter b preceded by the fewest number of a’s possible. When applied to the same string “aaab”, you might expect it to match only one a and the last letter b. In fact, however, this pattern matches the entire string, just like the greedy version of the pattern. This is because regular expression pattern matching is done by finding the first position in the string at which a match is possible. Since a match is possible starting at the first character of the string, shorter matches starting at subsequent characters are never even considered.\n\n#### ALTERNATION, GROUPING, AND REFERENCES\nThe regular expression grammar includes special characters for specifying alternatives, grouping subexpressions, and referring to previous subexpressions. The | character separates alternatives. For example, `/ab|cd|ef/` matches the string “ab” or the string “cd” or the string “ef”. And `/\\d{3}|[a-z]{4}/ `matches either three digits or four lowercase letters.\n\nNote that alternatives are considered left to right until a match is found. If the left alternative matches, the right alternative is ignored, even if it would have produced a “better” match. Thus, when the pattern `/a|ab/` is applied to the string “ab”, it matches only the first letter.\n\nParentheses have several purposes in regular expressions. One purpose is to group separate items into a single subexpression so that the items can be treated as a single unit by `|, *, +, ?,` and so on. For example, `/java(script)?/` matches “java” followed by the optional “script”. And `/(ab|cd)+|ef/` matches either the string “ef” or one or more repetitions of either of the strings “ab” or “cd”.\n\nAnother purpose of parentheses in regular expressions is to define subpatterns within the complete pattern. When a regular expression is successfully matched against a target string, it is possible to extract the portions of the target string that matched any particular parenthesized subpattern. (You’ll see how these matching substrings are obtained later in this section.) For example, suppose you are looking for one or more lowercase letters followed by one or more digits. You might use the pattern `/[a-z]+\\d+/`. But suppose you only really care about the digits at the end of each match. If you put that part of the pattern in parentheses (`/[a-z]+(\\d+)/`), you can extract the digits from any matches you find, as explained later.\n\nA related use of parenthesized subexpressions is to allow you to refer back to a subexpression later in the same regular expression. This is done by following a `\\` character by a digit or digits. The digits refer to the position of the parenthesized subexpression within the regular expression. For example, `\\1` refers back to the first subexpression, and `\\3` refers to the third. Note that, because subexpressions can be nested within others, it is the position of the left parenthesis that is counted. In the following regular expression, for example, the nested subexpression `([Ss]cript)` is referred to as `\\2`:\n```js\n/([Jj]ava([Ss]cript)?)\\sis\\s(fun\\w*)/\n```\nA reference to a previous subexpression of a regular expression does not refer to the pattern for that subexpression but rather to the text that matched the pattern. Thus, references can be used to enforce a constraint that separate portions of a string contain exactly the same characters. For example, the following regular expression matches zero or more characters within single or double quotes. However, it does not require the opening and closing quotes to match (i.e., both single quotes or both double quotes):\n```js\n/['\"][^'\"]*['\"]/\n```\nTo require the quotes to match, use a reference:\n```js\n/(['\"])[^'\"]*\\1/\n```\nThe \\1 matches whatever the first parenthesized subexpression matched. In this example, it enforces the constraint that the closing quote match the opening quote. This regular expression does not allow single quotes within double-quoted strings or vice versa. (It is not legal to use a reference within a character class, so you cannot write: `/(['\"])[^\\1]*\\1/`.)\n\nWhen we cover the RegExp API later, you’ll see that this kind of reference to a parenthesized subexpression is a powerful feature of regular-expression search-and-replace operations.\n\nIt is also possible to group items in a regular expression without creating a numbered reference to those items. Instead of simply grouping the items within ( and ), begin the group with (?: and end it with ). Consider the following pattern:\n```js\n/([Jj]ava(?:[Ss]cript)?)\\sis\\s(fun\\w*)/\n```\nIn this example, the subexpression `(?:[Ss]cript)` is used simply for grouping, so the ? repetition character can be applied to the group. These modified parentheses do not produce a reference, so in this regular expression, `\\2` refers to the text matched by `(fun\\w*)`.\n\nTable 11-4 summarizes the regular expression alternation, grouping, and referencing operators.\n\nTable 11-4. Regular expression alternation, grouping, and reference characters\nCharacter\tMeaning\n|\n\nAlternation: match either the subexpression to the left or the subexpression to the right.\n\n(...)\n\nGrouping: group items into a single unit that can be used with *, +, ?, |, and so on. Also remember the characters that match this group for use with later references.\n\n(?:...)\n\nGrouping only: group items into a single unit, but do not remember the characters that match this group.\n\n\\n\n\nMatch the same characters that were matched when group number n was first matched. Groups are subexpressions within (possibly nested) parentheses. Group numbers are assigned by counting left parentheses from left to right. Groups formed with (?: are not numbered.\n\nNAMED CAPTURE GROUPS\nES2018 standardizes a new feature that can make regular expressions more self-documenting and easier to understand. This new feature is known as “named capture groups” and it allows us to associate a name with each left parenthesis in a regular expression so that we can refer to the matching text by name rather than by number. Equally important: using names allows someone reading the code to more easily understand the purpose of that portion of the regular expression. As of early 2020, this feature is implemented in Node, Chrome, Edge, and Safari, but not yet by Firefox.\n\nTo name a group, use (?<...> instead of ( and put the name between the angle brackets. For example, here is a regular expression that might be used to check the formatting of the final line of a US mailing address:\n```js\n/(?<city>\\w+) (?<state>[A-Z]{2}) (?<zipcode>\\d{5})(?<zip9>-\\d{4})?/\n```\nNotice how much context the group names provide to make the regular expression easier to understand. In §11.3.2, when we discuss the String replace() and match() methods and the RegExp exec() method, you’ll see how the RegExp API allows you to refer to the text that matches each of these groups by name rather than by position.\n\nIf you want to refer back to a named capture group within a regular expression, you can do that by name as well. In the preceding example, we were able to use a regular expression “backreference” to write a RegExp that would match a single- or double-quoted string where the open and close quotes had to match. We could rewrite this RegExp using a named capturing group and a named backreference like this:\n```js\n/(?<quote>['\"])[^'\"]*\\k<quote>/\n```\nThe `\\k<quote>` is a named backreference to the named group that captures the open quotation mark.\n\nSPECIFYING MATCH POSITION\nAs described earlier, many elements of a regular expression match a single character in a string. For example, \\s matches a single character of whitespace. Other regular expression elements match the positions between characters instead of actual characters. \\b, for example, matches an ASCII word boundary—the boundary between a \\w (ASCII word character) and a \\W (nonword character), or the boundary between an ASCII word character and the beginning or end of a string.4 Elements such as \\b do not specify any characters to be used in a matched string; what they do specify, however, are legal positions at which a match can occur. Sometimes these elements are called regular expression anchors because they anchor the pattern to a specific position in the search string. The most commonly used anchor elements are ^, which ties the pattern to the beginning of the string, and $, which anchors the pattern to the end of the string.\n\nFor example, to match the word “JavaScript” on a line by itself, you can use the regular expression /^JavaScript$/. If you want to search for “Java” as a word by itself (not as a prefix, as it is in “JavaScript”), you can try the pattern /\\sJava\\s/, which requires a space before and after the word. But there are two problems with this solution. First, it does not match “Java” at the beginning or the end of a string, but only if it appears with space on either side. Second, when this pattern does find a match, the matched string it returns has leading and trailing spaces, which is not quite what’s needed. So instead of matching actual space characters with \\s, match (or anchor to) word boundaries with \\b. The resulting expression is /\\bJava\\b/. The element \\B anchors the match to a location that is not a word boundary. Thus, the pattern `/\\B[Ss]cript/` matches “JavaScript” and “postscript”, but not “script” or “Scripting”.\n\nYou can also use arbitrary regular expressions as anchor conditions. If you include an expression within (?= and ) characters, it is a lookahead assertion, and it specifies that the enclosed characters must match, without actually matching them. For example, to match the name of a common programming language, but only if it is followed by a colon, you could use `/[Jj]ava([Ss]cript)?(?=\\:)/`. This pattern matches the word “JavaScript” in “JavaScript: The Definitive Guide”, but it does not match “Java” in “Java in a Nutshell” because it is not followed by a colon.\n\nIf you instead introduce an assertion with (?!, it is a negative lookahead assertion, which specifies that the following characters must not match. For example, `/Java(?!Script)([A-Z]\\w*)/` matches “Java” followed by a capital letter and any number of additional ASCII word characters, as long as “Java” is not followed by “Script”. It matches “JavaBeans” but not “Javanese”, and it matches “JavaScrip” but not “JavaScript” or “JavaScripter”. Table 11-5 summarizes regular expression anchors.\n\nTable 11-5. Regular expression anchor characters\nCharacter\tMeaning\n^\n\nMatch the beginning of the string or, with the m flag, the beginning of a line.\n\n$\n\nMatch the end of the string and, with the m flag, the end of a line.\n\n\\b\n\nMatch a word boundary. That is, match the position between a \\w character and a \\W character or between a \\w character and the beginning or end of a string. (Note, however, that [\\b] matches backspace.)\n\n\\B\n\nMatch a position that is not a word boundary.\n\n(?=p)\n\nA positive lookahead assertion. Require that the following characters match the pattern p, but do not include those characters in the match.\n\n(?!p)\n\nA negative lookahead assertion. Require that the following characters do not match the pattern p.\n\n#### LOOKBEHIND ASSERTIONS\nES2018 extends regular expression syntax to allow “lookbehind” assertions. These are like lookahead assertions but refer to text before the current match position. As of early 2020, these are implemented in Node, Chrome, and Edge, but not Firefox or Safari.\n\nSpecify a positive lookbehind assertion with (?<=...) and a negative lookbehind assertion with (?<!...). For example, if you were working with US mailing addresses, you could match a 5-digit zip code, but only when it follows a two-letter state abbreviation, like this:\n```js\n/(?<= [A-Z]{2} )\\d{5}/\n```\nAnd you could match a string of digits that is not preceded by a Unicode currency symbol with a negative lookbehind assertion like this:\n```js\n/(?<![\\p{Currency_Symbol}\\d.])\\d+(\\.\\d+)?/u\n```\n#### FLAGS\nEvery regular expression can have one or more flags associated with it to alter its matching behavior. JavaScript defines six possible flags, each of which is represented by a single letter. Flags are specified after the second / character of a regular expression literal or as a string passed as the second argument to the RegExp() constructor. The supported flags and their meanings are:\n\ng\nThe g flag indicates that the regular expression is “global”—that is, that we intend to use it to find all matches within a string rather than just finding the first match. This flag does not alter the way that pattern matching is done, but, as we’ll see later, it does alter the behavior of the String match() method and the RegExp exec() method in important ways.\n\ni\nThe i flag specifies that pattern matching should be case-insensitive.\n\nm\nThe m flag specifies that matching should be done in “multiline” mode. It says that the RegExp will be used with multiline strings and that the ^ and $ anchors should match both the beginning and end of the string and also the beginning and end of individual lines within the string.\n\ns\nLike the m flag, the s flag is also useful when working with text that includes newlines. Normally, a “.” in a regular expression matches any character except a line terminator. When the s flag is used, however, “.” will match any character, including line terminators. The s flag was added to JavaScript in ES2018 and, as of early 2020, is supported in Node, Chrome, Edge, and Safari, but not Firefox.\n\nu\nThe u flag stands for Unicode, and it makes the regular expression match full Unicode codepoints rather than matching 16-bit values. This flag was introduced in ES6, and you should make a habit of using it on all regular expressions unless you have some reason not to. If you do not use this flag, then your RegExps will not work well with text that includes emoji and other characters (including many Chinese characters) that require more than 16 bits. Without the u flag, the “.” character matches any 1 UTF-16 16-bit value. With the flag, however, “.” matches one Unicode codepoint, including those that have more than 16 bits. Setting the u flag on a RegExp also allows you to use the new \\u{...} escape sequence for Unicode character and also enables the \\p{...} notation for Unicode character classes.\n\ny\nThe y flag indicates that the regular expression is “sticky” and should match at the beginning of a string or at the first character following the previous match. When used with a regular expression that is designed to find a single match, it effectively treats that regular expression as if it begins with ^ to anchor it to the beginning of the string. This flag is more useful with regular expressions that are used repeatedly to find all matches within a string. In this case, it causes special behavior of the String match() method and the RegExp exec() method to enforce that each subsequent match is anchored to the string position at which the last one ended.\n\nThese flags may be specified in any combination and in any order. For example, if you want your regular expression to be Unicode-aware to do case-insensitive matching and you intend to use it to find multiple matches within a string, you would specify the flags uig, gui, or any other permutation of these three letters.\n\n### 11.3.2 String Methods for Pattern Matching\nUntil now, we have been describing the grammar used to define regular expressions, but not explaining how those regular expressions can actually be used in JavaScript code. We are now switching to cover the API for using RegExp objects. This section begins by explaining the string methods that use regular expressions to perform pattern matching and search-and-replace operations. The sections that follow this one continue the discussion of pattern matching with JavaScript regular expressions by discussing the RegExp object and its methods and properties.\n\nSEARCH()\nStrings support four methods that use regular expressions. The simplest is search(). This method takes a regular expression argument and returns either the character position of the start of the first matching substring or −1 if there is no match:\n```js\n\"JavaScript\".search(/script/ui)  // => 4\n\"Python\".search(/script/ui)      // => -1\n```\nIf the argument to search() is not a regular expression, it is first converted to one by passing it to the RegExp constructor. search() does not support global searches; it ignores the g flag of its regular expression argument.\n\nREPLACE()\nThe replace() method performs a search-and-replace operation. It takes a regular expression as its first argument and a replacement string as its second argument. It searches the string on which it is called for matches with the specified pattern. If the regular expression has the g flag set, the replace() method replaces all matches in the string with the replacement string; otherwise, it replaces only the first match it finds. If the first argument to replace() is a string rather than a regular expression, the method searches for that string literally rather than converting it to a regular expression with the RegExp() constructor, as search() does. As an example, you can use replace() as follows to provide uniform capitalization of the word “JavaScript” throughout a string of text:\n```js\n// No matter how it is capitalized, replace it with the correct capitalization\ntext.replace(/javascript/gi, \"JavaScript\");\n```\nreplace() is more powerful than this, however. Recall that parenthesized subexpressions of a regular expression are numbered from left to right and that the regular expression remembers the text that each subexpression matches. If a $ followed by a digit appears in the replacement string, replace() replaces those two characters with the text that matches the specified subexpression. This is a very useful feature. You can use it, for example, to replace quotation marks in a string with other characters:\n```js\n// A quote is a quotation mark, followed by any number of\n// nonquotation mark characters (which we capture), followed\n// by another quotation mark.\nlet quote = /\"([^\"]*)\"/g;\n// Replace the straight quotation marks with guillemets\n// leaving the quoted text (stored in $1) unchanged.\n'He said \"stop\"'.replace(quote, '«$1»')  // => 'He said «stop»'\n```\nIf your RegExp uses named capture groups, then you can refer to the matching text by name rather than by number:\n```js\nlet quote = /\"(?<quotedText>[^\"]*)\"/g;\n'He said \"stop\"'.replace(quote, '«$<quotedText>»')  // => 'He said «stop»'\n```\nInstead of passing a replacement string as the second argument to replace(), you can also pass a function that will be invoked to compute the replacement value. The replacement function is invoked with a number of arguments. First is the entire matched text. Next, if the RegExp has capturing groups, then the substrings that were captured by those groups are passed as arguments. The next argument is the position within the string at which the match was found. After that, the entire string that replace() was called on is passed. And finally, if the RegExp contained any named capture groups, the last argument to the replacement function is an object whose property names match the capture group names and whose values are the matching text. As an example, here is code that uses a replacement function to convert decimal integers in a string to hexadecimal:\n```js\nlet s = \"15 times 15 is 225\";\ns.replace(/\\d+/gu, n => parseInt(n).toString(16))  // => \"f times f is e1\"\n```\nMATCH()\nThe match() method is the most general of the String regular expression methods. It takes a regular expression as its only argument (or converts its argument to a regular expression by passing it to the RegExp() constructor) and returns an array that contains the results of the match, or null if no match is found. If the regular expression has the g flag set, the method returns an array of all matches that appear in the string. For example:\n```js\n\"7 plus 8 equals 15\".match(/\\d+/g)  // => [\"7\", \"8\", \"15\"]\n```\nIf the regular expression does not have the g flag set, match() does not do a global search; it simply searches for the first match. In this nonglobal case, match() still returns an array, but the array elements are completely different. Without the g flag, the first element of the returned array is the matching string, and any remaining elements are the substrings matching the parenthesized capturing groups of the regular expression. Thus, if match() returns an array a, a[0] contains the complete match, a[1] contains the substring that matched the first parenthesized expression, and so on. To draw a parallel with the replace() method, a[1] is the same string as $1, a[2] is the same as $2, and so on.\n\nFor example, consider parsing a URL5 with the following code:\n```js\n// A very simple URL parsing RegExp\nlet url = /(\\w+):\\/\\/([\\w.]+)\\/(\\S*)/;\nlet text = \"Visit my blog at http://www.example.com/~david\";\nlet match = text.match(url);\nlet fullurl, protocol, host, path;\nif (match !== null) {\n    fullurl = match[0];   // fullurl == \"http://www.example.com/~david\"\n    protocol = match[1];  // protocol == \"http\"\n    host = match[2];      // host == \"www.example.com\"\n    path = match[3];      // path == \"~david\"\n}\n```\nIn this non-global case, the array returned by match() also has some object properties in addition to the numbered array elements. The input property refers to the string on which match() was called. The index property is the position within that string at which the match starts. And if the regular expression contains named capture groups, then the returned array also has a groups property whose value is an object. The properties of this object match the names of the named groups, and the values are the matching text. We could rewrite the previous URL parsing example, for example, like this:\n```js\nlet url = /(?<protocol>\\w+):\\/\\/(?<host>[\\w.]+)\\/(?<path>\\S*)/;\nlet text = \"Visit my blog at http://www.example.com/~david\";\nlet match = text.match(url);\nmatch[0]               // => \"http://www.example.com/~david\"\nmatch.input            // => text\nmatch.index            // => 17\nmatch.groups.protocol  // => \"http\"\nmatch.groups.host      // => \"www.example.com\"\nmatch.groups.path      // => \"~david\"\n```\nWe’ve seen that match() behaves quite differently depending on whether the RegExp has the g flag set or not. There are also important but less dramatic differences in behavior when the y flag is set. Recall that the y flag makes a regular expression “sticky” by constraining where in the string matches can begin. If a RegExp has both the g and y flags set, then match() returns an array of matched strings, just as it does when g is set without y. But the first match must begin at the start of the string, and each subsequent match must begin at the character immediately following the previous match.\n\nIf the y flag is set without g, then match() tries to find a single match, and, by default, this match is constrained to the start of the string. You can change this default match start position, however, by setting the lastIndex property of the RegExp object at the index at which you want to match at. If a match is found, then this lastIndex will be automatically updated to the first character after the match, so if you call match() again, in this case, it will look for a subsequent match. (lastIndex may seem like a strange name for a property that specifies the position at which to begin the next match. We will see it again when we cover the RegExp exec() method, and its name may make more sense in that context.)\n```js\nlet vowel = /[aeiou]/y;  // Sticky vowel match\n\"test\".match(vowel)      // => null: \"test\" does not begin with a vowel\nvowel.lastIndex = 1;     // Specify a different match position\n\"test\".match(vowel)[0]   // => \"e\": we found a vowel at position 1\nvowel.lastIndex          // => 2: lastIndex was automatically updated\n\"test\".match(vowel)      // => null: no vowel at position 2\nvowel.lastIndex          // => 0: lastIndex gets reset after failed match\n```\nIt is worth noting that passing a non-global regular expression to the match() method of a string is the same as passing the string to the exec() method of the regular expression: the returned array and its properties are the same in both cases.\n\nMATCHALL()\nThe matchAll() method is defined in ES2020, and as of early 2020 is implemented by modern web browsers and Node. matchAll() expects a RegExp with the g flag set. Instead of returning an array of matching substrings like match() does, however, it returns an iterator that yields the kind of match objects that match() returns when used with a non-global RegExp. This makes matchAll() the easiest and most general way to loop through all matches within a string.\n\nYou might use matchAll() to loop through the words in a string of text like this:\n```js\n// One or more Unicode alphabetic characters between word boundaries\nconst words = /\\b\\p{Alphabetic}+\\b/gu; // \\p is not supported in Firefox yet\nconst text = \"This is a naïve test of the matchAll() method.\";\nfor(let word of text.matchAll(words)) {\n    console.log(`Found '${word[0]}' at index ${word.index}.`);\n}\n```\nYou can set the lastIndex property of a RegExp object to tell matchAll() what index in the string to begin matching at. Unlike the other pattern-matching methods, however, matchAll() never modifies the lastIndex property of the RegExp you call it on, and this makes it much less likely to cause bugs in your code.\n\nSPLIT()\nThe last of the regular expression methods of the String object is split(). This method breaks the string on which it is called into an array of substrings, using the argument as a separator. It can be used with a string argument like this:\n```js\n\"123,456,789\".split(\",\")           // => [\"123\", \"456\", \"789\"]\n```\nThe split() method can also take a regular expression as its argument, and this allows you to specify more general separators. Here we call it with a separator that includes an arbitrary amount of whitespace on either side:\n```js\n\"1, 2, 3,\\n4, 5\".split(/\\s*,\\s*/)  // => [\"1\", \"2\", \"3\", \"4\", \"5\"]\n```\nSurprisingly, if you call split() with a RegExp delimiter and the regular expression includes capturing groups, then the text that matches the capturing groups will be included in the returned array. For example:\n```js\nconst htmlTag = /<([^>]+)>/;  // < followed by one or more non->, followed by >\n\"Testing<br/>1,2,3\".split(htmlTag)  // => [\"Testing\", \"br/\", \"1,2,3\"]\n```\n### 11.3.3 The RegExp Class\nThis section documents the RegExp() constructor, the properties of RegExp instances, and two important pattern-matching methods defined by the RegExp class.\n\nThe RegExp() constructor takes one or two string arguments and creates a new RegExp object. The first argument to this constructor is a string that contains the body of the regular expression—the text that would appear within slashes in a regular-expression literal. Note that both string literals and regular expressions use the \\ character for escape sequences, so when you pass a regular expression to RegExp() as a string literal, you must replace each \\ character with \\\\. The second argument to RegExp() is optional. If supplied, it indicates the regular expression flags. It should be g, i, m, s, u, y, or any combination of those letters.\n\nFor example:\n```js\n// Find all five-digit numbers in a string. Note the double \\\\ in this case.\nlet zipcode = new RegExp(\"\\\\d{5}\", \"g\");\n```\nThe RegExp() constructor is useful when a regular expression is being dynamically created and thus cannot be represented with the regular expression literal syntax. For example, to search for a string entered by the user, a regular expression must be created at runtime with RegExp().\n\nInstead of passing a string as the first argument to RegExp(), you can also pass a RegExp object. This allows you to copy a regular expression and change its flags:\n```js\nlet exactMatch = /JavaScript/;\nlet caseInsensitive = new RegExp(exactMatch, \"i\");\n```\n#### REGEXP PROPERTIES\nRegExp objects have the following properties:\n\nsource\nThis read-only property is the source text of the regular expression: the characters that appear between the slashes in a RegExp literal.\n\nflags\nThis read-only property is a string that specifies the set of letters that represent the flags for the RegExp.\n\nglobal\nA read-only boolean property that is true if the g flag is set.\n\nignoreCase\nA read-only boolean property that is true if the i flag is set.\n\nmultiline\nA read-only boolean property that is true if the m flag is set.\n\ndotAll\nA read-only boolean property that is true if the s flag is set.\n\nunicode\nA read-only boolean property that is true if the u flag is set.\n\nsticky\nA read-only boolean property that is true if the y flag is set.\n\nlastIndex\nThis property is a read/write integer. For patterns with the g or y flags, it specifies the character position at which the next search is to begin. It is used by the exec() and test() methods, described in the next two subsections.\n\nTEST()\nThe test() method of the RegExp class is the simplest way to use a regular expression. It takes a single string argument and returns true if the string matches the pattern or false if it does not match.\n\ntest() works by simply calling the (much more complicated) exec() method described in the next section and returning true if exec() returns a non-null value. Because of this, if you use test() with a RegExp that uses the g or y flags, then its behavior depends on the value of the lastIndex property of the RegExp object, which can change unexpectedly. See “The lastIndex Property and RegExp Reuse” for more details.\n\nEXEC()\nThe RegExp exec() method is the most general and powerful way to use regular expressions. It takes a single string argument and looks for a match in that string. If no match is found, it returns null. If a match is found, however, it returns an array just like the array returned by the match() method for non-global searches. Element 0 of the array contains the string that matched the regular expression, and any subsequent array elements contain the substrings that matched any capturing groups. The returned array also has named properties: the index property contains the character position at which the match occurred, and the input property specifies the string that was searched, and the groups property, if defined, refers to an object that holds the substrings matching the any named capturing groups.\n\nUnlike the String match() method, exec() returns the same kind of array whether or not the regular expression has the global g flag. Recall that match() returns an array of matches when passed a global regular expression. exec(), by contrast, always returns a single match and provides complete information about that match. When exec() is called on a regular expression that has either the global g flag or the sticky y flag set, it consults the lastIndex property of the RegExp object to determine where to start looking for a match. (And if the y flag is set, it also constrains the match to begin at that position.) For a newly created RegExp object, lastIndex is 0, and the search begins at the start of the string. But each time exec() successfully finds a match, it updates the lastIndex property to the index of the character immediately after the matched text. If exec() fails to find a match, it resets lastIndex to 0. This special behavior allows you to call exec() repeatedly in order to loop through all the regular expression matches in a string. (Although, as we’ve described, in ES2020 and later, the matchAll() method of String is an easier way to loop through all matches.) For example, the loop in the following code will run twice:\n```js\nlet pattern = /Java/g;\nlet text = \"JavaScript > Java\";\nlet match;\nwhile((match = pattern.exec(text)) !== null) {\n    console.log(`Matched ${match[0]} at ${match.index}`);\n    console.log(`Next search begins at ${pattern.lastIndex}`);\n}\n```\nTHE LASTINDEX PROPERTY AND REGEXP REUSE\nAs you have seen already, JavaScript’s regular expression API is complicated. The use of the lastIndex property with the g and y flags is a particularly awkward part of this API. When you use these flags, you need to be particularly careful when calling the match(), exec(), or test() methods because the behavior of these methods depends on lastIndex, and the value of lastIndex depends on what you have previously done with the RegExp object. This makes it easy to write buggy code.\n\nSuppose, for example, that we wanted to find the index of all `<p>` tags within a string of HTML text. We might write code like this:\n```js\nlet match, positions = [];\nwhile((match = /<p>/g.exec(html)) !== null) { // POSSIBLE INFINITE LOOP\n    positions.push(match.index);\n}\n```\nThis code does not do what we want it to. If the html string contains at least one `<p>` tag, then it will loop forever. The problem is that we use a RegExp literal in the while loop condition. For each iteration of the loop, we’re creating a new RegExp object with lastIndex set to 0, so exec() always begins at the start of the string, and if there is a match, it will keep matching over and over. The solution, of course, is to define the RegExp once, and save it to a variable so that we’re using the same RegExp object for each iteration of the loop.\n\nOn the other hand, sometimes reusing a RegExp object is the wrong thing to do. Suppose, for example, that we want to loop through all of the words in a dictionary to find words that contain pairs of double letters:\n```js\nlet dictionary = [ \"apple\", \"book\", \"coffee\" ];\nlet doubleLetterWords = [];\nlet doubleLetter = /(\\w)\\1/g;\n\nfor(let word of dictionary) {\n    if (doubleLetter.test(word)) {\n        doubleLetterWords.push(word);\n    }\n}\ndoubleLetterWords  // => [\"apple\", \"coffee\"]: \"book\" is missing!\n```\nBecause we set the g flag on the RegExp, the lastIndex property is changed after successful matches, and the test() method (which is based on exec()) starts searching for a match at the position specified by lastIndex. After matching the “pp” in “apple”, lastIndex is 3, and so we start searching the word “book” at position 3 and do not see the “oo” that it contains.\n\nWe could fix this problem by removing the g flag (which is not actually necessary in this particular example), or by moving the RegExp literal into the body of the loop so that it is re-created on each iteration, or by explicitly resetting lastIndex to zero before each call to test().\n\nThe moral here is that lastIndex makes the RegExp API error prone. So be extra careful when using the g or y flags and looping. And in ES2020 and later, use the String matchAll() method instead of exec() to sidestep this problem since matchAll() does not modify lastIndex.\n\n## 11.4 Dates and Times\nThe Date class is JavaScript’s API for working with dates and times. Create a Date object with the Date() constructor. With no arguments, it returns a Date object that represents the current date and time:\n\n> Date 类是用于处理日期和时间的 JavaScript API。 使用 Date() 构造函数创建一个 Date 对象。没有实参，它将返回一个 Date 对象，该对象代表当前日期和时间：\n\n```js\nlet now = new Date();     // The current time\n```\nIf you pass one numeric argument, the Date() constructor interprets that argument as the number of milliseconds since the 1970 epoch:\n\n> 如果传递一个数字实参，则 Date() 构造函数将该实参解释为自 1970 年以来的毫秒数：\n\n```js\nlet epoch = new Date(0);  // Midnight, January 1st, 1970, GMT\n```\nIf you specify two or more integer arguments, they are interpreted as the year, month, day-of-month, hour, minute, second, and millisecond in your local time zone, as in the following:\n\n> 如果指定两个或多个整数实参，则它们将被解释为本地时区中的年、月、日、小时、分钟、秒和毫秒，如下所示：\n\n```js\nlet century = new Date(2100,         // Year 2100\n                       0,            // January\n                       1,            // 1st\n                       2, 3, 4, 5);  // 02:03:04.005, local time\n```\nOne quirk of the Date API is that the first month of a year is number 0, but the first day of a month is number 1. If you omit the time fields, the Date() constructor defaults them all to 0, setting the time to midnight.\n\n> Date API 的一个怪癖是一年的第一个月是数字 0，而一个月的第一天是数字 1。如果省略了时间字段，则 Date() 构造函数会将它们全部默认为 0，并将时间设置为到午夜。\n\nNote that when invoked with multiple numbers, the Date() constructor interprets them using whatever time zone the local computer is set to. If you want to specify a date and time in UTC (Universal Coordinated Time, aka GMT), then you can use the Date.UTC(). This static method takes the same arguments as the Date() constructor, interprets them in UTC, and returns a millisecond timestamp that you can pass to the Date() constructor:\n\n> 请注意，当使用多个数字调用时，Date() 构造函数使用本地计算机设置为的时区解释它们。如果要以UTC（协调世界时，又称为GMT）指定日期和时间，则可以使用 Date.UTC()。 此静态方法采用与 Date() 构造函数相同的实参，以 UTC 解释它们，并返回可以传递给 Date() 构造函数的毫秒级时间戳：\n\n```js\n// Midnight in England, January 1, 2100\nlet century = new Date(Date.UTC(2100, 0, 1));\n```\nIf you print a date (with `console.log(century)`, for example), it will, by default, be printed in your local time zone. If you want to display a date in UTC, you should explicitly convert it to a string with toUTCString() or toISOString().\n\n> 如果您打印日期（例如，使用 `console.log(century)`），则默认情况下将在本地时区打印日期。 如果要以 UTC 显示日期，则应使用 toUTCString() 或 toISOString() 将其显式转换为字符串。\n\nFinally, if you pass a string to the Date() constructor, it will attempt to parse that string as a date and time specification. The constructor can parse dates specified in the formats produced by the toString(), toUTCString(), and toISOString() methods:\n\n> 最后，如果将字符串传递给 Date() 构造函数，它将尝试解析该字符串作为日期和时间规范。构造函数可以解析以 toString()、toUTCString() 和 toISOString() 方法产生的格式指定的日期：\n\n```js\nlet century = new Date(\"2100-01-01T00:00:00Z\");  // An ISO format date\n```\nOnce you have a Date object, various get and set methods allow you to query and modify the year, month, day-of-month, hour, minute, second, and millisecond fields of the Date. Each of these methods has two forms: one that gets or sets using local time and one that gets or sets using UTC time. To get or set the year of a Date object, for example, you would use getFullYear(), getUTCFullYear(), setFullYear(), or setUTCFullYear():\n\n> 有了 Date 对象后，可以使用各种 get 和 set 方法查询和修改日期的年、月、日、小时、分钟、秒和毫秒字段。这些方法中的每一种都有两种形式：一种使用本地时间获取或设置，另一种使用 UTC 时间获取或设置。例如，要获取或设置 Date 对象的年份，可以使用 getFullYear()、getUTCFullYear()、setFullYear() 或 setUTCFullYear()：\n\n```js\nlet d = new Date();                  // Start with the current date\nd.setFullYear(d.getFullYear() + 1);  // Increment the year\n```\nTo get or set the other fields of a Date, replace “FullYear” in the method name with “Month”, “Date”, “Hours”, “Minutes”, “Seconds”, or “Milliseconds”. Some of the date set methods allow you to set more than one field at a time. setFullYear() and setUTCFullYear() also optionally allow you to set the month and day-of-month as well. And setHours() and setUTCHours() allow you to specify the minutes, seconds, and milliseconds fields in addition to the hours field.\n\n> 要获取或设置日期的其他字段，请将方法名称中的“FullYear”替换为“Month”、“Date”、“Hours”、“Minutes”、“Seconds”或“Milliseconds”。一些日期设置方法可以一次设置多个字段。 setFullYear() 和 setUTCFullYear() 还可以选择设置月和日。setHours() 和 setUTCHours() 允许您除了指定小时数字段之外，还指定分钟，秒和毫秒字段。\n\nNote that the methods for querying the day-of-month are getDate() and getUTCDate(). The more natural-sounding functions getDay() and getUTCDay() return the day-of-week (0 for Sunday through 6 for Saturday). The day-of-week is read-only, so there is not a corresponding setDay() method.\n\n> 请注意，查询日期的方法是 getDate() 和 getUTCDate()。听起函数 getDay() 和 getUTCDay() 更像查询日期，但是它们返回星期几（ 0 为星期日，星期六为 6）。星期几是只读的，因此没有相应的 setDay() 方法。 \n\n### 11.4.1 Timestamps\nJavaScript represents dates internally as integers that specify the number of milliseconds since (or before) midnight on January 1, 1970, UTC time. Integers as large as 8,640,000,000,000,000 are supported, so JavaScript won’t be running out of milliseconds for more than 270,000 years.\n\n> JavaScript 在内部将日期表示为整数，该整数指定自 UTC 时间 1970 年 1 月 1 日午夜以来（或之前）的毫秒数。 支持的最大整数为 8,640,000,000,000,000，因此 JavaScript 在超过 270,000 年的时间内不会用完毫秒。\n\nFor any Date object, the getTime() method returns this internal value, and the setTime() method sets it. So you can add 30 seconds to a Date with code like this, for example:\n\n> 对于任何 Date 对象，getTime() 方法将返回此内部值，而 setTime() 方法将对其进行设置。 因此，可以使用以下代码向日期添加 30 秒，例如：\n\n```js\nd.setTime(d.getTime() + 30000);\n```\nThese millisecond values are sometimes called timestamps, and it is sometimes useful to work with them directly rather than with Date objects. The static Date.now() method returns the current time as a timestamp and is helpful when you want to measure how long your code takes to run:\n\n> 这些毫秒值有时被称为时间戳，有时直接使用它们而不是使用日期对象有时很有用。静态的 Date.now() 方法返回当前时间作为时间戳，当要测量代码运行多长时间时，该方法很有用：\n\n```js\nlet startTime = Date.now();\nreticulateSplines(); // Do some time-consuming operation\nlet endTime = Date.now();\nconsole.log(`Spline reticulation took ${endTime - startTime}ms.`);\n```\n#### HIGH-RESOLUTION TIMESTAMPS\nThe timestamps returned by Date.now() are measured in milliseconds. A millisecond is actually a relatively long time for a computer, and sometimes you may want to measure elapsed time with higher precision. The performance.now() function allows this: it also returns a millisecond-based timestamp, but the return value is not an integer, so it includes fractions of a millisecond. The value returned by performance.now() is not an absolute timestamp like the Date.now() value is. Instead, it simply indicates how much time has elapsed since a web page was loaded or since the Node process started.\n\n> Date.now() 返回的时间戳以毫秒为单位。对于计算机而言，毫秒实际上是一个相对较长的时间，有时您可能希望以更高的精度测量经过的时间。performance.now() 函数允许这样做：它也返回毫秒级的时间戳，但是返回值不是整数，因此它包含毫秒的分数。 performance.now() 返回的值不是像 Date.now() 那样的绝对时间戳。取而代之的是，它仅指示网页加载成功或 Node 进程开始以来已花费了多少时间。\n\nThe performance object is part of a larger Performance API that is not defined by the ECMAScript standard but is implemented by web browsers and by Node. In order to use the performance object in Node, you must import it with:\n\n> performance 对象是较大的 Performance API 的一部分，该 API 不是由 ECMAScript 标准定义的，而是由 Web 浏览器和 Node 实现的。为了在 Node 中使用 performance 对象，必须使用以下命令导入它：\n\n```js\nconst { performance } = require(\"perf_hooks\");\n```\nAllowing high-precision timing on the web may allow unscrupulous websites to fingerprint visitors, so browsers (notably Firefox) may reduce the precision of performance.now() by default. As a web developer, you should be able to re-enable high-precision timing somehow (such as by setting privacy.reduceTimerPrecision to false in Firefox).\n\n> 允许在 Web 上进行高精度计时可能会导致不道德的网站对访问者进行指纹识别，因此默认情况下，浏览器（尤其是Firefox）可能会降低 performance.now() 的精度。作为 Web 开发人员，您应该能够以某种方式重新启用高精度计时（例如，通过在 Firefox 中将 privacy.reduceTimerPrecision 设置为 false）。\n\n### 11.4.2 Date Arithmetic\nDate objects can be compared with JavaScript’s standard <, <=, >, and >= comparison operators. And you can subtract one Date object from another to determine the number of milliseconds between the two dates. (This works because the Date class defines a valueOf() method that returns a timestamp.)\n\n> 日期对象可以用 JavaScript 的标准 <、<=、> 和 >= 比较运算符进行比较。可以从另一个日期对象中减去一个日期对象，以确定两个日期之间的毫秒数。（这是有效的，因为 Date 类定义了一个返回时间戳的 valueOf() 方法。）\n\nIf you want to add or subtract a specified number of seconds, minutes, or hours from a Date, it is often easiest to simply modify the timestamp as demonstrated in the previous example, when we added 30 seconds to a date. This technique becomes more cumbersome if you want to add days, and it does not work at all for months and years since they have varying numbers of days. To do date arithmetic involving days, months, and years, you can use setDate(), setMonth(), and setYear(). Here, for example, is code that adds three months and two weeks to the current date:\n\n> 如果您想从日期中添加或减去指定的秒、分钟数或小时数，通常最简单的方法就是修改时间戳，如上一个示例所示，即为日期添加 30 秒。如果要添加天数，此技术将变得更加繁琐，并且由于它们的天数不同，因此几个月甚至几年都无法使用。要进行涉及天，月和年的日期算术，可以使用 setDate()、setMonth() 和 setYear()。例如，下面的代码向当前日期添加了三个月零两个星期：\n\n```js\nlet d = new Date();\nd.setMonth(d.getMonth() + 3, d.getDate() + 14);\n```\nDate setting methods work correctly even when they overflow. When we add three months to the current month, we can end up with a value greater than 11 (which represents December). The setMonth() handles this by incrementing the year as needed. Similarly, when we set the day of the month to a value larger than the number of days in the month, the month gets incremented appropriately.\n\n> 日期设置方法即使在溢出时也可以正常工作。当我们在当月增加三个月时，最终可以得到大于 11 的值（代表 12 月）。setMonth() 通过根据需要增加年份来处理此问题。同样，当我们将月份中的日期设置为大于月份中天数的值时，月份会适当增加。\n\n### 11.4.3 Formatting and Parsing Date Strings\nIf you are using the Date class to actually keep track of dates and times (as opposed to just measuring time intervals), then you are likely to need to display dates and times to the users of your code. The Date class defines a number of different methods for converting Date objects to strings. Here are some examples:\n\n> 如果使用 Date 类实际跟踪日期和时间（而不是仅测量时间间隔），则可能需要向代码用户显示日期和时间。 Date 类定义了许多用于将 Date 对象转换为字符串的不同方法。这里有些例子：\n\n```js\nlet d = new Date(2020, 0, 1, 17, 10, 30); // 5:10:30pm on New Year's Day 2020\nd.toString()  // => \"Wed Jan 01 2020 17:10:30 GMT-0800 (Pacific Standard Time)\"\nd.toUTCString()         // => \"Thu, 02 Jan 2020 01:10:30 GMT\"\nd.toLocaleDateString()  // => \"1/1/2020\": 'en-US' locale\nd.toLocaleTimeString()  // => \"5:10:30 PM\": 'en-US' locale\nd.toISOString()         // => \"2020-01-02T01:10:30.000Z\"\n```\nThis is a full list of the string formatting methods of the Date class:\n\n> 这是 Date 类的字符串格式化方法的完整列表：\n\ntoString()\nThis method uses the local time zone but does not format the date and time in a locale-aware way.\n\n> 此方法使用本地时区，但不以语言环境的方式格式化日期和时间。\n\ntoUTCString()\nThis method uses the UTC time zone but does not format the date in a locale-aware way.\n\n> 此方法使用 UTC 时区，但不以语言环境的方式格式化日期和时间。\n\ntoISOString()\nThis method prints the date and time in the standard year-month-day hours:minutes:seconds.ms format of the ISO-8601 standard. The letter “T” separates the date portion of the output from the time portion of the output. The time is expressed in UTC, and this is indicated with the letter “Z” as the last letter of the output.\n\n> 此方法以 ISO-8601 标准的标准 year-month-day hours:minutes:seconds.ms 格式打印日期和时间。 字母“T”将输出的日期部分与输出的时间部分分开。时间以 UTC 表示，并以字母“Z”表示为输出的最后一个字母。\n\ntoLocaleString()\nThis method uses the local time zone and a format that is appropriate for the user’s locale.\n\n> 此方法使用本地时区和适合用户所在区域的格式。\n\ntoDateString()\nThis method formats only the date portion of the Date and omits the time. It uses the local time zone and does not do locale-appropriate formatting.\n\n> 此方法仅格式化日期的日期部分，并省略时间。它使用本地时区，并且不进行适合区域设置的格式。\n\ntoLocaleDateString()\nThis method formats only the date. It uses the local time zone and a locale-appropriate date format.\n\n> 此方法仅格式化日期。它使用本地时区和适合本地的日期格式。\n\ntoTimeString()\nThis method formats only the time and omits the date. It uses the local time zone but does not format the time in a locale-aware way.\n\n> 此方法仅格式化时间，而省略日期。它使用本地时区，但不以语言环境的方式格式化时间。\n\ntoLocaleTimeString()\nThis method formats the time in a locale-aware way and uses the local time zone.\n\n> 此方法以语言环境的方式格式化时间，并使用本地时区。\n\nNone of these date-to-string methods is ideal when formatting dates and times to be displayed to end users. See §11.7.2 for a more general-purpose and locale-aware date- and time-formatting technique.\n\n> 在格式化要显示给最终用户的日期和时间时，这些日期到字符串方法都不是理想的选择。有关更通用的和可识别区域设置的日期和时间格式技术，请参见 §11.7.2。\n\nFinally, in addition to these methods that convert a Date object to a string, there is also a static Date.parse() method that takes a string as its argument, attempts to parse it as a date and time, and returns a timestamp representing that date. Date.parse() is able to parse the same strings that the Date() constructor can and is guaranteed to be able to parse the output of toISOString(), toUTCString(), and toString().\n\n> 最后，除了将 Date 对象转换为字符串的这些方法之外，还有一个静态 Date.parse() 方法，该方法将字符串作为实参，尝试将其解析为日期和时间，并返回时间戳描述日期。Date.parse() 能够解析与 Date() 构造函数相同的字符串，并保证能够解析 toISOString()、toUTCString() 和 toString() 的输出。\n\n## 11.5 Error Classes\nThe JavaScript throw and catch statements can throw and catch any JavaScript value, including primitive values. There is no exception type that must be used to signal errors. JavaScript does define an Error class, however, and it is traditional to use instances of Error or a subclass when signaling an error with throw. One good reason to use an Error object is that, when you create an Error, it captures the state of the JavaScript stack, and if the exception is uncaught, the stack trace will be displayed with the error message, which will help you debug the issue. (Note that the stack trace shows where the Error object was created, not where the throw statement throws it. If you always create the object right before throwing it with throw new Error(), this will not cause any confusion.)\n\n> JavaScript 的 throw 和 catch 语句可以抛出并捕获任何 JavaScript 值，包括原始值。没有必须用于发出错误的异常类型。JavaScript 确实定义了一个 Error 类，但是传统上是使用 throw 发出错误信号时使用 Error 的实例或子类。使用 Error 对象的一个​​很好的理由是，当创建一个 Error 时，它会捕获 JavaScript 堆栈的状态，并且如果未捕获到异常，则堆栈跟踪将与错误消息一起显示，这将有助于调试问题。（请注意，堆栈跟踪显示的是 Error 对象的创建位置，而不是 throw 语句将其抛出的位置。如果总是在使用 throw new Error() 对其进行抛出之前立即创建该对象，则不会造成任何混乱。）\n\nError objects have two properties: message and name, and a toString() method. The value of the message property is the value you passed to the Error() constructor, converted to a string if necessary. For error objects created with Error(), the name property is always “Error”. The toString() method simply returns the value of the name property followed by a colon and space and the value of the message property.\n\n> 错误对象具有两个属性：message 和 name，以及一个 toString() 方法。 message 属性的值是传递给 Error() 构造函数的值，并在必要时转换为字符串。对于使用 Error() 创建的错误对象，name 属性始终为“Error”。toString() 方法仅返回 name 属性的值后跟冒号和空格以及 message 属性的值。\n\nAlthough it is not part of the ECMAScript standard, Node and all modern browsers also define a stack property on Error objects. The value of this property is a multi-line string that contains a stack trace of the JavaScript call stack at the moment that the Error object was created. This can be useful information to log when an unexpected error is caught.\n\n> 尽管它不是 ECMAScript 标准的一部分，但 Node 和所有现代浏览器还在 Error 对象上定义了 stack 属性。此属性的值是多行字符串，其中包含创建 Error 对象时的 JavaScript 调用堆栈的堆栈跟踪。当捕获到意外错误时，这对于记录日志很有用。\n\nIn addition to the Error class, JavaScript defines a number of subclasses that it uses to signal particular types of errors defined by ECMAScript. These subclasses are EvalError, RangeError, ReferenceError, SyntaxError, TypeError, and URIError. You can use these error classes in your own code if they seem appropriate. Like the base Error class, each of these subclasses has a constructor that takes a single message argument. And instances of each of these subclasses have a name property whose value is the same as the constructor name.\n\n> 除了 Error 类之外，JavaScript 还定义了许多子类，这些子类用于表示 ECMAScript 定义的特定类型的错误。这些子类是 EvalError、RangeError、ReferenceError、SyntaxError、TypeError 和 URIError。如果合适可以在自己的代码中使用这些错误类。像基本错误类一样，这些子类每一个都有一个采用单个 message 实参的构造函数。每个这些子类的实例都有一个 name 属性，其值与构造函数名称相同。\n\nYou should feel free to define your own Error subclasses that best encapsulate the error conditions of your own program. Note that you are not limited to the name and message properties. If you create a subclass, you can define new properties to provide error details. If you are writing a parser, for example, you might find it useful to define a ParseError class with line and column properties that specify the exact location of the parsing failure. Or if you are working with HTTP requests, you might want to define an HTTPError class that has a status property that holds the HTTP status code (such as 404 or 500) of the failed request.\n\n> 应该随意定义自己的错误子类，以最好地封装自己程序的错误条件。请注意，可以不受限于名称和消息两个属性。如果创建子类，则可以定义新属性以提供错误详细信息。例如，如果正在编写一个转换，可能会发现定义一个 ParseError 类非常有用，该类具有指定解析失败确切位置的行和列属性。或者，如果正在处理 HTTP 请求，则可能需要定义一个 HTTPError 类，该类具有一个 status 属性，该属性保存失败请求的 HTTP 状态代码（例如 404 或 500）。\n\nFor example:\n```js\nclass HTTPError extends Error {\n    constructor(status, statusText, url) {\n        super(`${status} ${statusText}: ${url}`);\n        this.status = status;\n        this.statusText = statusText;\n        this.url = url;\n    }\n\n    get name() { return \"HTTPError\"; }\n}\n\nlet error = new HTTPError(404, \"Not Found\", \"http://example.com/\");\nerror.status        // => 404\nerror.message       // => \"404 Not Found: http://example.com/\"\nerror.name          // => \"HTTPError\"\n```\n## 11.6 JSON Serialization and Parsing\nWhen a program needs to save data or needs to transmit data across a network connection to another program, it must to convert its in-memory data structures into a string of bytes or characters than can be saved or transmitted and then later be parsed to restore the original in-memory data structures. This process of converting data structures into streams of bytes or characters is known as serialization (or marshaling or even pickling).\n\nThe easiest way to serialize data in JavaScript uses a serialization format known as JSON. This acronym stands for “JavaScript Object Notation” and, as the name implies, the format uses JavaScript object and array literal syntax to convert data structures consisting of objects and arrays into strings. JSON supports primitive numbers and strings and also the values true, false, and null, as well as arrays and objects built up from those primitive values. JSON does not support other JavaScript types like Map, Set, RegExp, Date, or typed arrays. Nevertheless, it has proved to be a remarkably versatile data format and is in common use even with non-JavaScript-based programs.\n\nJavaScript supports JSON serialization and deserialization with the two functions JSON.stringify() and JSON.parse(), which were covered briefly in §6.8. Given an object or array (nested arbitrarily deeply) that does not contain any nonserializable values like RegExp objects or typed arrays, you can serialize the object simply by passing it to JSON.stringify(). As the name implies, the return value of this function is a string. And given a string returned by JSON.stringify(), you can re-create the original data structure by passing the string to JSON.parse():\n```js\nlet o = {s: \"\", n: 0, a: [true, false, null]};\nlet s = JSON.stringify(o);  // s == '{\"s\":\"\",\"n\":0,\"a\":[true,false,null]}'\nlet copy = JSON.parse(s);   // copy == {s: \"\", n: 0, a: [true, false, null]}\n```\nIf we leave out the part where serialized data is saved to a file or sent over the network, we can use this pair of functions as a somewhat inefficient way of creating a deep copy of an object:\n```js\n// Make a deep copy of any serializable object or array\nfunction deepcopy(o) {\n    return JSON.parse(JSON.stringify(o));\n}\n```\nJSON IS A SUBSET OF JAVASCRIPT\nWhen data is serialized to JSON format, the result is valid JavaScript source code for an expression that evaluates to a copy of the original data structure. If you prefix a JSON string with var data = and pass the result to eval(), you’ll get a copy of the original data structure assigned to the variable data. You should never do this, however, because it is a huge security hole—if an attacker could inject arbitrary JavaScript code into a JSON file, they could make your program run their code. It is faster and safer to just use JSON.parse() to decode JSON-formatted data.\n\nJSON is sometimes used as a human-readable configuration file format. If you find yourself hand-editing a JSON file, note that the JSON format is a very strict subset of JavaScript. Comments are not allowed and property names must be enclosed in double quotes even when JavaScript would not require this.\n\nTypically, you pass only a single argument to JSON.stringify() and JSON.parse(). Both functions accept an optional second argument that allows us to extend the JSON format, and these are described next. JSON.stringify() also takes an optional third argument that we’ll discuss first. If you would like your JSON-formatted string to be human-readable (if it is being used as a configuration file, for example), then you should pass null as the second argument and pass a number or string as the third argument. This third argument tells JSON.stringify() that it should format the data on multiple indented lines. If the third argument is a number, then it will use that number of spaces for each indentation level. If the third argument is a string of whitespace (such as '\\t'), it will use that string for each level of indent.\n```js\nlet o = {s: \"test\", n: 0};\nJSON.stringify(o, null, 2)  // => '{\\n  \"s\": \"test\",\\n  \"n\": 0\\n}'\n```\nJSON.parse() ignores whitespace, so passing a third argument to JSON.stringify() has no impact on our ability to convert the string back into a data structure.\n\n### 11.6.1 JSON Customizations\nIf JSON.stringify() is asked to serialize a value that is not natively supported by the JSON format, it looks to see if that value has a toJSON() method, and if so, it calls that method and then stringifies the return value in place of the original value. Date objects implement toJSON(): it returns the same string that toISOString() method does. This means that if you serialize an object that includes a Date, the date will automatically be converted to a string for you. When you parse the serialized string, the re-created data structure will not be exactly the same as the one you started with because it will have a string where the original object had a Date.\n\nIf you need to re-create Date objects (or modify the parsed object in any other way), you can pass a “reviver” function as the second argument to JSON.parse(). If specified, this “reviver” function is invoked once for each primitive value (but not the objects or arrays that contain those primitive values) parsed from the input string. The function is invoked with two arguments. The first is a property name—either an object property name or an array index converted to a string. The second argument is the primitive value of that object property or array element. Furthermore, the function is invoked as a method of the object or array that contains the primitive value, so you can refer to that containing object with the this keyword.\n\nThe return value of the reviver function becomes the new value of the named property. If it returns its second argument, the property will remain unchanged. If it returns undefined, then the named property will be deleted from the object or array before JSON.parse() returns to the user.\n\nAs an example, here is a call to JSON.parse() that uses a reviver function to filter some properties and to re-create Date objects:\n```js\nlet data = JSON.parse(text, function(key, value) {\n    // Remove any values whose property name begins with an underscore\n    if (key[0] === \"_\") return undefined;\n\n    // If the value is a string in ISO 8601 date format convert it to a Date.\n    if (typeof value === \"string\" &&\n        /^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d.\\d\\d\\dZ$/.test(value)) {\n        return new Date(value);\n    }\n\n    // Otherwise, return the value unchanged\n    return value;\n});\n```\nIn addition to its use of toJSON() described earlier, JSON.stringify() also allows its output to be customized by passing an array or a function as the optional second argument.\n\nIf an array of strings (or numbers—they are converted to strings) is passed instead as the second argument, these are used as the names of object properties (or array elements). Any property whose name is not in the array will be omitted from stringification. Furthermore, the returned string will include properties in the same order that they appear in the array (which can be very useful when writing tests).\n\nIf you pass a function, it is a replacer function—effectively the inverse of the optional reviver function you can pass to JSON.parse(). If specified, the replacer function is invoked for each value to be stringified. The first argument to the replacer function is the object property name or array index of the value within that object, and the second argument is the value itself. The replacer function is invoked as a method of the object or array that contains the value to be stringified. The return value of the replacer function is stringified in place of the original value. If the replacer returns undefined or returns nothing at all, then that value (and its array element or object property) is omitted from the stringification.\n```js\n// Specify what fields to serialize, and what order to serialize them in\nlet text = JSON.stringify(address, [\"city\",\"state\",\"country\"]);\n\n// Specify a replacer function that omits RegExp-value properties\nlet json = JSON.stringify(o, (k, v) => v instanceof RegExp ? undefined : v);\n```\nThe two JSON.stringify() calls here use the second argument in a benign way, producing serialized output that can be deserialized without requiring a special reviver function. In general, though, if you define a toJSON() method for a type, or if you use a replacer function that actually replaces nonserializable values with serializable ones, then you will typically need to use a custom reviver function with JSON.parse() to get your original data structure back. If you do this, you should understand that you are defining a custom data format and sacrificing portability and compatibility with a large ecosystem of JSON-compatible tools and languages.\n\n## 11.7 The Internationalization API\nThe JavaScript internationalization API consists of the three classes Intl.NumberFormat, Intl.DateTimeFormat, and Intl.Collator that allow us to format numbers (including monetary amounts and percentages), dates, and times in locale-appropriate ways and to compare strings in locale-appropriate ways. These classes are not part of the ECMAScript standard but are defined as part of the ECMA402 standard and are well-supported by web browsers. The Intl API is also supported in Node, but at the time of this writing, prebuilt Node binaries do not ship with the localization data required to make them work with locales other than US English. So in order to use these classes with Node, you may need to download a separate data package or use a custom build of Node.\n\nOne of the most important parts of internationalization is displaying text that has been translated into the user’s language. There are various ways to achieve this, but none of them are within the scope of the Intl API described here.\n\n### 11.7.1 Formatting Numbers\nUsers around the world expect numbers to be formatted in different ways. Decimal points can be periods or commas. Thousands separators can be commas or periods, and they aren’t used every three digits in all places. Some currencies are divided into hundredths, some into thousandths, and some have no subdivisions. Finally, although the so-called “Arabic numerals” 0 through 9 are used in many languages, this is not universal, and users in some countries will expect to see numbers written using the digits from their own scripts.\n\nThe Intl.NumberFormat class defines a format() method that takes all of these formatting possibilities into account. The constructor takes two arguments. The first argument specifies the locale that the number should be formatted for and the second is an object that specifies more details about how the number should be formatted. If the first argument is omitted or undefined, then the system locale (which we assume to be the user’s preferred locale) will be used. If the first argument is a string, it specifies a desired locale, such as \"en-US\" (English as used in the United States), \"fr\" (French), or \"zh-Hans-CN\" (Chinese, using the simplified Han writing system, in China). The first argument can also be an array of locale strings, and in this case, Intl.NumberFormat will choose the most specific one that is well supported.\n\nThe second argument to the Intl.NumberFormat() constructor, if specified, should be an object that defines one or more of the following properties:\n\nstyle\nSpecifies the kind of number formatting that is required. The default is \"decimal\". Specify \"percent\" to format a number as a percentage or specify \"currency\" to specify a number as an amount of money.\n\ncurrency\nIf style is \"currency\", then this property is required to specify the three-letter ISO currency code (such as \"USD\" for US dollars or \"GBP\" for British pounds) of the desired currency.\n\ncurrencyDisplay\nIf style is \"currency\", then this property specifies how the currency is displayed. The default value \"symbol\" uses a currency symbol if the currency has one. The value \"code\" uses the three-letter ISO code, and the value \"name\" spells out the name of the currency in long form.\n\nuseGrouping\nSet this property to false if you do not want numbers to have thousands separators (or their locale-appropriate equivalents).\n\nminimumIntegerDigits\nThe minimum number of digits to use to display the integer part of the number. If the number has fewer digits than this, it will be padded on the left with zeros. The default value is 1, but you can use values as high as 21.\n\nminimumFractionDigits, maximumFractionDigits\nThese two properties control the formatting of the fractional part of the number. If a number has fewer fractional digits than the minimum, it will be padded with zeros on the right. If it has more than the maximum, then the fractional part will be rounded. Legal values for both properties are between 0 and 20. The default minimum is 0 and the default maximum is 3, except when formatting monetary amounts, when the length of the fractional part varies depending on the specified currency.\n\nminimumSignificantDigits, maximumSignificantDigits\nThese properties control the number of significant digits used when formatting a number, making them suitable when formatting scientific data, for example. If specified, these properties override the integer and fractional digit properties listed previously. Legal values are between 1 and 21.\n\nOnce you have created an Intl.NumberFormat object with the desired locale and options, you use it by passing a number to its format() method, which returns an appropriately formatted string. For example:\n```js\nlet euros = Intl.NumberFormat(\"es\", {style: \"currency\", currency: \"EUR\"});\neuros.format(10)    // => \"10,00 €\": ten euros, Spanish formatting\n\nlet pounds = Intl.NumberFormat(\"en\", {style: \"currency\", currency: \"GBP\"});\npounds.format(1000) // => \"£1,000.00\": One thousand pounds, English formatting\n```\nA useful feature of Intl.NumberFormat (and the other Intl classes as well) is that its format() method is bound to the NumberFormat object to which it belongs. So instead of defining a variable that refers to the formatting object and then invoking the format() method on that, you can just assign the format() method to a variable and use it as if it were a standalone function, as in this example:\n```js\nlet data = [0.05, .75, 1];\nlet formatData = Intl.NumberFormat(undefined, {\n    style: \"percent\",\n    minimumFractionDigits: 1,\n    maximumFractionDigits: 1\n}).format;\n\ndata.map(formatData)   // => [\"5.0%\", \"75.0%\", \"100.0%\"]: in en-US locale\n```\nSome languages, such as Arabic, use their own script for decimal digits:\n```js\nlet arabic = Intl.NumberFormat(\"ar\", {useGrouping: false}).format;\narabic(1234567890)   // => \"١٢٣٤٥٦٧٨٩٠\"\n```\nOther languages, such as Hindi, use a script that has its own set of digits, but tend to use the ASCII digits 0–9 by default. If you want to override the default script used for digits, add -u-nu- to the locale and follow it with an abbreviated script name. You can format numbers with Indian-style grouping and Devanagari digits like this, for example:\n```js\nlet hindi = Intl.NumberFormat(\"hi-IN-u-nu-deva\").format;\nhindi(1234567890)    // => \"१,२३,४५,६७,८९०\"\n```\n-u- in a locale specifies that what comes next is a Unicode extension. nu is the extension name for the numbering system, and deva is short for Devanagari. The Intl API standard defines names for a number of other numbering systems, mostly for the Indic languages of South and Southeast Asia.\n\n### 11.7.2 Formatting Dates and Times\nThe Intl.DateTimeFormat class is a lot like the Intl.NumberFormat class. The Intl.DateTimeFormat() constructor takes the same two arguments that Intl.NumberFormat() does: a locale or array of locales and an object of formatting options. And the way you use an Intl.DateTimeFormat instance is by calling its format() method to convert a Date object to a string.\n\nAs mentioned in §11.4, the Date class defines simple toLocaleDateString() and toLocaleTimeString() methods that produce locale-appropriate output for the user’s locale. But these methods don’t give you any control over what fields of the date and time are displayed. Maybe you want to omit the year but add a weekday to the date format. Do you want the month to be represented numerically or spelled out by name? The Intl.DateTimeFormat class provides fine-grained control over what is output based on the properties in the options object that is passed as the second argument to the constructor. Note, however, that Intl.DateTimeFormat cannot always display exactly what you ask for. If you specify options to format hours and seconds but omit minutes, you’ll find that the formatter displays the minutes anyway. The idea is that you use the options object to specify what date and time fields you’d like to present to the user and how you’d like those formatted (by name or by number, for example), then the formatter will look for a locale-appropriate format that most closely matches what you have asked for.\n\nThe available options are the following. Only specify properties for date and time fields that you would like to appear in the formatted output.\n\nyear\nUse \"numeric\" for a full, four-digit year or \"2-digit\" for a two-digit abbreviation.\n\nmonth\nUse \"numeric\" for a possibly short number like “1”, or \"2-digit\" for a numeric representation that always has two digits, like “01”. Use \"long\" for a full name like “January”, \"short\" for an abbreviated name like “Jan”, and \"narrow\" for a highly abbreviated name like “J” that is not guaranteed to be unique.\n\nday\nUse \"numeric\" for a one- or two-digit number or \"2-digit\" for a two-digit number for the day-of-month.\n\nweekday\nUse \"long\" for a full name like “Monday”, \"short\" for an abbreviated name like “Mon”, and \"narrow\" for a highly abbreviated name like “M” that is not guaranteed to be unique.\n\nera\nThis property specifies whether a date should be formatted with an era, such as CE or BCE. This may be useful if you are formatting dates from very long ago or if you are using a Japanese calendar. Legal values are \"long\", \"short\", and \"narrow\".\n\nhour, minute, second\nThese properties specify how you would like time displayed. Use \"numeric\" for a one- or two-digit field or \"2-digit\" to force single-digit numbers to be padded on the left with a 0.\n\ntimeZone\nThis property specifies the desired time zone for which the date should be formatted. If omitted, the local time zone is used. Implementations always recognize “UTC” and may also recognize Internet Assigned Numbers Authority (IANA) time zone names, such as “America/Los_Angeles”.\n\ntimeZoneName\nThis property specifies how the time zone should be displayed in a formatted date or time. Use \"long\" for a fully spelled-out time zone name and \"short\" for an abbreviated or numeric time zone.\n\nhour12\nThis boolean property specifies whether or not to use 12-hour time. The default is locale dependent, but you can override it with this property.\n\nhourCycle\nThis property allows you to specify whether midnight is written as 0 hours, 12 hours, or 24 hours. The default is locale dependent, but you can override the default with this property. Note that hour12 takes precedence over this property. Use the value \"h11\" to specify that midnight is 0 and the hour before midnight is 11pm. Use \"h12\" to specify that midnight is 12. Use \"h23\" to specify that midnight is 0 and the hour before midnight is 23. And use \"h24\" to specify that midnight is 24.\n\nHere are some examples:\n```js\nlet d = new Date(\"2020-01-02T13:14:15Z\");  // January 2nd, 2020, 13:14:15 UTC\n\n// With no options, we get a basic numeric date format\nIntl.DateTimeFormat(\"en-US\").format(d) // => \"1/2/2020\"\nIntl.DateTimeFormat(\"fr-FR\").format(d) // => \"02/01/2020\"\n\n// Spelled out weekday and month\nlet opts = { weekday: \"long\", month: \"long\", year: \"numeric\", day: \"numeric\" };\nIntl.DateTimeFormat(\"en-US\", opts).format(d) // => \"Thursday, January 2, 2020\"\nIntl.DateTimeFormat(\"es-ES\", opts).format(d) // => \"jueves, 2 de enero de 2020\"\n\n// The time in New York, for a French-speaking Canadian\nopts = { hour: \"numeric\", minute: \"2-digit\", timeZone: \"America/New_York\" };\nIntl.DateTimeFormat(\"fr-CA\", opts).format(d) // => \"8 h 14\"\n```\nIntl.DateTimeFormat can display dates using calendars other than the default Julian calendar based on the Christian era. Although some locales may use a non-Christian calendar by default, you can always explicitly specify the calendar to use by adding -u-ca- to the locale and following that with the name of the calendar. Possible calendar names include “buddhist”, “chinese”, “coptic”, “ethiopic”, “gregory”, “hebrew”, “indian”, “islamic”, “iso8601”, “japanese”, and “persian”. Continuing the preceding example, we can determine the year in various non-Christian calendars:\n```js\nlet opts = { year: \"numeric\", era: \"short\" };\nIntl.DateTimeFormat(\"en\", opts).format(d)                // => \"2020 AD\"\nIntl.DateTimeFormat(\"en-u-ca-iso8601\", opts).format(d)   // => \"2020 AD\"\nIntl.DateTimeFormat(\"en-u-ca-hebrew\", opts).format(d)    // => \"5780 AM\"\nIntl.DateTimeFormat(\"en-u-ca-buddhist\", opts).format(d)  // => \"2563 BE\"\nIntl.DateTimeFormat(\"en-u-ca-islamic\", opts).format(d)   // => \"1441 AH\"\nIntl.DateTimeFormat(\"en-u-ca-persian\", opts).format(d)   // => \"1398 AP\"\nIntl.DateTimeFormat(\"en-u-ca-indian\", opts).format(d)    // => \"1941 Saka\"\nIntl.DateTimeFormat(\"en-u-ca-chinese\", opts).format(d)   // => \"36 78\"\nIntl.DateTimeFormat(\"en-u-ca-japanese\", opts).format(d)  // => \"2 Reiwa\"\n```\n### 11.7.3 Comparing Strings\nThe problem of sorting strings into alphabetical order (or some more general “collation order” for nonalphabetical scripts) is more challenging than English speakers often realize. English uses a relatively small alphabet with no accented letters, and we have the benefit of a character encoding (ASCII, since incorporated into Unicode) whose numerical values perfectly match our standard string sort order. Things are not so simple in other languages. Spanish, for example treats ñ as a distinct letter that comes after n and before o. Lithuanian alphabetizes Y before J, and Welsh treats digraphs like CH and DD as single letters with CH coming after C and DD sorting after D.\n\nIf you want to display strings to a user in an order that they will find natural, it is not enough use the sort() method on an array of strings. But if you create an Intl.Collator object, you can pass the compare() method of that object to the sort() method to perform locale-appropriate sorting of the strings. Intl.Collator objects can be configured so that the compare() method performs case-insensitive comparisons or even comparisons that only consider the base letter and ignore accents and other diacritics.\n\nLike Intl.NumberFormat() and Intl.DateTimeFormat(), the Intl.Collator() constructor takes two arguments. The first specifies a locale or an array of locales, and the second is an optional object whose properties specify exactly what kind of string comparison is to be done. The supported properties are these:\n\nusage\nThis property specifies how the collator object is to be used. The default value is \"sort\", but you can also specify \"search\". The idea is that, when sorting strings, you typically want a collator that differentiates as many strings as possible to produce a reliable ordering. But when comparing two strings, some locales may want a less strict comparison that ignores accents, for example.\n\nsensitivity\nThis property specifies whether the collator is sensitive to letter case and accents when comparing strings. The value \"base\" causes comparisons that ignore case and accents, considering only the base letter for each character. (Note, however, that some languages consider certain accented characters to be distinct base letters.) \"accent\" considers accents in comparisons but ignores case. \"case\" considers case and ignores accents. And \"variant\" performs strict comparisons that consider both case and accents. The default value for this property is \"variant\" when usage is \"sort\". If usage is \"search\", then the default sensitivity depends on the locale.\n\nignorePunctuation\nSet this property to true to ignore spaces and punctuation when comparing strings. With this property set to true, the strings “any one” and “anyone”, for example, will be considered equal.\n\nnumeric\nSet this property to true if the strings you are comparing are integers or contain integers and you want them to be sorted into numerical order instead of alphabetical order. With this option set, the string “Version 9” will be sorted before “Version 10”, for example.\n\ncaseFirst\nThis property specifies which letter case should come first. If you specify \"upper\", then “A” will sort before “a”. And if you specify \"lower\", then “a” will sort before “A”. In either case, note that the upper- and lowercase variants of the same letter will be next to one another in sort order, which is different than Unicode lexicographic ordering (the default behavior of the Array sort() method) in which all ASCII uppercase letters come before all ASCII lowercase letters. The default for this property is locale dependent, and implementations may ignore this property and not allow you to override the case sort order.\n\nOnce you have created an Intl.Collator object for the desired locale and options, you can use its compare() method to compare two strings. This method returns a number. If the returned value is less than zero, then the first string comes before the second string. If it is greater than zero, then the first string comes after the second string. And if compare() returns zero, then the two strings are equal as far as this collator is concerned.\n\nThis compare() method that takes two strings and returns a number less than, equal to, or greater than zero is exactly what the Array sort() method expects for its optional argument. Also, Intl.Collator automatically binds the compare() method to its instance, so you can pass it directly to sort() without having to write a wrapper function and invoke it through the collator object. Here are some examples:\n```js\n// A basic comparator for sorting in the user's locale.\n// Never sort human-readable strings without passing something like this:\nconst collator = new Intl.Collator().compare;\n[\"a\", \"z\", \"A\", \"Z\"].sort(collator)      // => [\"a\", \"A\", \"z\", \"Z\"]\n\n// Filenames often include numbers, so we should sort those specially\nconst filenameOrder = new Intl.Collator(undefined, { numeric: true }).compare;\n[\"page10\", \"page9\"].sort(filenameOrder)  // => [\"page9\", \"page10\"]\n\n// Find all strings that loosely match a target string\nconst fuzzyMatcher = new Intl.Collator(undefined, {\n    sensitivity: \"base\",\n    ignorePunctuation: true\n}).compare;\nlet strings = [\"food\", \"fool\", \"Føø Bar\"];\nstrings.findIndex(s => fuzzyMatcher(s, \"foobar\") === 0)  // => 2\n```\nSome locales have more than one possible collation order. In Germany, for example, phone books use a slightly more phonetic sort order than dictionaries do. In Spain, before 1994, “ch” and “ll” were treated as separate letters, so that country now has a modern sort order and a traditional sort order. And in China, collation order can be based on character encodings, the base radical and strokes of each character, or on the Pinyin romanization of characters. These collation variants cannot be selected through the Intl.Collator options argument, but they can be selected by adding -u-co- to the locale string and adding the name of the desired variant. Use \"de-DE-u-co-phonebk\" for phone book ordering in Germany, for example, and \"zh-TW-u-co-pinyin\" for Pinyin ordering in Taiwan.\n```js\n// Before 1994, CH and LL were treated as separate letters in Spain\nconst modernSpanish = Intl.Collator(\"es-ES\").compare;\nconst traditionalSpanish = Intl.Collator(\"es-ES-u-co-trad\").compare;\nlet palabras = [\"luz\", \"llama\", \"como\", \"chico\"];\npalabras.sort(modernSpanish)      // => [\"chico\", \"como\", \"llama\", \"luz\"]\npalabras.sort(traditionalSpanish) // => [\"como\", \"chico\", \"luz\", \"llama\"]\n```\n## 11.8 The Console API\nYou’ve seen the console.log() function used throughout this book: in web browsers, it prints a string in the “Console” tab of the browser’s developer tools pane, which can be very helpful when debugging. In Node, console.log() is a general-purpose output function and prints its arguments to the process’s stdout stream, where it typically appears to the user in a terminal window as program output.\n\nThe Console API defines a number of useful functions in addition to console.log(). The API is not part of any ECMAScript standard, but it is supported by browsers and by Node and has been formally written up and standardized at https://console.spec.whatwg.org.\n\nThe Console API defines the following functions:\n\nconsole.log()\nThis is the most well-known of the console functions. It converts its arguments to strings and outputs them to the console. It includes spaces between the arguments and starts a new line after outputting all arguments.\n\nconsole.debug(), console.info(), console.warn(), console.error()\nThese functions are almost identical to console.log(). In Node, console.error() sends its output to the stderr stream rather than the stdout stream, but the other functions are aliases of console.log(). In browsers, output messages generated by each of these functions may be prefixed by an icon that indicates its level or severity, and the developer console may also allow developers to filter console messages by level.\n\nconsole.assert()\nIf the first argument is truthy (i.e., if the assertion passes), then this function does nothing. But if the first argument is false or another falsy value, then the remaining arguments are printed as if they had been passed to console.error() with an “Assertion failed” prefix. Note that, unlike typical assert() functions, console.assert() does not throw an exception when an assertion fails.\n\nconsole.clear()\nThis function clears the console when that is possible. This works in browsers and in Node when Node is displaying its output to a terminal. If Node’s output has been redirected to a file or a pipe, however, then calling this function has no effect.\n\nconsole.table()\nThis function is a remarkably powerful but little-known feature for producing tabular output, and it is particularly useful in Node programs that need to produce output that summarizes data. console.table() attempts to display its argument in tabular form (although, if it can’t do that, it displays it using regular console.log() formatting). This works best when the argument is a relatively short array of objects, and all of the objects in the array have the same (relatively small) set of properties. In this case, each object in the array is formatted as a row of the table, and each property is a column of the table. You can also pass an array of property names as an optional second argument to specify the desired set of columns. If you pass an object instead of an array of objects, then the output will be a table with one column for property names and one column for property values. Or, if those property values are themselves objects, their property names will become columns in the table.\n\nconsole.trace()\nThis function logs its arguments like console.log() does, and, in addition, follows its output with a stack trace. In Node, the output goes to stderr instead of stdout.\n\nconsole.count()\nThis function takes a string argument and logs that string, followed by the number of times it has been called with that string. This can be useful when debugging an event handler, for example, if you need to keep track of how many times the event handler has been triggered.\n\nconsole.countReset()\nThis function takes a string argument and resets the counter for that string.\n\nconsole.group()\nThis function prints its arguments to the console as if they had been passed to console.log(), then sets the internal state of the console so that all subsequent console messages (until the next console.groupEnd() call) will be indented relative to the message that it just printed. This allows a group of related messages to be visually grouped with indentation. In web browsers, the developer console typically allows grouped messages to be collapsed and expanded as a group. The arguments to console.group() are typically used to provide an explanatory name for the group.\n\nconsole.groupCollapsed()\nThis function works like console.group() except that in web browsers, the group will be “collapsed” by default and the messages it contains will be hidden unless the user clicks to expand the group. In Node, this function is a synonym for console.group().\n\nconsole.groupEnd()\nThis function takes no arguments. It produces no output of its own but ends the indentation and grouping caused by the most recent call to console.group() or console.groupCollapsed().\n\nconsole.time()\nThis function takes a single string argument, makes a note of the time it was called with that string, and produces no output.\n\nconsole.timeLog()\nThis function takes a string as its first argument. If that string had previously been passed to console.time(), then it prints that string followed by the elapsed time since the console.time() call. If there are any additional arguments to console.timeLog(), they are printed as if they had been passed to console.log().\n\nconsole.timeEnd()\nThis function takes a single string argument. If that argument had previously been passed to console.time(), then it prints that argument and the elapsed time. After calling console.timeEnd(), it is no longer legal to call console.timeLog() without first calling console.time() again.\n\n### 11.8.1 Formatted Output with Console\nConsole functions that print their arguments like console.log() have a little-known feature: if the first argument is a string that includes %s, %i, %d, %f, %o, %O, or %c, then this first argument is treated as format string,6 and the values of subsequent arguments are substituted into the string in place of the two-character % sequences.\n\nThe meanings of the sequences are as follows:\n\n%s\nThe argument is converted to a string.\n\n%i and %d\nThe argument is converted to a number and then truncated to an integer.\n\n%f\nThe argument is converted to a number\n\n%o and %O\nThe argument is treated as an object, and property names and values are displayed. (In web browsers, this display is typically interactive, and users can expand and collapse properties to explore a nested data structure.) %o and %O both display object details. The uppercase variant uses an implementation-dependent output format that is judged to be most useful for software developers.\n\n%c\nIn web browsers, the argument is interpreted as a string of CSS styles and used to style any text that follows (until the next %c sequence or the end of the string). In Node, the %c sequence and its corresponding argument are simply ignored.\n\nNote that it is not often necessary to use a format string with the console functions: it is usually easy to obtain suitable output by simply passing one or more values (including objects) to the function and allowing the implementation to display them in a useful way. As an example, note that, if you pass an Error object to console.log(), it is automatically printed along with its stack trace.\n\n## 11.9 URL APIs\nSince JavaScript is so commonly used in web browsers and web servers, it is common for JavaScript code to need to manipulate URLs. The URL class parses URLs and also allows modification (adding search parameters or altering paths, for example) of existing URLs. It also properly handles the complicated topic of escaping and unescaping the various components of a URL.\n\nThe URL class is not part of any ECMAScript standard, but it works in Node and all internet browsers other than Internet Explorer. It is standardized at https://url.spec.whatwg.org.\n\nCreate a URL object with the URL() constructor, passing an absolute URL string as the argument. Or pass a relative URL as the first argument and the absolute URL that it is relative to as the second argument. Once you have created the URL object, its various properties allow you to query unescaped versions of the various parts of the URL:\n```js\nlet url = new URL(\"https://example.com:8000/path/name?q=term#fragment\");\nurl.href        // => \"https://example.com:8000/path/name?q=term#fragment\"\nurl.origin      // => \"https://example.com:8000\"\nurl.protocol    // => \"https:\"\nurl.host        // => \"example.com:8000\"\nurl.hostname    // => \"example.com\"\nurl.port        // => \"8000\"\nurl.pathname    // => \"/path/name\"\nurl.search      // => \"?q=term\"\nurl.hash        // => \"#fragment\"\n```\nAlthough it is not commonly used, URLs can include a username or a username and password, and the URL class can parse these URL components, too:\n```js\nlet url = new URL(\"ftp://admin:1337!@ftp.example.com/\");\nurl.href       // => \"ftp://admin:1337!@ftp.example.com/\"\nurl.origin     // => \"ftp://ftp.example.com\"\nurl.username   // => \"admin\"\nurl.password   // => \"1337!\"\n```\nThe origin property here is a simple combination of the URL protocol and host (including the port if one is specified). As such, it is a read-only property. But each of the other properties demonstrated in the previous example is read/write: you can set any of these properties to set the corresponding part of the URL:\n```js\nlet url = new URL(\"https://example.com\");  // Start with our server\nurl.pathname = \"api/search\";               // Add a path to an API endpoint\nurl.search = \"q=test\";                     // Add a query parameter\nurl.toString()  // => \"https://example.com/api/search?q=test\"\n```\nOne of the important features of the URL class is that it correctly adds punctuation and escapes special characters in URLs when that is needed:\n```js\nlet url = new URL(\"https://example.com\");\nurl.pathname = \"path with spaces\";\nurl.search = \"q=foo#bar\";\nurl.pathname  // => \"/path%20with%20spaces\"\nurl.search    // => \"?q=foo%23bar\"\nurl.href      // => \"https://example.com/path%20with%20spaces?q=foo%23bar\"\n```\nThe href property in these examples is a special one: reading href is equivalent to calling toString(): it reassembles all parts of the URL into the canonical string form of the URL. And setting href to a new string reruns the URL parser on the new string as if you had called the URL() constructor again.\n\nIn the previous examples, we’ve been using the search property to refer to the entire query portion of a URL, which consists of the characters from a question mark to the end of the URL or to the first hash character. Sometimes, it is sufficient to just treat this as a single URL property. Often, however, HTTP requests encode the values of multiple form fields or multiple API parameters into the query portion of a URL using the application/x-www-form-urlencoded format. In this format, the query portion of the URL is a question mark followed by one or more name/value pairs, which are separated from one another by ampersands. The same name can appear more than once, resulting in a named search parameter with more than one value.\n\nIf you want to encode these kinds of name/value pairs into the query portion of a URL, then the searchParams property will be more useful than the search property. The search property is a read/write string that lets you get and set the entire query portion of the URL. The searchParams property is a read-only reference to a URLSearchParams object, which has an API for getting, setting, adding, deleting, and sorting the parameters encoded into the query portion of the URL:\n```js\nlet url = new URL(\"https://example.com/search\");\nurl.search                            // => \"\": no query yet\nurl.searchParams.append(\"q\", \"term\"); // Add a search parameter\nurl.search                            // => \"?q=term\"\nurl.searchParams.set(\"q\", \"x\");       // Change the value of this parameter\nurl.search                            // => \"?q=x\"\nurl.searchParams.get(\"q\")             // => \"x\": query the parameter value\nurl.searchParams.has(\"q\")             // => true: there is a q parameter\nurl.searchParams.has(\"p\")             // => false: there is no p parameter\nurl.searchParams.append(\"opts\", \"1\"); // Add another search parameter\nurl.search                            // => \"?q=x&opts=1\"\nurl.searchParams.append(\"opts\", \"&\"); // Add another value for same name\nurl.search                            // => \"?q=x&opts=1&opts=%26\": note escape\nurl.searchParams.get(\"opts\")          // => \"1\": the first value\nurl.searchParams.getAll(\"opts\")       // => [\"1\", \"&\"]: all values\nurl.searchParams.sort();              // Put params in alphabetical order\nurl.search                            // => \"?opts=1&opts=%26&q=x\"\nurl.searchParams.set(\"opts\", \"y\");    // Change the opts param\nurl.search                            // => \"?opts=y&q=x\"\n// searchParams is iterable\n[...url.searchParams]                 // => [[\"opts\", \"y\"], [\"q\", \"x\"]]\nurl.searchParams.delete(\"opts\");      // Delete the opts param\nurl.search                            // => \"?q=x\"\nurl.href                              // => \"https://example.com/search?q=x\"\n```\nThe value of the searchParams property is a URLSearchParams object. If you want to encode URL parameters into a query string, you can create a URLSearchParams object, append parameters, then convert it to a string and set it on the search property of a URL:\n```js\nlet url = new URL(\"http://example.com\");\nlet params = new URLSearchParams();\nparams.append(\"q\", \"term\");\nparams.append(\"opts\", \"exact\");\nparams.toString()               // => \"q=term&opts=exact\"\nurl.search = params;\nurl.href                        // => \"http://example.com/?q=term&opts=exact\"\n```\n### 11.9.1 Legacy URL Functions\nPrior to the definition of the URL API described previously, there have been multiple attempts to support URL escaping and unescaping in the core JavaScript language. The first attempt was the globally defined escape() and unescape() functions, which are now deprecated but still widely implemented. They should not be used.\n\nWhen escape() and unescape() were deprecated, ECMAScript introduced two pairs of alternative global functions:\n\nencodeURI() and decodeURI()\nencodeURI() takes a string as its argument and returns a new string in which non-ASCII characters plus certain ASCII characters (such as space) are escaped. decodeURI() reverses the process. Characters that need to be escaped are first converted to their UTF-8 encoding, then each byte of that encoding is replaced with a %xx escape sequence, where xx is two hexadecimal digits. Because encodeURI() is intended for encoding entire URLs, it does not escape URL separator characters such as /, ?, and #. But this means that encodeURI() cannot work correctly for URLs that have those characters within their various components.\n\nencodeURIComponent() and decodeURIComponent()\nThis pair of functions works just like encodeURI() and decodeURI() except that they are intended to escape individual components of a URI, so they also escape characters like /, ?, and # that are used to separate those components. These are the most useful of the legacy URL functions, but be aware that encodeURIComponent() will escape / characters in a path name that you probably do not want escaped. And it will convert spaces in a query parameter to %20, even though spaces are supposed to be escaped with a + in that portion of a URL.\n\nThe fundamental problem with all of these legacy functions is that they seek to apply a single encoding scheme to all parts of a URL when the fact is that different portions of a URL use different encodings. If you want a properly formatted and encoded URL, the solution is simply to use the URL class for all URL manipulation you do.\n\n## 11.10 Timers\nSince the earliest days of JavaScript, web browsers have defined two functions—setTimeout() and setInterval()—that allow programs to ask the browser to invoke a function after a specified amount of time has elapsed or to invoke the function repeatedly at a specified interval. These functions have never been standardized as part of the core language, but they work in all browsers and in Node and are a de facto part of the JavaScript standard library.\n\nThe first argument to setTimeout() is a function, and the second argument is a number that specifies how many milliseconds should elapse before the function is invoked. After the specified amount of time (and maybe a little longer if the system is busy), the function will be invoked with no arguments. Here, for example, are three setTimeout() calls that print console messages after one second, two seconds, and three seconds:\n```js\nsetTimeout(() => { console.log(\"Ready...\"); }, 1000);\nsetTimeout(() => { console.log(\"set...\"); }, 2000);\nsetTimeout(() => { console.log(\"go!\"); }, 3000);\n```\nNote that setTimeout() does not wait for the time to elapse before returning. All three lines of code in this example run almost instantly, but then nothing happens until 1,000 milliseconds elapse.\n\nIf you omit the second argument to setTimeout(), it defaults to 0. That does not mean, however, that the function you specify is invoked immediately. Instead, the function is registered to be called “as soon as possible.” If a browser is particularly busy handling user input or other events, it may take 10 milliseconds or more before the function is invoked.\n\nsetTimeout() registers a function to be invoked once. Sometimes, that function will itself call setTimeout() to schedule another invocation at a future time. If you want to invoke a function repeatedly, however, it is often simpler to use setInterval(). setInterval() takes the same two arguments as setTimeout() but invokes the function repeatedly every time the specified number of milliseconds (approximately) have elapsed.\n\nBoth setTimeout() and setInterval() return a value. If you save this value in a variable, you can then use it later to cancel the execution of the function by passing it to clearTimeout() or clearInterval(). The returned value is typically a number in web browsers and is an object in Node. The actual type doesn’t matter, and you should treat it as an opaque value. The only thing you can do with this value is pass it to clearTimeout() to cancel the execution of a function registered with setTimeout() (assuming it hasn’t been invoked yet) or to stop the repeating execution of a function registered with setInterval().\n\nHere is an example that demonstrates the use of setTimeout(), setInterval(), and clearInterval() to display a simple digital clock with the Console API:\n```js\n// Once a second: clear the console and print the current time\nlet clock = setInterval(() => {\n    console.clear();\n    console.log(new Date().toLocaleTimeString());\n}, 1000);\n\n// After 10 seconds: stop the repeating code above.\nsetTimeout(() => { clearInterval(clock); }, 10000);\nWe’ll see setTimeout() and setInterval() again when we cover asynchronous programming in Chapter 13.\n```\n## 11.11 Summary\nLearning a programming language is not just about mastering the grammar. It is equally important to study the standard library so that you are familiar with all the tools that are shipped with the language. This chapter has documented JavaScript’s standard library, which includes:\n\nImportant data structures, such as Set, Map, and typed arrays.\n\nThe Date and URL classes for working with dates and URLs.\n\nJavaScript’s regular expression grammar and its RegExp class for textual pattern matching.\n\nJavaScript’s internationalization library for formatting dates, time, and numbers and for sorting strings.\n\nThe JSON object for serializing and deserializing simple data structures and the console object for logging messages.\n\n---\n\n[^1]: Not everything documented here is defined by the JavaScript language specification: some of the classes and functions documented here were first implemented in web browsers and then adopted by Node, making them de facto members of the JavaScript standard library.\n2. This predictable iteration order is another thing about JavaScript sets that Python programmers may find surprising.\n3. Typed arrays were first introduced to client-side JavaScript when web browsers added support for WebGL graphics. What is new in ES6 is that they have been elevated to a core language feature.\n4. Except within a character class (square brackets), where \\b matches the backspace character.\n5. Parsing URLs with regular expressions is not a good idea. See §11.9 for a more robust URL parser.\n6. C programmers will recognize many of these character sequences from the printf() function."
  },
  {
    "path": "content/posts/ch12.md",
    "content": "---\ntitle: \"第 12 章 迭代器和生成器\"\ndate: 2020-11-02T22:18:31+08:00\n---\n\nIterable objects and their associated iterators are a feature of ES6 that we’ve seen several times throughout this book. Arrays (including TypedArrays) are iterable, as are strings and Set and Map objects. This means that the contents of these data structures can be iterated—looped over—with the for/of loop, as we saw in §5.4.4:\n\n> 可迭代对象及其关联的迭代器是 ES6 的一个特性，在本书中我们已经多次看到。数组（包括 typedarray）是可迭代的，字符串、Set 和 Map 对象也是如此。这意味着这些数据结构的内容可以被 for/of 循环遍历，就像我们在 §5.4.4 中看到的那样：\n\n```js\nlet sum = 0;\nfor(let i of [1,2,3]) { // Loop once for each of these values\n    sum += i;\n}\nsum   // => 6\n```\n\nIterators can also be used with the ... operator to expand or “spread” an iterable object into an array initializer or function invocation, as we saw in §7.1.2:\n\n> 迭代器还可以用 ... 运算符将可迭代对象展开或“扩展”到数组初始化或函数调用中，如 §7.1.2 所示：\n\n```js\nlet chars = [...\"abcd\"]; // chars == [\"a\", \"b\", \"c\", \"d\"]\nlet data = [1, 2, 3, 4, 5];\nMath.max(...data)        // => 5\n```\n\nIterators can be used with destructuring assignment:\n\n> 迭代器可以与析构赋值一起使用：\n\n```js\nlet purpleHaze = Uint8Array.of(255, 0, 255, 128);\nlet [r, g, b, a] = purpleHaze; // a == 128\n```\n\nWhen you iterate a Map object, the returned values are [key, value] pairs, which work well with destructuring assignment in a for/of loop:\n\n> 当你迭代一个 Map 对象时，返回的值是 [key, value] 对，这在 for/of 循环的解构赋值中很好用：\n\n```js\nlet m = new Map([[\"one\", 1], [\"two\", 2]]);\nfor(let [k,v] of m) console.log(k, v); // Logs 'one 1' and 'two 2'\n```\n\nIf you want to iterate just the keys or just the values rather than the pairs, you can use the keys() and values() methods:\n\n> 如果只迭代键或只迭代值而不是一对，可以使用 keys() 和 values() 方法:\n\n```js\n[...m]            // => [[\"one\", 1], [\"two\", 2]]: default iteration\n[...m.entries()]  // => [[\"one\", 1], [\"two\", 2]]: entries() method is the same\n[...m.keys()]     // => [\"one\", \"two\"]: keys() method iterates just map keys\n[...m.values()]   // => [1, 2]: values() method iterates just map values\n```\n\nFinally, a number of built-in functions and constructors that are commonly used with Array objects are actually written (in ES6 and later) to accept arbitrary iterators instead. The Set() constructor is one such API:\n\n> 最后，通常用于数组对象的许多内置函数和构造函数实际上被编写（在ES6及以后版本中）为接受任意实参的迭代器。Set() 构造函数就是这样一种API：\n\n```js\n// Strings are iterable, so the two sets are the same:\nnew Set(\"abc\") // => new Set([\"a\", \"b\", \"c\"])\n```\n\nThis chapter explains how iterators work and demonstrates how to create your own data structures that are iterable. After explaining basic iterators, this chapter covers generators, a powerful new feature of ES6 that is primarily used as a particularly easy way to create iterators.\n\n> 本章说明了迭代器是如何工作的，并演示了如何创建自己的可迭代的数据结构。在说明了基本的迭代器之后，本章将介绍生成器，这是 ES6 的一个强大的新特性，它是一种特别简单的方法创建迭代器。\n\n## 12.1 How Iterators Work\n\nThe for/of loop and spread operator work seamlessly with iterable objects, but it is worth understanding what is actually happening to make the iteration work. There are three separate types that you need to understand to understand iteration in JavaScript. First, there are the iterable objects: these are types like Array, Set, and Map that can be iterated. Second, there is the iterator object itself, which performs the iteration. And third, there is the iteration result object that holds the result of each step of the iteration.\n\n> for/of 循环和展开运算符可与可迭代对象无缝配合，但是值得了解使迭代工作的实际情况。需要了解三种独立的类型才能理解 JavaScript 中的迭代。首先，可迭代的对象：可以迭代的是诸如 Array，Set 和 Map 之类的类型。其次，迭代器对象本身，它执行迭代。第三，一个迭代结果对象，该对象保存迭代的每个步骤的结果。\n\nAn iterable object is any object with a special iterator method that returns an iterator object. An iterator is any object with a next() method that returns an iteration result object. And an iteration result object is an object with properties named value and done. To iterate an iterable object, you first call its iterator method to get an iterator object. Then, you call the next() method of the iterator object repeatedly until the returned value has its done property set to true. The tricky thing about this is that the iterator method of an iterable object does not have a conventional name but uses the Symbol Symbol.iterator as its name. So a simple for/of loop over an iterable object iterable could also be written the hard way, like this:\n\n> 任何对象具有特殊迭代器方法，并且该方法返回迭代器对象，那么该对象为可迭代对象。迭代器对象具有 next() 方法，该方法返回迭代结果对象。迭代结果对象是具有名为 value 和 done 的属性的对象。要迭代一个可迭代的对象，首先要调用其迭代器方法以获取一个迭代器对象。然后，重复调用迭代器对象的 next() 方法，直到返回的值的 done 属性设置为 true。棘手的事情是，可迭代对象的迭代器方法没有常规名称，而是使用 Symbol Symbol.iterator 作为其名称。因此，也可以用很复杂的方式编写可迭代对象的简单 for/of 循环，如下所示：\n\n```js\nlet iterable = [99];\nlet iterator = iterable[Symbol.iterator]();\nfor(let result = iterator.next(); !result.done; result = iterator.next()) {\n    console.log(result.value)  // result.value == 99\n}\n```\nThe iterator object of the built-in iterable datatypes is itself iterable. (That is, it has a method named Symbol.iterator that just returns itself.) This is occasionally useful in code like the following when you want to iterate though a “partially used” iterator:\n\n> 内置可迭代数据类型的迭代器对象本身是可迭代的。（也就是说，它具有一个名为 Symbol.iterator 的方法，该方法会自行返回。）当要通过“部分使用”的迭代器进行迭代时，以下代码中有时会很有用：\n\n```js\nlet list = [1,2,3,4,5];\nlet iter = list[Symbol.iterator]();\nlet head = iter.next().value;  // head == 1\nlet tail = [...iter];          // tail == [2,3,4,5]\n```\n## 12.2 Implementing Iterable Objects\nIterable objects are so useful in ES6 that you should consider making your own datatypes iterable whenever they represent something that can be iterated. The Range classes shown in Examples 9-2 and 9-3 in Chapter 9 were iterable. Those classes used generator functions to make themselves iterable. We’ll document generators later in this chapter, but first, we will implement the Range class one more time, making it iterable without relying on a generator.\n\n> 可迭代对象在 ES6 中非常有用，应该考虑使自己的数据类型在可以表示迭代的任何时候都可迭代。第 9 章示例 9-2 和 9-3 中显示的 Range 类是可迭代的。这些类使用生成器函数使其可迭代。我们将在本章稍后介绍生成器，但首先，我们将再次实现 Range 类，使其无需依赖生成器即可迭代。\n\nIn order to make a class iterable, you must implement a method whose name is the Symbol Symbol.iterator. That method must return an iterator object that has a next() method. And the next() method must return an iteration result object that has a value property and/or a boolean done property. Example 12-1 implements an iterable Range class and demonstrates how to create iterable, iterator, and iteration result objects.\n\n> 为了使类可迭代，必须实现一个名称为 Symbol Symbol.iterator 的方法。该方法必须返回一个具有 next() 方法的迭代器对象。并且 next() 方法必须返回具有 value 属性和或或布尔型 done 属性的迭代结果对象。示例 12-1 实现了一个可迭代的 Range 类，并演示了如何创建可迭代的、迭代器和迭代结果对象。\n\nExample 12-1. An iterable numeric Range class\n\n> 示例 12-1 一个可迭代数值范围类\n\n```js\n/*\n * A Range object represents a range of numbers {x: from <= x <= to}\n * Range defines a has() method for testing whether a given number is a member\n * of the range. Range is iterable and iterates all integers within the range.\n */\nclass Range {\n    constructor (from, to) {\n        this.from = from;\n        this.to = to;\n    }\n\n    // Make a Range act like a Set of numbers\n    has(x) { return typeof x === \"number\" && this.from <= x && x <= this.to; }\n\n    // Return string representation of the range using set notation\n    toString() { return `{ x | ${this.from} ≤ x ≤ ${this.to} }`; }\n\n    // Make a Range iterable by returning an iterator object.\n    // Note that the name of this method is a special symbol, not a string.\n    [Symbol.iterator]() {\n        // Each iterator instance must iterate the range independently of\n        // others. So we need a state variable to track our location in the\n        // iteration. We start at the first integer >= from.\n        let next = Math.ceil(this.from);  // This is the next value we return\n        let last = this.to;               // We won't return anything > this\n        return {                          // This is the iterator object\n            // This next() method is what makes this an iterator object.\n            // It must return an iterator result object.\n            next() {\n                return (next <= last)   // If we haven't returned last value yet\n                    ? { value: next++ } // return next value and increment it\n                    : { done: true };   // otherwise indicate that we're done.\n            },\n\n            // As a convenience, we make the iterator itself iterable.\n            [Symbol.iterator]() { return this; }\n        };\n    }\n}\n\nfor(let x of new Range(1,10)) console.log(x); // Logs numbers 1 to 10\n[...new Range(-2,2)]                          // => [-2, -1, 0, 1, 2]\n```\nIn addition to making your classes iterable, it can be quite useful to define functions that return iterable values. Consider these iterable-based alternatives to the map() and filter() methods of JavaScript arrays:\n\n> 除了使类可迭代外，定义返回可迭代值的函数也非常有用。考虑 JavaScript 数组的 map() 和 filter() 方法的这些基于迭代的替代方法：\n\n```js\n// Return an iterable object that iterates the result of applying f()\n// to each value from the source iterable\nfunction map(iterable, f) {\n    let iterator = iterable[Symbol.iterator]();\n    return {     // This object is both iterator and iterable\n        [Symbol.iterator]() { return this; },\n        next() {\n            let v = iterator.next();\n            if (v.done) {\n                return v;\n            } else {\n                return { value: f(v.value) };\n            }\n        }\n    };\n}\n\n// Map a range of integers to their squares and convert to an array\n[...map(new Range(1,4), x => x*x)]  // => [1, 4, 9, 16]\n\n// Return an iterable object that filters the specified iterable,\n// iterating only those elements for which the predicate returns true\nfunction filter(iterable, predicate) {\n    let iterator = iterable[Symbol.iterator]();\n    return { // This object is both iterator and iterable\n        [Symbol.iterator]() { return this; },\n        next() {\n            for(;;) {\n                let v = iterator.next();\n                if (v.done || predicate(v.value)) {\n                    return v;\n                }\n            }\n        }\n    };\n}\n\n// Filter a range so we're left with only even numbers\n[...filter(new Range(1,10), x => x % 2 === 0)]  // => [2,4,6,8,10]\n```\nOne key feature of iterable objects and iterators is that they are inherently lazy: when computation is required to compute the next value, that computation can be deferred until the value is actually needed. Suppose, for example, that you have a very long string of text that you want to tokenize into space-separated words. You could simply use the split() method of your string, but if you do this, then the entire string has to be processed before you can use even the first word. And you end up allocating lots of memory for the returned array and all of the strings within it. Here is a function that allows you to lazily iterate the words of a string without keeping them all in memory at once (in ES2020, this function would be much easier to implement using the iterator-returning matchAll() method described in §11.3.2):\n\n> 可迭代对象和迭代器的一个关键特性是它们固有的惰性：当需要计算才能计算下一个值时，可以将计算推迟到实际需要该值时进行。例如，假设有一个很长的文本字符串，想将其标记为以空格分隔的单词。可以简单地使用字符串的 split() 方法，但是如果这样做，则必须先处理整个字符串，才能使用第一个单词。最后，将为返回的数组及其中的所有字符串分配大量内存。这是一个允许懒惰地迭代字符串的祖母而无需一次将所有单词都保留在内存中的函数（在 ES2020 中，使用 §11.3.2 中描述的迭代器返回 matchAll() 方法可以更轻松地实现此函数。）：\n\n```js\nfunction words(s) {\n    var r = /\\s+|$/g;                     // Match one or more spaces or end\n    r.lastIndex = s.match(/[^ ]/).index;  // Start matching at first nonspace\n    return {                              // Return an iterable iterator object\n        [Symbol.iterator]() {             // This makes us iterable\n            return this;\n        },\n        next() {                          // This makes us an iterator\n            let start = r.lastIndex;      // Resume where the last match ended\n            if (start < s.length) {       // If we're not done\n                let match = r.exec(s);    // Match the next word boundary\n                if (match) {              // If we found one, return the word\n                    return { value: s.substring(start, match.index) };\n                }\n            }\n            return { done: true };        // Otherwise, say that we're done\n        }\n    };\n}\n\n[...words(\" abc def  ghi! \")] // => [\"abc\", \"def\", \"ghi!\"]\n```\n### 12.2.1 “Closing” an Iterator: The Return Method\nImagine a (server-side) JavaScript variant of the words() iterator that, instead of taking a source string as its argument, takes the name of a file, opens the file, reads lines from it, and iterates the words from those lines. In most operating systems, programs that open files to read from them need to remember to close those files when they are done reading, so this hypothetical iterator would be sure to close the file after the next() method returns the last word in it.\n\n> 想象一下 words() 迭代器的（服务器端）JavaScript 变体，它不用源字符串作为实参，而是使用文件名，打开文件，从文件中读取行，然后从这些行中迭代单词。在大多数操作系统中，打开文件以从其中读取文件的程序需要记住在完成读取后关闭这些文件，因此，这个假设的迭代器将确保在 next() 方法返回文件中的最后一个字之后关闭文件。\n\nBut iterators don’t always run all the way to the end: a for/of loop might be terminated with a break or return or by an exception. Similarly, when an iterator is used with destructuring assignment, the next() method is only called enough times to obtain values for each of the specified variables. The iterator may have many more values it could return, but they will never be requested.\n\n> 但是迭代器并不总是一直运行到最后：for/of 循环可能会因中断或返回或异常而终止。同样，当迭代器用于解构赋值时，仅调用 next() 方法足够多次，以获取每个指定变量的值。迭代器可能有更多可能返回的值，但是它们永远不会被请求。\n\nIf our hypothetical words-in-a-file iterator never runs all the way to the end, it still needs to close the file it opened. For this reason, iterator objects may implement a return() method to go along with the next() method. If iteration stops before next() has returned an iteration result with the done property set to true (most commonly because you left a for/of loop early via a break statement), then the interpreter will check to see if the iterator object has a return() method. If this method exists, the interpreter will invoke it with no arguments, giving the iterator the chance to close files, release memory, and otherwise clean up after itself. The return() method must return an iterator result object. The properties of the object are ignored, but it is an error to return a non-object value.\n\n> 如果我们假设的文件中单词迭代器从未一直运行到最后，它仍然需要关闭它打开的文件。因此，迭代器对象可以实现 return() 方法与 next() 方法一起使用。如果迭代在 next() 返回完了属性设置为 true 的迭代结果之前停止（通常是因为通过 break 语句提前离开了 for/of 循环），则解释器将检查迭代器对象是否具有 return() 方法。如果存在此方法，则解释器将不带任何参数调用它，从而使迭代器有机会关闭文件，释放内存以及自行清理。return() 方法必须返回迭代器结果对象。对象的属性被忽略，但是返回非对象值是错误的。\n\nThe for/of loop and the spread operator are really useful features of JavaScript, so when you are creating APIs, it is a good idea to use them when possible. But having to work with an iterable object, its iterator object, and the iterator’s result objects makes the process somewhat complicated. Fortunately, generators can dramatically simplify the creation of custom iterators, as we’ll see in the rest of this chapter.\n\n> for/of 循环和展开运算符是 JavaScript 真正实用功能，因此在创建 API 时，最好在尽可能的使用它们。但是必须使用一个可迭代的对象，其迭代器对象以及迭代器的结果对象，这会使过程变得有些复杂。幸运的是，生成器可以极大地简化自定义迭代器的创建，这将在本章的其余部分中看到。\n\n## 12.3 Generators\nA generator is a kind of iterator defined with powerful new ES6 syntax; it’s particularly useful when the values to be iterated are not the elements of a data structure, but the result of a computation.\n\n> 生成器是一种使用强大的新 ES6 语法定义的迭代器；当要迭代的值不是数据结构的元素而是计算结果时，此功能特别有用。\n\nTo create a generator, you must first define a generator function. A generator function is syntactically like a regular JavaScript function but is defined with the keyword function* rather than function. (Technically, this is not a new keyword, just a * after the keyword function and before the function name.) When you invoke a generator function, it does not actually execute the function body, but instead returns a generator object. This generator object is an iterator. Calling its next() method causes the body of the generator function to run from the start (or whatever its current position is) until it reaches a yield statement. yield is new in ES6 and is something like a return statement. The value of the yield statement becomes the value returned by the next() call on the iterator. An example makes this clearer:\n\n> 要创建生成器，必须首先定义一个生成器函数。生成器函数在语法上类似于常规 JavaScript 函数，但使用关键字 function* 而不是 function 定义。（从技术上讲，这不是新关键字，只是关键字 function 之后和函数名称之前的 *。）调用生成器函数时，它实际上并不执行函数主体，而是返回生成器对象。该生成器对象是一个迭代器。调用其 next() 方法会使生成器函数的主体从头开始运行（或无论其当前位置是什么），直到到达 yield 语句为止。yield 是 ES6 新特性，类似于 return 语句。yield 语句的值成为迭代器上 next() 调用返回的值。一个示例使这更加清楚：\n\n```js\n// A generator function that yields the set of one digit (base-10) primes.\nfunction* oneDigitPrimes() { // Invoking this function does not run the code\n    yield 2;                 // but just returns a generator object. Calling\n    yield 3;                 // the next() method of that generator runs\n    yield 5;                 // the code until a yield statement provides\n    yield 7;                 // the return value for the next() method.\n}\n\n// When we invoke the generator function, we get a generator\nlet primes = oneDigitPrimes();\n\n// A generator is an iterator object that iterates the yielded values\nprimes.next().value          // => 2\nprimes.next().value          // => 3\nprimes.next().value          // => 5\nprimes.next().value          // => 7\nprimes.next().done           // => true\n\n// Generators have a Symbol.iterator method to make them iterable\nprimes[Symbol.iterator]()    // => primes\n\n// We can use generators like other iterable types\n[...oneDigitPrimes()]        // => [2,3,5,7]\nlet sum = 0;\nfor(let prime of oneDigitPrimes()) sum += prime;\nsum                          // => 17\n```\nIn this example, we used a function* statement to define a generator. Like regular functions, however, we can also define generators in expression form. Once again, we just put an asterisk after the function keyword:\n\n> 在此示例中，我们使用 function* 语句定义了生成器。但是，像常规函数一样，我们也可以在 from 表达式中定义生成器。再一次，我们只在 function 关键字之后加上一个星号：\n\n```js\nconst seq = function*(from,to) {\n    for(let i = from; i <= to; i++) yield i;\n};\n[...seq(3,5)]  // => [3, 4, 5]\n```\nIn classes and object literals, we can use shorthand notation to omit the function keyword entirely when we define methods. To define a generator in this context, we simply use an asterisk before the method name where the function keyword would have been, had we used it:\n\n> 在类和对象文字中，我们在定义方法时可以使用速记标记来完全省略 function 关键字。要在这种情况下定义生成器，我们只需在方法名称之前使用星号即可：\n\n```js\nlet o = {\n    x: 1, y: 2, z: 3,\n    // A generator that yields each of the keys of this object\n    *g() {\n        for(let key of Object.keys(this)) {\n            yield key;\n        }\n    }\n};\n[...o.g()] // => [\"x\", \"y\", \"z\", \"g\"]\n```\nNote that there is no way to write a generator function using arrow function syntax.\n\n> 请注意，无法使用箭头函数语法编写生成器函数。\n\nGenerators often make it particularly easy to define iterable classes. We can replace the `[Symbol.iterator]()` method show in Example 12-1 with a much shorter `*[Symbol.iterator]()` generator function that looks like this:\n\n> 生成器通常使定义可迭代类特别容易。我们可以用更短的 `*[Symbol.iterator];()` 生成器函数代替示例 12-1 中的 `[Symbol.iterator]()` 方法，如下所示：\n\n```js\n*[Symbol.iterator]() {\n    for(let x = Math.ceil(this.from); x <= this.to; x++) yield x;\n}\n```\nSee Example 9-3 in Chapter 9 to see this generator-based iterator function in context.\n\n> 请参阅第 9 章中的示例 9-3，可以看到在上下文中看到此基于生成器的迭代器函数。 \n\n### 12.3.1 Generator Examples\nGenerators are more interesting if they actually generate the values they yield by doing some kind of computation. Here, for example, is a generator function that yields Fibonacci numbers:\n\n> 如果生成器实际上通过执行某种计算来生成它们产生的值，则它们会更有趣。例如，这里是生成斐波那契数的生成器函数：\n\n```js\nfunction* fibonacciSequence() {\n    let x = 0, y = 1;\n    for(;;) {\n        yield y;\n        [x, y] = [y, x+y];  // Note: destructuring assignment\n    }\n}\n```\nNote that the fibonacciSequence() generator function here has an infinite loop and yields values forever without returning. If this generator is used with the ... spread operator, it will loop until memory is exhausted and the program crashes. With care, it is possible to use it in a for/of loop, however:\n\n> 请注意，此处的 fibonacciSequence() 生成器函数具有无限循环，并且永久产生值而不会返回。如果将此生成器与 ... 展开运算符一起使用，它将循环播放直到内存耗尽且程序崩溃。小心地在 for/of 循环中使用它：\n\n```js\n// Return the nth Fibonacci number\nfunction fibonacci(n) {\n    for(let f of fibonacciSequence()) {\n        if (n-- <= 0) return f;\n    }\n}\nfibonacci(20)   // => 10946\n```\nThis kind of infinite generator becomes more useful with a take() generator like this:\n\n> 这种无限生成器在使用 take() 生成器时变得更加有用，如下所示：\n\n```js\n// Yield the first n elements of the specified iterable object\nfunction* take(n, iterable) {\n    let it = iterable[Symbol.iterator](); // Get iterator for iterable object\n    while(n-- > 0) {           // Loop n times:\n        let next = it.next();  // Get the next item from the iterator.\n        if (next.done) return; // If there are no more values, return early\n        else yield next.value; // otherwise, yield the value\n    }\n}\n\n// An array of the first 5 Fibonacci numbers\n[...take(5, fibonacciSequence())]  // => [1, 1, 2, 3, 5]\n```\nHere is another useful generator function that interleaves the elements of multiple iterable objects:\n\n> 这是另一个有用的生成器函数，它交错多个可迭代对象的元素：\n\n```js\n// Given an array of iterables, yield their elements in interleaved order.\nfunction* zip(...iterables) {\n    // Get an iterator for each iterable\n    let iterators = iterables.map(i => i[Symbol.iterator]());\n    let index = 0;\n    while(iterators.length > 0) {       // While there are still some iterators\n        if (index >= iterators.length) {    // If we reached the last iterator\n            index = 0;                      // go back to the first one.\n        }\n        let item = iterators[index].next(); // Get next item from next iterator.\n        if (item.done) {                    // If that iterator is done\n            iterators.splice(index, 1);     // then remove it from the array.\n        }\n        else {                              // Otherwise,\n            yield item.value;               // yield the iterated value\n            index++;                        // and move on to the next iterator.\n        }\n    }\n}\n\n// Interleave three iterable objects\n[...zip(oneDigitPrimes(),\"ab\",[0])]     // => [2,\"a\",0,3,\"b\",5,7]\n```\n### 12.3.2 yield* and Recursive Generators\nIn addition to the zip() generator defined in the preceding example, it might be useful to have a similar generator function that yields the elements of multiple iterable objects sequentially rather than interleaving them. We could write that generator like this:\n\n> 除了前面示例中定义的 zip() 生成器之外，具有类似的生成器功能可能会很有用，该功能可以按顺序生成多个可迭代对象的元素，而不是交织它们。我们可以这样编写生成器：\n\n```js\nfunction* sequence(...iterables) {\n    for(let iterable of iterables) {\n        for(let item of iterable) {\n            yield item;\n        }\n    }\n}\n\n[...sequence(\"abc\",oneDigitPrimes())]  // => [\"a\",\"b\",\"c\",2,3,5,7]\n```\nThis process of yielding the elements of some other iterable object is common enough in generator functions that ES6 has special syntax for it. The yield* keyword is like yield except that, rather than yielding a single value, it iterates an iterable object and yields each of the resulting values. The sequence() generator function that we’ve used can be simplified with yield* like this:\n\n> 这种生成其他可迭代对象的元素的过程在生成器函数中已经足够普遍，以至于为它 ES6 具有特殊的语法。yield* 关键字类似于 yield，除了它不产生单个值，而是迭代一个可迭代的对象并产生每个结果值。我们使用的 sequence() 生成器函数可以通过 yield* 进行简化，如下所示：\n\n```js\nfunction* sequence(...iterables) {\n    for(let iterable of iterables) {\n        yield* iterable;\n    }\n}\n\n[...sequence(\"abc\",oneDigitPrimes())]  // => [\"a\",\"b\",\"c\",2,3,5,7]\n```\nThe array forEach() method is often an elegant way to loop over the elements of an array, so you might be tempted to write the sequence() function like this:\n\n> 数组 forEach() 方法通常是一种循环遍历数组元素的好方法，因此可能会很想像这样编写 sequence() 函数：\n\n```js\nfunction* sequence(...iterables) {\n    iterables.forEach(iterable => yield* iterable );  // Error\n}\n```\nThis does not work, however. yield and yield* can only be used within generator functions, but the nested arrow function in this code is a regular function, not a function* generator function, so yield is not allowed.\n\n> 但是，这不起作用。yield 和 yield* 只能在生成器函数中使用，但是此代码中的嵌套箭头函数是常规函数，而不是 function* 生成器函数，因此不允许 yield。\n\nyield* can be used with any kind of iterable object, including iterables implemented with generators. This means that yield* allows us to define recursive generators, and you might use this feature to allow simple non-recursive iteration over a recursively defined tree structure, for example.\n\n> yield* 可用于任何种类的可迭代对象，包括使用生成器实现的可迭代对象。这意味着 yield* 允许我们定义递归生成器，例如，可以使用此功能在递归定义的树结构上进行简单的非递归迭代。\n\n## 12.4 Advanced Generator Features\nThe most common use of generator functions is to create iterators, but the fundamental feature of generators is that they allow us to pause a computation, yield intermediate results, and then resume the computation later. This means that generators have features beyond those of iterators, and we explore those features in the following sections.\n\n> 生成器函数最常见的用途是创建迭代器，但是生成器的基本特性是它们允许我们暂停计算，产生中间结果，然后在以后恢复计算。这意味着生成器具有的功能超出了迭代器的功能，我们将在以下各节中探讨这些功能。\n\n### 12.4.1 The Return Value of a Generator Function\nThe generator functions we’ve seen so far have not had return statements, or if they have, they have been used to cause an early return, not to return a value. Like any function, though, a generator function can return a value. In order to understand what happens in this case, recall how iteration works. The return value of the next() function is an object that has a value property and/or a done property. With typical iterators and generators, if the value property is defined, then the done property is undefined or is false. And if done is true, then value is undefined. But in the case of a generator that returns a value, the final call to next returns an object that has both value and done defined. The value property holds the return value of the generator function, and the done property is true, indicating that there are no more values to iterate. This final value is ignored by the for/of loop and by the spread operator, but it is available to code that manually iterates with explicit calls to next():\n\n> 到目前为止，我们看到的生成器函数还没有 return 语句，或者，如果有的话，它们只是被用来引起较早的返回，而不是产生返回值。但是，像任何函数一样，生成器函数可以返回一个值。为了了解在这种情况下会发生什么，请回忆一下迭代是如何工作的。next() 函数的返回值是一个具有 value 属性和或或 done 属性的对象。对于典型的迭代器和生成器，如果定义了 value 属性，则 done 属性是 undefined 或为 false。如果 done 为 true，那么值就是 undefined。但是，如果生成器返回一个值，则对 next 的最终调用将返回一个同时具有 value ​​和 done 定义的对象。 value 属性保存生成器函数的返回值，并且 done 属性为 true，表示没有更多的值可以迭代。最终值将被 for/of 循环和展开运算符忽略，但可用通过对 next() 的显式调用手动进行迭代：\n\n```js\nfunction *oneAndDone() {\n    yield 1;\n    return \"done\";\n}\n\n// The return value does not appear in normal iteration.\n[...oneAndDone()]   // => [1]\n\n// But it is available if you explicitly call next()\nlet generator = oneAndDone();\ngenerator.next()           // => { value: 1, done: false}\ngenerator.next()           // => { value: \"done\", done: true }\n// If the generator is already done, the return value is not returned again\ngenerator.next()           // => { value: undefined, done: true }\n```\n### 12.4.2 The Value of a yield Expression\nIn the preceding discussion, we’ve treated yield as a statement that takes a value but has no value of its own. In fact, however, yield is an expression, and it can have a value.\n\n> 在前面的讨论中，我们将 yield 视为带有值但没有自身值的语句。但是，实际上，yield 是一个表达式，可以有一个值。\n\nWhen the next() method of a generator is invoked, the generator function runs until it reaches a yield expression. The expression that follows the yield keyword is evaluated, and that value becomes the return value of the next() invocation. At this point, the generator function stops executing right in the middle of evaluating the yield expression. The next time the next() method of the generator is called, the argument passed to next() becomes the value of the yield expression that was paused. So the generator returns values to its caller with yield, and the caller passes values in to the generator with next(). The generator and caller are two separate streams of execution passing values (and control) back and forth. The following code illustrates:\n\n> 调用生成器的 next() 方法时，生成器函数将运行直至到达 yield 表达式。将评估 yield 关键字之后的表达式，该值将成为 next() 调用的返回值。此时，生成器函数在评估 yield 表达式的中间立即停止执行。下次调用生成器的 next() 方法时，传递给 next() 的参数成为已暂停的 yield 表达式的值。因此，生成器将把 yield 的值返回给它的调用者，然后调用者通过 next() 将值传递给生成器。生成器和调用者是两个独立的执行流，来回传递值（和控制）。以下代码说明： \n\n```js\nfunction* smallNumbers() {\n    console.log(\"next() invoked the first time; argument discarded\");\n    let y1 = yield 1;    // y1 == \"b\"\n    console.log(\"next() invoked a second time with argument\", y1);\n    let y2 = yield 2;    // y2 == \"c\"\n    console.log(\"next() invoked a third time with argument\", y2);\n    let y3 = yield 3;    // y3 == \"d\"\n    console.log(\"next() invoked a fourth time with argument\", y3);\n    return 4;\n}\n\nlet g = smallNumbers();\nconsole.log(\"generator created; no code runs yet\");\nlet n1 = g.next(\"a\");   // n1.value == 1\nconsole.log(\"generator yielded\", n1.value);\nlet n2 = g.next(\"b\");   // n2.value == 2\nconsole.log(\"generator yielded\", n2.value);\nlet n3 = g.next(\"c\");   // n3.value == 3\nconsole.log(\"generator yielded\", n3.value);\nlet n4 = g.next(\"d\");   // n4 == { value: 4, done: true }\nconsole.log(\"generator returned\", n4.value);\n```\nWhen this code runs, it produces the following output that demonstrates the back-and-forth between the two blocks of code:\n\n> 此代码运行时，将产生以下输出，演示两个代码块之间的来回交互：\n\n```\ngenerator created; no code runs yet\nnext() invoked the first time; argument discarded\ngenerator yielded 1\nnext() invoked a second time with argument b\ngenerator yielded 2\nnext() invoked a third time with argument c\ngenerator yielded 3\nnext() invoked a fourth time with argument d\ngenerator returned 4\n```\nNote the asymmetry in this code. The first invocation of next() starts the generator, but the value passed to that invocation is not accessible to the generator.\n\n> 注意此代码中的不对称性。next() 的首次调用将启动生成器，但是生成器无法访问传递给该调用的值。\n\n### 12.4.3 The return() and throw() Methods of a Generator\nWe’ve seen that you can receive values yielded by or returned by a generator function. And you can pass values to a running generator by passing those values when you call the next() method of the generator.\n\n> 我们已经看到可以接收生成器函数产生或返回的值。可以在调用生成器的 next() 方法时将值传递给正在运行的生成器。\n\nIn addition to providing input to a generator with next(), you can also alter the flow of control inside the generator by calling its return() and throw() methods. As the names suggest, calling these methods on a generator causes it to return a value or throw an exception as if the next statement in the generator was a return or throw.\n\n> 除了使用 next() 向生成器提供输入之外，还可以通过调用生成器的 return() 和 throw() 方法来更改生成器内部的控制流。顾名思义，在生成器上调用这些方法会导致其返回值或引发异常，就像生成器中的下一条语句是 return 或 throw 一样。\n\nRecall from earlier in the chapter that, if an iterator defines a return() method and iteration stops early, then the interpreter automatically calls the return() method to give the iterator a chance to close files or do other cleanup. In the case of generators, you can’t define a custom return() method to handle cleanup, but you can structure the generator code to use a try/finally statement that ensures the necessary cleanup is done (in the finally block) when the generator returns. By forcing the generator to return, the generator’s built-in return() method ensures that the cleanup code is run when the generator will no longer be used.\n\n> 从本章前面的内容回想起，如果迭代器定义了 return() 方法且迭代提早停止，则解释器将自动调用 return() 方法，以使迭代器有机会关闭文件或进行其他清理。对于生成器，不能定义自定义的 return() 方法来处理清理，但是可以构造生成器代码以使用 try/finally 语句，以确保生成器返回时执行清理操作（在 finally 块中）。通过强制生成器返回，生成器的内置 return() 方法可确保在不再使用生成器时运行清除代码。\n\nJust as the next() method of a generator allows us to pass arbitrary values into a running generator, the throw() method of a generator gives us a way to send arbitrary signals (in the form of exceptions) into a generator. Calling the throw() method always causes an exception inside the generator. But if the generator function is written with appropriate exception-handling code, the exception need not be fatal but can instead be a means of altering the behavior of the generator. Imagine, for example, a counter generator that yields an ever-increasing sequence of integers. This could be written so that an exception sent with throw() would reset the counter to zero.\n\n> 正如生成器的 next() 方法允许我们将任意值传递给正在运行的生成器一样，生成器的 throw() 方法为我们提供了一种将任意信号（以异常形式）发送到生成器的方法。调用 throw() 方法总是会在生成器内部引起异常。但是，如果生成器函数有适当的异常处理代码编，则该异常是致命的，不过这可以用作更改生成器行为的一种手段。例如，想象一下产生一个不断增加的整数序列的计数器生成器。可以这样编写，使用 throw() 发送的异常将计数器重置为零。\n\nWhen a generator uses yield* to yield values from some other iterable object, then a call to the next() method of the generator causes a call to the next() method of the iterable object. The same is true of the return() and throw() methods. If a generator uses yield* on an iterable object that has these methods defined, then calling return() or throw() on the generator causes the iterator’s return() or throw() method to be called in turn. All iterators must have a next() method. Iterators that need to clean up after incomplete iteration should define a return() method. And any iterator may define a throw() method, though I don’t know of any practical reason to do so.\n\n> 当生成器使用 yield* 从其他可迭代对象生成值时，对生成器的 next() 方法的调用会导致对可迭代对象的 next() 方法的调用。return() 和 throw() 方法也是如此。 如果生成器在定义了这些方法的可迭代对象上使用 yield*，则在生成器上调用 return() 或 throw() 会导致依次调用迭代器的 return() 或 throw() 方法。所有迭代器都必须具有 next() 方法。需要在不完整的迭代后进行清理的迭代器应定义一个 return() 方法。而且，任何迭代器都可以定义 throw() 方法，尽管我不知道有任何实际原因。\n\n### 12.4.4 A Final Note About Generators\nGenerators are a very powerful generalized control structure. They give us the ability to pause a computation with yield and restart it again at some arbitrary later time with an arbitrary input value. It is possible to use generators to create a kind of cooperative threading system within single-threaded JavaScript code. And it is possible to use generators to mask asynchronous parts of your program so that your code appears sequential and synchronous, even though some of your function calls are actually asynchronous and depend on events from the network.\n\n> 生成器是一个非常强大的通用控制结构。它们使我们能够使用 yield 暂停计算，并在以后任意任意时间使用任意输入值重新开始计算。可以使用生成器在单线程 JavaScript 代码中创建一种协作线程系统。而且，即使某些函数调用实际上是异步的并且依赖于网络事件，也可以使用生成器来掩盖程序的异步部分，从而使代码显得顺序和同步。\n\nTrying to do these things with generators leads to code that is mind-bendingly hard to understand or to explain. It has been done, however, and the only really practical use case has been for managing asynchronous code. JavaScript now has async and await keywords (see Chapter 13) for this very purpose, however, and there is no longer any reason to abuse generators in this way.\n\n> 尝试使用生成器执行这些操作会导致代码难以理解或解释。但是，它已经成为了过去时，唯一真正实用的用例是管理异步代码。为此，JavaScript 现在具有 async 和 await 关键字（请参阅第 13 章），并且不再有任何理由以这种方式滥用生成器。\n\n## 12.5 Summary\nIn this chapter, you have learned:\n\n> 在本章中，您学习了：\n\nThe for/of loop and the ... spread operator work with iterable objects.\n\n> for/of 循环和 ... 展开运算符可迭代对象。\n\nAn object is iterable if it has a method with the symbolic name [Symbol.iterator] that returns an iterator object.\n\n> 如果对象具有符号名称为 [Symbol.iterator] 的方法，则该方法返回迭代器对象，该对象是可迭代的。\n\nAn iterator object has a next() method that returns an iteration result object.\n\n> 迭代器对象具有 next() 方法，该方法返回迭代结果对象。\n\nAn iteration result object has a value property that holds the next iterated value, if there is one. If the iteration has completed, then the result object must have a done property set to true.\n\n> 迭代结果对象具有一个 value 属性，该属性保存下一个迭代值（如果有）。如果迭代已完成，则结果对象必须将 done 属性设置为 true。 \n\nYou can implement your own iterable objects by defining a \\[Symbol.iterator]() method that returns an object with a next() method that returns iteration result objects. You can also implement functions that accept iterator arguments and return iterator values.\n\n> 可以通过定义返回对象的 \\[Symbol.iterator]() 方法和返回迭代结果对象的 next() 方法来实现自己的可迭代对象。还可以实现接受迭代器参数并返回迭代器值的函数。 \n\nGenerator functions (functions defined with function* instead of function) are another way to define iterators.\n\n> 生成器函数（用 function* 代替 function 定义的函数）是定义迭代器的另一种方法。\n\nWhen you invoke a generator function, the body of the function does not run right away; instead, the return value is an iterable iterator object. Each time the next() method of the iterator is called, another chunk of the generator function runs.\n\n> 当调用生成器函数时，该函数的主体不会立即运行。相反，返回值是一个可迭代的迭代器对象。每次调用迭代器的 next() 方法时，都会运行另一部分生成器函数。\n\nGenerator functions can use the yield operator to specify the values that are returned by the iterator. Each call to next() causes the generator function to run up to the next yield expression. The value of that yield expression then becomes the value returned by the iterator. When there are no more yield expressions, then the generator function returns, and the iteration is complete.\n\n> 生成器函数可以使用 yield 运算符来指定迭代器返回的值。每次调用 next() 都会使生成器函数运行到下一个 yield 表达式。然后，该 yield 表达式的值将成为迭代器返回的值。当没有更多的 yield 表达式时，生成器函数将返回，并且迭代完成。 "
  },
  {
    "path": "content/posts/ch13.md",
    "content": "---\ntitle: \"第 13 章 异步 JavaScript\"\ndate: 2020-11-02T22:18:30+08:00\n---\n\nSome computer programs, such as scientific simulations and machine learning models, are compute-bound: they run continuously, without pause, until they have computed their result. Most real-world computer programs, however, are significantly asynchronous. This means that they often have to stop computing while waiting for data to arrive or for some event to occur. JavaScript programs in a web browser are typically event-driven, meaning that they wait for the user to click or tap before they actually do anything. And JavaScript-based servers typically wait for client requests to arrive over the network before they do anything.\n\n> 一些计算机程序，如科学模拟和机器学习模型，是计算受限的：它们不停地运行，没有停顿，直到计算出结果。然而，值得注意的是现实世界中的大多数计算机程序都是异步的。这意味着在等待数据到达或某些事件发生时，它们常常不得不停止计算。web 浏览器中的 JavaScript 程序是典型地事件驱动的，这意味着它们在实际执行任何操作之前等待用户单击或点击。基于 javascript 的服务器通常在执行任何操作之前等待客户机请求通过网络到达。\n\nThis kind of asynchronous programming is commonplace in JavaScript, and this chapter documents three important language features that help make it easier to work with asynchronous code. Promises, new in ES6, are objects that represent the not-yet-available result of an asynchronous operation. The keywords async and await were introduced in ES2017 and provide new syntax that simplifies asynchronous programming by allowing you to structure your Promise-based code as if it was synchronous. Finally, asynchronous iterators and the for/await loop were introduced in ES2018 and allow you to work with streams of asynchronous events using simple loops that appear synchronous.\n\n> 这种异步编程在 JavaScript 中很常见，本章将介绍三种重要的语言特性，它们有助于简化异步代码的使用。Promise 是 ES6 中的新特性，是表示目前不可用结果的异步操作对象。关键字 async 和 await 是在 ES2017 中引入的，它们提供了新的语法，通过允许将基于 Promise 的代码构造成同步的方式来简化异步编程。最后，在 ES2018 中引入了异步迭代器和 for/await 循环，允许使用简单的同步循环处理异步事件流。\n\nIronically, even though JavaScript provides these powerful features for working with asynchronous code, there are no features of the core language that are themselves asynchronous. In order to demonstrate Promises, async, await, and for/await, therefore, we will first take a detour into client-side and server-side JavaScript to explain some of the asynchronous features of web browsers and Node. (You can learn more about client-side and server-side JavaScript in Chapters 15 and 16.)\n\n> 具有讽刺意味的是，尽管 JavaScript 为处理异步代码提供了这些强大的特性，但核心语言本身并没有异步的特性。因此，为了演示 Promise、async、await 和 for/await，我们将首先使用客户端和服务器端 JavaScript 来解释 web 浏览器和 Node 的一些异步特性。（可以在第 15 章和第 16 章中了解更多关于客户端和服务器端 JavaScript 的知识。）\n\n## 13.1 Asynchronous Programming with Callbacks\n\nAt its most fundamental level, asynchronous programming in JavaScript is done with callbacks. A callback is a function that you write and then pass to some other function. That other function then invokes (“calls back”) your function when some condition is met or some (asynchronous) event occurs. The invocation of the callback function you provide notifies you of the condition or event, and sometimes, the invocation will include function arguments that provide additional details. This is easier to understand with some concrete examples, and the subsections that follow demonstrate various forms of callback-based asynchronous programming using both client-side JavaScript and Node.\n\n> 在最基本的层次上，JavaScript 中的异步编程是通过回调来完成的。回调是一个你编写的函数，然后传递给其他函数。当满足某些条件或发生某些（异步）事件时，其他函数调用（“回调”）你的函数。提供的回调函数的调用会通知你条件或事件，有时，调用将提供包含额外细节的函数实参。通过一些具体的示例会更容易理解，下面的子节将演示使用客户端 JavaScript 和 Node 的各种形式的基于回调的异步编程。\n\n### 13.1.1 Timers\n\nOne of the simplest kinds of asynchrony is when you want to run some code after a certain amount of time has elapsed. As we saw in §11.10, you can do this with the setTimeout() function:\n\n> 当希望在经过一定时间后运行某些代码是一种最简单的异步类型。正如我们在 §11.10 中看到的，可以通过 setTimeout() 函数来做到这一点：\n\n```js\nsetTimeout(checkForUpdates, 60000);\n```\n\nThe first argument to setTimeout() is a function and the second is a time interval measured in milliseconds. In the preceding code, a hypothetical checkForUpdates() function will be called 60,000 milliseconds (1 minute) after the setTimeout() call. checkForUpdates() is a callback function that your program might define, and setTimeout() is the function that you invoke to register your callback function and specify under what asynchronous conditions it should be invoked.\n\n> setTimeout() 的第一个实参是一个函数，第二个实参是一个以毫秒为单位的时间间隔。前面的代码中，在 setTimeout() 调用后 60,000 毫秒（1分钟）后，将调用一个假定的 checkForUpdates() 函数。checkForUpdates() 是程序定义的一个回调函数，而 setTimeout() 是用于注册回调函数并指定应该在什么异步条件下调用它的函数。\n\nsetTimeout() calls the specified callback function one time, passing no arguments, and then forgets about it. If you are writing a function that really does check for updates, you probably want it to run repeatedly. You can do this by using setInterval() instead of setTimeout():\n\n> setTimeout() 调用一次指定的回调函数，不传递任何实参，然后忘记它。如果正在编写一个检查更新的函数，可能希望它重复运行。可以使用 setInterval() 来代替 setTimeout()：\n\n```js\n// Call checkForUpdates in one minute and then again every minute after that\nlet updateIntervalId = setInterval(checkForUpdates, 60000);\n\n// setInterval() returns a value that we can use to stop the repeated\n// invocations by calling clearInterval(). (Similarly, setTimeout()\n// returns a value that you can pass to clearTimeout())\nfunction stopCheckingForUpdates() {\n    clearInterval(updateIntervalId);\n}\n```\n\n### 13.1.2 Events\n\nClient-side JavaScript programs are almost universally event driven: rather than running some kind of predetermined computation, they typically wait for the user to do something and then respond to the user’s actions. The web browser generates an event when the user presses a key on the keyboard, moves the mouse, clicks a mouse button, or touches a touchscreen device. Event-driven JavaScript programs register callback functions for specified types of events in specified contexts, and the web browser invokes those functions whenever the specified events occur. These callback functions are called event handlers or event listeners, and they are registered with addEventListener():\n\n> 客户端 JavaScript 程序几乎都是由事件驱动的：它们通常不等待用户执行某种预定的计算，而是等待用户执行某些操作，然后响应用户的操作。当用户按下键盘上的键，移动鼠标，单击鼠标按钮或触摸触摸屏设备时，Web 浏览器会发生事件。事件驱动的 JavaScript 程序在指定的上下文中为指定类型的事件注册回调函数，并且只要指定事件发生，Web 浏览器就会调用这些函数。这些回调函数称为事件句柄或事件监听器，并且使用 addEventListener() 注册：\n\n```js\n// Ask the web browser to return an object representing the HTML\n// <button> element that matches this CSS selector\nlet okay = document.querySelector('#confirmUpdateDialog button.okay');\n\n// Now register a callback function to be invoked when the user\n// clicks on that button.\nokay.addEventListener('click', applyUpdate);\n```\nIn this example, applyUpdate() is a hypothetical callback function that we assume is implemented somewhere else. The call to document.querySelector() returns an object that represents a single specified element in the web page. We call addEventListener() on that element to register our callback. Then the first argument to addEventListener() is a string that specifies the kind of event we’re interested in—a mouse click or touchscreen tap, in this case. If the user clicks or taps on that specific element of the web page, then the browser will invoke our applyUpdate() callback function, passing an object that includes details (such as the time and the mouse pointer coordinates) about the event.\n\n> 在此示例中，假设 applyUpdate() 是我们在某个地方实现的回调函数。调用 document.querySelector() 返回一个对象，该对象表示网页中的单个指定元素。我们在该元素上调用 addEventListener() 来注册我们的回调。然后，addEventListener() 的第一个实参是一个字符串，该字符串指定了事件的类型（在这种情况下是单击鼠标或触摸屏）。如果用户单击或点击网页上的特定元素，则浏览器将调用我们的 applyUpdate() 回调函数，并传递一个包含事件详细信息（例如时间和鼠标指针坐标）的对象。\n\n### 13.1.3 Network Events\nAnother common source of asynchrony in JavaScript programming is network requests. JavaScript running in the browser can fetch data from a web server with code like this:\n\n> JavaScript 编程中异步的另一个常见来源是网络请求。在浏览器中运行的 JavaScript 可以使用以下代码从 Web 服务器获取数据：\n\n```js\nfunction getCurrentVersionNumber(versionCallback) { // Note callback argument\n    // Make a scripted HTTP request to a backend version API\n    let request = new XMLHttpRequest();\n    request.open(\"GET\", \"http://www.example.com/api/version\");\n    request.send();\n\n    // Register a callback that will be invoked when the response arrives\n    request.onload = function() {\n        if (request.status === 200) {\n            // If HTTP status is good, get version number and call callback.\n            let currentVersion = parseFloat(request.responseText);\n            versionCallback(null, currentVersion);\n        } else {\n            // Otherwise report an error to the callback\n            versionCallback(response.statusText, null);\n        }\n    };\n    // Register another callback that will be invoked for network errors\n    request.onerror = request.ontimeout = function(e) {\n        versionCallback(e.type, null);\n    };\n}\n```\nClient-side JavaScript code can use the XMLHttpRequest class plus callback functions to make HTTP requests and asynchronously handle the server’s response when it arrives.[^1] The getCurrentVersionNumber() function defined here (we can imagine that it is used by the hypothetical checkForUpdates() function we discussed in §13.1.1) makes an HTTP request and defines event handlers that will be invoked when the server’s response is received or when a timeout or other error causes the request to fail.\n\n> 客户端 JavaScript 代码可以使用 XMLHttpRequest 类以及回调函数来发出 HTTP 请求，并在服务器响应时异步处理。[^1] 这里定义的 getCurrentVersionNumber() 函数（我们可以假设在 §13.1.1 提到的 checkForUpdates() 函数使用了该函数）发出 HTTP 请求并定义事件处理程序，该事件处理程序将在收到服务器的响应或超时或其他异常导致请求失败时被调用。\n\nNotice that the code example above does not call addEventListener() as our previous example did. For most web APIs (including this one), event handlers can be defined by invoking addEventListener() on the object generating the event and passing the name of the event of interest along with the callback function. Typically, though, you can also register a single event listener by assigning it directly to a property of the object. That is what we do in this example code, assigning functions to the onload, onerror, and ontimeout properties. By convention, event listener properties like these always have names that begin with on. addEventListener() is the more flexible technique because it allows for multiple event handlers. But in cases where you are sure that no other code will need to register a listener for the same object and event type, it can be simpler to simply set the appropriate property to your callback.\n\n> 请注意，上面的代码示例未像前面的示例那样调用 addEventListener()。对于大多数 Web API（包括此API），可以通过在生成事件的对象上调用 addEventListener() 并将事件的名称与回调函数一起传递来定义事件处理程序。不过，通常，也可以通过将单个事件侦听器直接分配给对象的属性来注册它。这就是我们在此示例代码中所做的，将函数分配给 onload、onerror 和 ontimeout 属性。按照惯例，此类事件侦听器属性的名称始终以 on 开头。 addEventListener() 是更灵活的技术，因为它允许多个事件处理程序。但是，如果确定没有其他代码需要为相同的对象和事件类型注册一个侦听器，则只需将适当的属性设置为回调会更简单。\n\nAnother thing to note about the getCurrentVersionNumber() function in this example code is that, because it makes an asynchronous request, it cannot synchronously return the value (the current version number) that the caller is interested in. Instead, the caller passes a callback function, which is invoked when the result is ready or when an error occurs. In this case, the caller supplies a callback function that expects two arguments. If the XMLHttpRequest works correctly, then getCurrentVersionNumber() invokes the callback with a null first argument and the version number as the second argument. Or, if an error occurs, then getCurrentVersionNumber() invokes the callback with error details in the first argument and null as the second argument.\n\n> 此示例代码中关于 getCurrentVersionNumber() 函数的另一点注意事项是，由于它发出异步请求，因此无法同步返回调用者感兴趣的值（当前版本号）。相反，调用者传递了一个回调函数，当结果准备就绪或发生异常时调用。在这种情况下，调用方提供了一个回调函数，该函数需要两个参数。如果 XMLHttpRequest 正常工作，则 getCurrentVersionNumber() 会使用 null 为第一个实参和版本号为第二个实参调用回调函数。或者，如果发生异常，则 getCurrentVersionNumber() 会在第一个实参中带有异常详细信息，而在第二个参数中使用 null。\n\n### 13.1.4 Callbacks and Events in Node\nThe Node.js server-side JavaScript environment is deeply asynchronous and defines many APIs that use callbacks and events. The default API for reading the contents of a file, for example, is asynchronous and invokes a callback function when the contents of the file have been read:\n```js\nconst fs = require(\"fs\"); // The \"fs\" module has filesystem-related APIs\nlet options = {           // An object to hold options for our program\n    // default options would go here\n};\n\n// Read a configuration file, then call the callback function\nfs.readFile(\"config.json\", \"utf-8\", (err, text) => {\n    if (err) {\n        // If there was an error, display a warning, but continue\n        console.warn(\"Could not read config file:\", err);\n    } else {\n        // Otherwise, parse the file contents and assign to the options object\n        Object.assign(options, JSON.parse(text));\n    }\n\n    // In either case, we can now start running the program\n    startProgram(options);\n});\n```\nNode’s fs.readFile() function takes a two-parameter callback as its last argument. It reads the specified file asynchronously and then invokes the callback. If the file was read successfully, it passes the file contents as the second callback argument. If there was an error, it passes the error as the first callback argument. In this example, we express the callback as an arrow function, which is a succinct and natural syntax for this kind of simple operation.\n\nNode also defines a number of event-based APIs. The following function shows how to make an HTTP request for the contents of a URL in Node. It has two layers of asynchronous code handled with event listeners. Notice that Node uses an on() method to register event listeners instead of addEventListener():\n```js\nconst https = require(\"https\");\n\n// Read the text content of the URL and asynchronously pass it to the callback.\nfunction getText(url, callback) {\n    // Start an HTTP GET request for the URL\n    request = https.get(url);\n\n    // Register a function to handle the \"response\" event.\n    request.on(\"response\", response => {\n        // The response event means that response headers have been received\n        let httpStatus = response.statusCode;\n\n        // The body of the HTTP response has not been received yet.\n        // So we register more event handlers to to be called when it arrives.\n        response.setEncoding(\"utf-8\");  // We're expecting Unicode text\n        let body = \"\";                  // which we will accumulate here.\n\n        // This event handler is called when a chunk of the body is ready\n        response.on(\"data\", chunk => { body += chunk; });\n\n        // This event handler is called when the response is complete\n        response.on(\"end\", () => {\n            if (httpStatus === 200) {   // If the HTTP response was good\n                callback(null, body);   // Pass response body to the callback\n            } else {                    // Otherwise pass an error\n                callback(httpStatus, null);\n            }\n        });\n    });\n\n    // We also register an event handler for lower-level network errors\n    request.on(\"error\", (err) => {\n        callback(err, null);\n    });\n}\n```\n## 13.2 Promises\nNow that we’ve seen examples of callback and event-based asynchronous programming in client-side and server-side JavaScript environments, we can introduce Promises, a core language feature designed to simplify asynchronous programming.\n\n> 现在，我们已经见过了客户端和服务器端 JavaScript 环境中基于回调和基于事件的异步编程的示例，我们接着介绍 Promise，这是一种旨在简化异步编程的核心语言特性。\n\nA Promise is an object that represents the result of an asynchronous computation. That result may or may not be ready yet, and the Promise API is intentionally vague about this: there is no way to synchronously get the value of a Promise; you can only ask the Promise to call a callback function when the value is ready. If you are defining an asynchronous API like the getText() function in the previous section, but want to make it Promise-based, omit the callback argument, and instead return a Promise object. The caller can then register one or more callbacks on this Promise object, and they will be invoked when the asynchronous computation is done.\n\n> Promise 是描述异步计算结果的对象。该结果可能准备好或尚未准备好，Promise API 故意对此含糊其词：无法同步获取 Promise 的值；只能要求 promise 在值准备好时调用回调函数。如果要像上一节中的 getText() 函数那样定义异步 API，但想使其基于 Promise，则省略 callback 参数，而返回 Promise 对象。然后，调用者可以在此 Promise 对象上注册一个或多个回调，并且在异步计算完成后将调用它们。\n\nSo, at the simplest level, Promises are just a different way of working with callbacks. However, there are practical benefits to using them. One real problem with callback-based asynchronous programming is that it is common to end up with callbacks inside callbacks inside callbacks, with lines of code so highly indented that it is difficult to read. Promises allow this kind of nested callback to be re-expressed as a more linear Promise chain that tends to be easier to read and easier to reason about.\n\n> 因此，最简单的说，promise 只是使用回调的另一种方式。但是，使用它有实际的好处。基于回调的异步编程的一个真正的问题是，通常在回调内部嵌套多层回调，并且代码行缩进程度很高，以至于很难阅读。Promise 允许将这种嵌套的回调作为更线性的 Promise 链重新表达，该链往往更易于阅读和推理。\n\nAnother problem with callbacks is that they can make handling errors difficult. If an asynchronous function (or an asynchronously invoked callback) throws an exception, there is no way for that exception to propagate back to the initiator of the asynchronous operation. This is a fundamental fact about asynchronous programming: it breaks exception handling. The alternative is to meticulously track and propagate errors with callback arguments and return values, but this is tedious and difficult to get right. Promises help here by standardizing a way to handle errors and providing a way for errors to propagate correctly through a chain of promises.\n\n> 回调的另一个问题是，它们会使处理异常变得困难。如果异步函数（或异步调用的回调）引发异常，则该异常无法传播回异步操作的发起者。这是关于异步编程的基本事实：它破坏了异常处理。替代方法是使用回调实参和返回值来精心跟踪和传播异常，但这很繁琐且难以正确处理。Promise 通过标准化处理异常的方式以及为异常通过 Promise 链正确传播的方式提供帮助。\n\nNote that Promises represent the future results of single asynchronous computations. They cannot be used to represent repeated asynchronous computations, however. Later in this chapter, we’ll write a Promise-based alternative to the setTimeout() function, for example. But we can’t use Promises to replace setInterval() because that function invokes a callback function repeatedly, which is something that Promises are just not designed to do. Similarly, we could use a Promise instead of the “load” event handler of an XMLHttpRequest object, since that callback is only ever called once. But we typically would not use a Promise in place of a “click” event handler of an HTML button object, since we normally want to allow the user to click a button multiple times.\n\n> 请注意，promise 表示单个异步计算的未来结果。但是，它不能用于表示重复的异步计算。例如，在本章的后面，我们将写一个基于 Promise 的 setTieout() 函数替代方法。但是，我们不能使用 Promise 来代替 setInterval()，因为该函数会反复调用回调函数，而 Promise 并不是。同样，我们可以使用 Promise 代替 XMLHttpRequest 对象的“load”事件处理程序，因为该回调仅被调用一次。但是我们通常不会使用 Promise 来代替 HTML 按钮对象的“click”事件处理程序，因为我们通常希望允许用户多次单击按钮。\n\nThe subsections that follow will:\n> 接下来的小节将：\n\n- Explain Promise terminology and show basic Promise usage\n- Show how promises can be chained\n- Demonstrate how to create your own Promise-based APIs\n\n> - 解释 Promise 术语并演示 Promise 的基本用法\n> - 展示如何将 Promise 链接起来\n> - 演示如何创建自己的基于 Promise 的 API\n\n#### IMPORTANT\nPromises seem simple at first, and the basic use case for Promises is, in fact, straightforward and simple. But they can become surprisingly confusing for anything beyond the simplest use cases. Promises are a powerful idiom for asynchronous programming, but you need to understand them deeply to use them correctly and confidently. It is worth taking the time to develop that deep understanding, however, and I urge you to study this long chapter carefully.\n\n> Promise 一开始似乎很简单，并且 Promise 的基本用例实际上也是简单明了的。但是，除了最简单的用例之外，它们还会使其他任何事情变得令人困惑。对于异步编程，Promise 是一个强大的习惯用法，但是需要深刻理解它们，才能正确、自信地使用它们。但是，它值得花时间来深入理解，我建议仔细阅读这一长篇章。\n\n### 13.2.1 Using Promises\nWith the advent of Promises in the core JavaScript language, web browsers have begun to implement Promise-based APIs. In the previous section, we implemented a getText() function that made an asynchronous HTTP request and passed the body of the HTTP response to a specified callback function as a string. Imagine a variant of this function, getJSON(), which parses the body of the HTTP response as JSON and returns a Promise instead of accepting a callback argument. We will implement a getJSON() function later in this chapter, but for now, let’s look at how we would use this Promise-returning utility function:\n\n> 随着核心 JavaScript 语言中 Promise 的出现，Web 浏览器已经开始实现基于 Promise 的 API。在上一节中，我们实现了一个 getText() 函数，该函数发出一个异步 HTTP 请求，并将 HTTP 响应的主体作为字符串传递给指定的回调函数。想象一下该函数的一个变体 getJSON()，它可以解析主体 HTTP 响应的形式为 JSON，并返回 Promise 而不是接受回调参数。我们将在本章稍后实现 getJSON() 函数，但现在，让我们看一下如何使用返回 Promise 功能程序函数：\n\n```js\ngetJSON(url).then(jsonData => {\n    // This is a callback function that will be asynchronously\n    // invoked with the parsed JSON value when it becomes available.\n});\n```\ngetJSON() starts an asynchronous HTTP request for the URL you specify and then, while that request is pending, it returns a Promise object. The Promise object defines a then() instance method. Instead of passing our callback function directly to getJSON(), we instead pass it to the then() method. When the HTTP response arrives, the body of that response is parsed as JSON, and the resulting parsed value is passed to the function that we passed to then().\n\n> getJSON() 对指定的 URL 启动一个异步 HTTP 请求，当该请求待定时，它返回一个 Promise 对象。Promise 对象定义了 then() 实例方法。我们没有将回调函数直接传递给 getJSON()，而是将其传递给 then() 方法。当 HTTP 响应时，该响应的主体将解析为 JSON，并将所解析的结果值传给我们传递给 then() 的回调函数。\n\nYou can think of the then() method as a callback registration method like the addEventListener() method used for registering event handlers in client-side JavaScript. If you call the then() method of a Promise object multiple times, each of the functions you specify will be called when the promised computation is complete.\n\n> 可以将 then() 方法视为回调注册方法，例如用于在客户端 JavaScript 中注册事件处理程序的 addEventListener() 方法。如果多次调用 Promise 对象的 then() 方法，则在完成 promise 的计算后将调用指定的每个函数。\n\nUnlike many event listeners, though, a Promise represents a single computation, and each function registered with then() will be invoked only once. It is worth noting that the function you pass to then() is invoked asynchronously, even if the asynchronous computation is already complete when you call then().\n\n> 但是，与许多事件侦听器不同，Promise 表示单个计算，并且 then() 注册的每个函数仅被调用一次。值得注意的是，传递给 then() 的函数是异步调用的，即使调用 then() 时异步计算已经完成。\n\nAt a simple syntactical level, the then() method is the distinctive feature of Promises, and it is idiomatic to append .then() directly to the function invocation that returns the Promise, without the intermediate step of assigning the Promise object to a variable.\n\n> 在简单的语法级别上，then() 方法是 Promise 独有的特性，编码中习惯于将 .then() 直接跟随返回 Promise 的函数，而无需将 Promise 对象分配给变量的中间步骤。\n\nIt is also idiomatic to name functions that return Promises and functions that use the results of Promises with verbs, and these idioms lead to code that is particularly easy to read:\n\n> 常用带有动词命名返回 Promise 的函数和使用 Promise 结果的函数，这些常用语使代码特别容易阅读：\n\n```js\n// Suppose you have a function like this to display a user profile\nfunction displayUserProfile(profile) { /* implementation omitted */ }\n\n// Here's how you might use that function with a Promise.\n// Notice how this line of code reads almost like an English sentence:\ngetJSON(\"/api/user/profile\").then(displayUserProfile);\n```\n#### HANDLING ERRORS WITH PROMISES\nAsynchronous operations, particularly those that involve networking, can typically fail in a number of ways, and robust code has to be written to handle the errors that will inevitably occur.\n\n> 异步操作，尤其是涉及网络的异步操作，通常会以多种方式失败，并且必须编写健壮的代码来处理不可避免地会发生的异常。\n\nFor Promises, we can do this by passing a second function to the then() method:\n\n> 对于 Promise，我们可以通过将第二个函数传递给 then() 方法来实现：\n\n```js\ngetJSON(\"/api/user/profile\").then(displayUserProfile, handleProfileError);\n```\nA Promise represents the future result of an asynchronous computation that occurs after the Promise object is created. Because the computation is performed after the Promise object is returned to us, there is no way that the computation can traditionally return a value or throw an exception that we can catch. The functions that we pass to then() provide alternatives. When a synchronous computation completes normally, it simply returns its result to its caller. When a Promise-based asynchronous computation completes normally, it passes its result to the function that is the first argument to then().\n\n> Promise 描述在 Promise 对象创建之后发生的异步计算的未来结果。由于计算是在 Promise 对象返回给我们之后执行的，因此该计算无法传统地返回值或引发我们可以捕获的异常。我们传递给 then() 的函数提供了替代方案。当同步计算正常完成时，它仅将其结果返回给其调用者。当基于 Promise 的异步计算正常完成时，它将其结果传递给 then() 的第一个实参函数。\n\nWhen something goes wrong in a synchronous computation, it throws an exception that propagates up the call stack until there is a catch clause to handle it. When an asynchronous computation runs, its caller is no longer on the stack, so if something goes wrong, it is simply not possible to throw an exception back to the caller.\n\n> 当同步计算中出现问题时，它将引发一个异常，该异常会沿调用堆栈传播，直到有一个 catch 子句来处理它为止。当异步计算运行时，它的调用者不再在堆栈上，因此，如果出现问题，则根本不可能将异常抛出给调用者。\n\nInstead, Promise-based asynchronous computations pass the exception (typically as an Error object of some kind, though this is not required) to the second function passed to then(). So, in the code above, if getJSON() runs normally, it passes its result to displayUserProfile(). If there is an error (the user is not logged in, the server is down, the user’s internet connection dropped, the request timed out, etc.), then getJSON() passes an Error object to handleProfileError().\n\n> 而基于 Promise 的异步计算将异常（通常是某种 Error 对象，尽管这不是必需的）传递给 then() 的第二个函数。因此，在上面的代码中，如果 getJSON() 正常运行，它将其结果传递给 displayUserProfile()。如果出现异常（用户未登录，服务器关闭，用户的 Internet 连接断开，请求超时等），则 getJSON() 会将 Error 对象传递给 handleProfileError()。\n\nIn practice, it is rare to see two functions passed to then(). There is a better and more idiomatic way of handling errors when working with Promises. To understand it, first consider what happens if getJSON() completes normally but an error occurs in displayUserProfile(). That callback function is invoked asynchronously when getJSON() returns, so it is also asynchronous and cannot meaningfully throw an exception (because there is no code on the call stack to handle it).\n\n> 实践中，很少有两个函数传递给 then()。在处理 Promise 时，有一种更好更常用的异常处理方式。为了理解它，首先考虑如果 getJSON() 正常完成但 displayUserProfile() 中发生异常该怎么办。当 getJSON() 返回时，该回调函数将异步调用，因此它也是异步的，并且无法有意义地引发异常（因为调用堆栈上没有代码可以处理该异常）。\n\nThe more idiomatic way to handle errors in this code looks like this:\n\n> 如下所示，处理此代码中异常的更常用方式：\n\n```js\ngetJSON(\"/api/user/profile\").then(displayUserProfile).catch(handleProfileError);\n```\nWith this code, a normal result from getJSON() is still passed to displayUserProfile(), but any error in getJSON() or in displayUserProfile() (including any exceptions thrown by displayUserProfile) get passed to handleProfileError(). The catch() method is just a shorthand for calling then() with a null first argument and the specified error handler function as the second argument.\n\n> 使用此代码，getJSON() 的正常结果仍会传递给 displayUserProfile()，但是 getJSON() 或 displayUserProfile() 中的任何异常（包括 displayUserProfile 抛出的任何异常）都将传递给 handleProfileError()。调用 then() 第一个实参为空，指定的异常处理函数为第二个实参，catch() 方法只是其简写。\n\nWe’ll have more to say about catch() and this error-handling idiom when we discuss Promise chains in the next section.\n\n> 在下一节中讨论 Promise 链时，我们将对 catch() 和这个处理异常常用方法作更多的说明。\n\n#### PROMISE TERMINOLOGY\nBefore we discuss Promises further, it is worth pausing to define some terms. When we are not programming and we talk about human promises, we say that a promise is “kept” or “broken.” When discussing JavaScript Promises, the equivalent terms are “fulfilled” and “rejected.” Imagine that you have called the then() method of a Promise and have passed two callback functions to it. We say that the promise has been fulfilled if and when the first callback is called. And we say that the Promise has been rejected if and when the second callback is called. If a Promise is neither fulfilled nor rejected, then it is pending. And once a promise is fulfilled or rejected, we say that it is settled. Note that a Promise can never be both fulfilled and rejected. Once a Promise settles, it will never change from fulfilled to rejected or vice versa.\n\n> 在我们进一步讨论 Promises 之前，需要暂停定义一些术语。现实生活中，我们讨论下人类的诺言，我们说“信守”或“违背”诺言。在讨论 JavaScript Promise 时，用“已兑现（fulfilled）”和“已拒绝（rejected）”。想象一下，已经调用了 Promise 的 then() 方法，并向其传递了两个回调函数。当调用第一个回调，那么我们说 Promise 已兑现。当调用第二个回调，我们则说 Promise 已被拒绝。如果一个 Promise 既不是已兑现也不是已拒绝，那么它就是待定（pending）。一旦 Promise 已兑现或已拒绝，我们就说它已敲定（settled）。请注意，一个 Promise 永远不会同时已兑现和已拒绝。Promise 一旦敲定，就永远不会从已兑现变为已拒绝，反之亦然。\n\nRemember how we defined Promises at the start of this section: “a Promise is an object that represents the result of an asynchronous operation.” It is important to remember that Promises are not just abstract ways registering callbacks to run when some async code finishes—they represent the results of that async code. If the async code runs normally (and the Promise is fulfilled), then that result is essentially the return value of the code. And if the async code does not complete normally (and the Promise is rejected), then the result is an Error object or some other value that the code might have thrown if it was not asynchronous. Any Promise that has settled has a value associated with it, and that value will not change. If the Promise is fulfilled, then the value is a return value that gets passed to any callback functions registered as the first argument of then(). If the Promise is rejected, then the value is an error of some sort that is passed to any callback functions registered with catch() or as the second argument of then().\n\n> 记住我们在本节开始时如何定义 Promise：“Promise 是描述异步计算结果的对象。”重要的是要记住，Promise 不仅仅是注册在某些异步代码完成时运行的回调的抽象方式，它们还描述了异步代码的结果。如果异步代码正常运行（并且 Promise 已兑现），那么该结果实质上就是代码的返回值。而且，如果异步代码无法正常完成（并且 Promise 已拒绝），那么结果将是 Error 对象或其他不是异步的代码可能会抛出的值。任何已敲定的 Promise 都有与其相关的值，并且该值不会改变。如果 Promise 已兑现，则该值是一个返回值，该值将传递给注册为 then() 第一个实参的回调函数。如果 Promise 已拒绝，则该值是某种异常，该异常会传递给使用 catch() 或 then() 的第二个实参注册的回调函数。\n\nThe reason that I want to be precise about Promise terminology is that Promises can also be resolved. It is easy to confuse this resolved state with the fulfilled state or with settled state, but it is not precisely the same as either. Understanding the resolved state is one of the keys to a deep understanding of Promises, and I’ll come back to it after we’ve discussed Promise chains below.\n\n> 我希望对 Promise 术语保持精确的原因是 Promise 还可以被决议。将已决议状态与已兑现状态或已敲定状态混淆是很容易的，但是三者都不完全相同。理解已决议状态是深入了解 Promise 的关键之一，在下面讨论了 Promise 链之后，我将再次介绍它。\n\n### 13.2.2 Chaining Promises\nOne of the most important benefits of Promises is that they provide a natural way to express a sequence of asynchronous operations as a linear chain of then() method invocations, without having to nest each operation within the callback of the previous one. Here, for example, is a hypothetical Promise chain:\n\n> Promise 的最重要的好处之一是，它们提供了一种自然的方式来表达一系列异步操作，表示 then() 方法调用的线性链，而不必将每个操作嵌套在前一个回调中。例如，一个假设的 Promise 链：\n\n```js\nfetch(documentURL)                      // Make an HTTP request\n    .then(response => response.json())  // Ask for the JSON body of the response\n    .then(document => {                 // When we get the parsed JSON\n        return render(document);        // display the document to the user\n    })\n    .then(rendered => {                 // When we get the rendered document\n        cacheInDatabase(rendered);      // cache it in the local database.\n    })\n    .catch(error => handle(error));     // Handle any errors that occur\n```\nThis code illustrates how a chain of Promises can make it easy to express a sequence of asynchronous operations. We’re not going to discuss this particular Promise chain at all, however. We will continue to explore the idea of using Promise chains to make HTTP requests, however.\n\n> 此代码表明了 Promise 链如何简化一系列异步操作。我们不会讨论这个特殊的 Promise 链。但是，我们将继续探索使用 Promise 链发出 HTTP 请求的想法。 \n\nEarlier in this chapter, we saw the XMLHttpRequest object used to make an HTTP request in JavaScript. That strangely named object has an old and awkward API, and it has largely been replaced by the newer, Promise-based Fetch API (§15.11.1). In its simplest form, this new HTTP API is just the function fetch(). You pass it a URL, and it returns a Promise. That promise is fulfilled when the HTTP response begins to arrive and the HTTP status and headers are available:\n\n> 在本章的前面，我们看到了 XMLHttpRequest 对象，该对象用于在 JavaScript 中发出 HTTP 请求。这个奇怪命名的对象具有一个旧且笨拙的 API，并且在很大程度上已被较新的基于 Promise 的 Fetch API（§15.11.1）所取代。以最简单的形式，这个新的 HTTP API 只是 fetch() 函数。给它传递一个 URL，然后返回一个 Promise。当 HTTP 开始收到响应并且 HTTP 状态和标头可用时，这个 promise 已兑现：\n\n```js\nfetch(\"/api/user/profile\").then(response => {\n    // When the promise resolves, we have status and headers\n    if (response.ok &&\n        response.headers.get(\"Content-Type\") === \"application/json\") {\n        // What can we do here? We don't actually have the response body yet.\n    }\n});\n```\nWhen the Promise returned by fetch() is fulfilled, it passes a Response object to the function you passed to its then() method. This response object gives you access to request status and headers, and it also defines methods like text() and json(), which give you access to the body of the response in text and JSON-parsed forms, respectively. But although the initial Promise is fulfilled, the body of the response may not yet have arrived. So these text() and json() methods for accessing the body of the response themselves return Promises. Here’s a naive way of using fetch() and the response.json() method to get the body of an HTTP response:\n\n> 当 fetch() 返回的 Promise 已兑现时，它将 Response 对象传递给传递给 then() 方法的函数。此响应对象可以访问请求状态和标头，并且还定义了诸如 text() 和 json() 之类的方法，这些方法可以分别以文本和 JSON 的形式访问响应的正文。但是，尽管最初的 Promise 已兑现，但响应的主体可能尚未到达。因此，这些用于访问响应正文的 text() 和 json() 方法本身返回 Promise。这是使用 fetch() 和 response.json() 方法获取 HTTP Response 响应正文的一种简单方法： \n\n```js\nfetch(\"/api/user/profile\").then(response => {\n    response.json().then(profile => {  // Ask for the JSON-parsed body\n        // When the body of the response arrives, it will be automatically\n        // parsed as JSON and passed to this function.\n        displayUserProfile(profile);\n    });\n});\n```\nThis is a naive way to use Promises because we nested them, like callbacks, which defeats the purpose. The preferred idiom is to use Promises in a sequential chain with code like this:\n\n> 这是 Promise 一种没经验的使用方式，因为我们像回调一样嵌套了它们，这违背了目的。首选常用方法是在顺序链中使用 Promise，其代码如下所示： \n\n```js\nfetch(\"/api/user/profile\")\n    .then(response => {\n        return response.json();\n    })\n    .then(profile => {\n        displayUserProfile(profile);\n    });\n```\nLet’s look at the method invocations in this code, ignoring the arguments that are passed to the methods:\n\n> 让我们看一下这段代码中的方法调用，忽略传递给方法的参数： \n\n```js\nfetch().then().then()\n```\nWhen more than one method is invoked in a single expression like this, we call it a method chain. We know that the fetch() function returns a Promise object, and we can see that the first .then() in this chain invokes a method on that returned Promise object. But there is a second .then() in the chain, which means that the first invocation of the then() method must itself return a Promise.\n\n> 当像这样在单个表达式中调用多个方法时，我们将其称为方法链。我们知道 fetch() 函数返回一个 Promise 对象，并且可以看到该链中的第一个 .then() 作为返回的 Promise 对象的方法调用。但是链中还有第二个 .then()，这意味着 then() 方法的第一次调用本身一定返回 Promise。\n\nSometimes, when an API is designed to use this kind of method chaining, there is just a single object, and each method of that object returns the object itself in order to facilitate chaining. That is not how Promises work, however. When we write a chain of .then() invocations, we are not registering multiple callbacks on a single Promise object. Instead, each invocation of the then() method returns a new Promise object. That new Promise object is not fulfilled until the function passed to then() is complete.\n\n> 有时，当一个 API 设计为使用这种方法链接时，只有一个对象，并且该对象的每个方法都返回该对象本身以便于链接。但是，这不是 Promise 的工作方式。当我们编写一系列的 .then() 调用时，我们并未在单个 Promise 对象上注册多个回调。而是，对 then() 方法的每次调用都会返回一个新的 Promise 对象。在传递给 then() 的函数完成之前，新的 Promise 对象不会被兑现。\n\nLet’s return to a simplified form of the original fetch() chain above. If we define the functions passed to the then() invocations elsewhere, we might refactor the code to look like this:\n\n> 让我们回到上面原始 fetch() 链的简化形式。如果我们在其他地方定义传递给 then() 调用的函数，则可以将代码重构为如下形式：\n\n```js\nfetch(theURL)          // task 1; returns promise 1\n    .then(callback1)   // task 2; returns promise 2\n    .then(callback2);  // task 3; returns promise 3\n```\nLet’s walk through this code in detail:\n\n> 让我们详细介绍这段代码：\n\n1. On the first line, fetch() is invoked with a URL. It initiates an HTTP GET request for that URL and returns a Promise. We’ll call this HTTP request “task 1” and we’ll call the Promise “promise 1”.\n2. On the second line, we invoke the then() method of promise 1, passing the callback1 function that we want to be invoked when promise 1 is fulfilled. The then() method stores our callback somewhere, then returns a new Promise. We’ll call the new Promise returned at this step “promise 2”, and we’ll say that “task 2” begins when callback1 is invoked.\n3. On the third line, we invoke the then() method of promise 2, passing the callback2 function we want invoked when promise 2 is fulfilled. This then() method remembers our callback and returns yet another Promise. We’ll say that “task 3” begins when callback2 is invoked. We can call this latest Promise “promise 3”, but we don’t really need a name for it because we won’t be using it at all.\n4. The previous three steps all happen synchronously when the expression is first executed. Now we have an asynchronous pause while the HTTP request initiated in step 1 is sent out across the internet.\n5. Eventually, the HTTP response starts to arrive. The asynchronous part of the fetch() call wraps the HTTP status and headers in a Response object and fulfills promise 1 with that Response object as the value.\n6. When promise 1 is fulfilled, its value (the Response object) is passed to our callback1() function, and task 2 begins. The job of this task, given a Response object as input, is to obtain the response body as a JSON object.\n7. Let’s assume that task 2 completes normally and is able to parse the body of the HTTP response to produce a JSON object. This JSON object is used to fulfill promise 2.\n8. The value that fulfills promise 2 becomes the input to task 3 when it is passed to the callback2() function. This third task now displays the data to the user in some unspecified way. When task 3 is complete (assuming it completes normally), then promise 3 will be fulfilled. But because we never did anything with promise 3, nothing happens when that Promise settles, and the chain of asynchronous computation ends at this point.\n\n> 1. 在第一行，使用 URL 调用 fetch()。它针对该 URL 发起 HTTP GET请求并返回 Promise。我们将这个 HTTP 请求称为“task 1”，将 Promise 称为“promise 1”。\n> 2. 在第二行，我们调用 promise 1 的 then() 方法，并传递 promise 1 在已兑现时要调用的 callback1 函数。then() 方法将回调函数存储在某个位置，然后返回一个新的 Promise。我们将在这一步返回的新 Promise 称为“promise 2”，并且我们说当 callback1 被调用时“task 2”开始。\n> 3. 在第三行，我们调用 promise 2 的 then() 方法，并传递 promise 2 在已兑现时要调用的 callback2 函数。这个 then() 方法会记住我们的回调并返回另一个 Promise。我们说“task 3”是在调用 callback2 时开始的。我们可以将最新的 Promise 称为“promise 3”，但是我们并不需要它的名称，因为我们根本不会使用它。\n> 4. 最初执行表达式时，前三个步骤都是同步发生的。现在，在第 1 步中启动的 HTTP 请求通过 Internet 发送时，我们有了异步暂停。\n> 5. 最终，HTTP 响应开始到达。fetch() 调用的异步部分将 HTTP 状态和标头包装在 Response 对象中，并以该 Response 对象作为值来兑现 promise 1。\n> 6. promise 1 已兑现后，其值（Response 对象）将传递到我们的 callback1() 函数，task 2 开始。以 Response 对象作为输入，此任务的工作是获得响应主体转化为 JSON 对象。\n> 7. 假设 task 2 正常完成，并且能够解析 HTTP 响应主体以生成 JSON 对象。此 JSON 对象用于兑现 promise 2。\n> 8. 兑现 promise 2 的值在传递给 callback2() 函数时成为 task 3 的输入。现在，第三个任务以某种未指定的方式向用户显示数据。当 task 3 完成时（假设它正常完成），则 promise 3 将被兑现。但是，因为我们从未对 promise 3 做任何事情，所以当 promise 3 敲定时，什么也没有发生，并且异步计算链到此结束。 \n\n### 13.2.3 Resolving Promises\nWhile explaining the URL-fetching Promise chain with the list in the last section, we talked about promises 1, 2, and 3. But there is actually a fourth Promise object involved as well, and this brings us to our important discussion of what it means for a Promise to be “resolved.”\n\n> 在上一部分中用列表解释 URL-fetching Promise 链时，我们讨论了 promise 1、2 和 3。但是实际上也涉及第四个 Promise 对象，并且这为我们带来重要的讨论————什么是 Promise 的“已决议（resolved）”状态 。 \n\nRemember that fetch() returns a Promise object which, when fulfilled, passes a Response object to the callback function we register. This Response object has .text(), .json(), and other methods to request the body of the HTTP response in various forms. But since the body may not yet have arrived, these methods must return Promise objects. In the example we’ve been studying, “task 2” calls the .json() method and returns its value. This is the fourth Promise object, and it is the return value of the callback1() function.\n\n> 请记住，fetch() 返回一个 Promise 对象，当其已兑现时，它将 Response 对象传递给我们注册的回调函数。此 Response 对象具有 .text()、.json() 和其他方法，以各种形式请求 HTTP 响应的主体。但是由于主体可能尚未到达，因此这些方法必须返回 Promise 对象。在我们一直在研究的示例中，“task 2”调用 .json() 方法并返回其值。这是第四个 Promise 对象，它是 callback1() 函数的返回值。\n\nLet’s rewrite the URL-fetching code one more time in a verbose and nonidiomatic way that makes the callbacks and promises explicit:\n\n> 让我们以冗长且非常用方式再次重写 URL-fetching 代码，使回调和 Promise 明确化：\n\n```js\nfunction c1(response) {               // callback 1\n    let p4 = response.json();\n    return p4;                        // returns promise 4\n}\n\nfunction c2(profile) {                // callback 2\n    displayUserProfile(profile);\n}\n\nlet p1 = fetch(\"/api/user/profile\");  // promise 1, task 1\nlet p2 = p1.then(c1);                 // promise 2, task 2\nlet p3 = p2.then(c2);                 // promise 3, task 3\n```\nIn order for Promise chains to work usefully, the output of task 2 must become the input to task 3. And in the example we’re considering here, the input to task 3 is the body of the URL that was fetched, parsed as a JSON object. But, as we’ve just discussed, the return value of callback c1 is not a JSON object, but Promise p4 for that JSON object. This seems like a contradiction, but it is not: when p1 is fulfilled, c1 is invoked, and task 2 begins. And when p2 is fulfilled, c2 is invoked, and task 3 begins. But just because task 2 begins when c1 is invoked, it does not mean that task 2 must end when c1 returns. Promises are about managing asynchronous tasks, after all, and if task 2 is asynchronous (which it is, in this case), then that task will not be complete by the time the callback returns.\n\n> 为了使 Promise 链有效地工作，task 2 的输出必须成为 task 3 的输入。在此示例中，我们在这里考虑的是，task 3 的输入是从 URL 所获取的的主体，将其解析为 JSON 对象。但是，正如我们刚刚讨论的那样，回调 c1 的返回值不是 JSON 对象，而是该 JSON 对象的 Promise p4。这似乎有矛盾，但并非如此：当 p1 已兑现时，将调用 c1，并且 task 2 开始。当 p2 已兑现时，c2 被调用，task 3 开始。但是，仅仅因为 c1 被调用时开始 task 2 ，可这并不意味着 task 2 必须在 c1 返回时结束。毕竟，Promise 是关于管理异步任务的，如果 task 2 是异步的（在本例中为异步），则在回调返回时该任务将不会完成。\n\nWe are now ready to discuss the final detail that you need to understand to really master Promises. When you pass a callback c to the then() method, then() returns a Promise p and arranges to asynchronously invoke c at some later time. The callback performs some computation and returns a value v. When the callback returns, p is resolved with the value v. When a Promise is resolved with a value that is not itself a Promise, it is immediately fulfilled with that value. So if c returns a non-Promise, that return value becomes the value of p, p is fulfilled and we are done. But if the return value v is itself a Promise, then p is resolved but not yet fulfilled. At this stage, p cannot settle until the Promise v settles. If v is fulfilled, then p will be fulfilled to the same value. If v is rejected, then p will be rejected for the same reason. This is what the “resolved” state of a Promise means: the Promise has become associated with, or “locked onto,” another Promise. We don’t know yet whether p will be fulfilled or rejected, but our callback c no longer has any control over that. p is “resolved” in the sense that its fate now depends entirely on what happens to Promise v.\n\n> 现在我们准备讨论最后的细节，需要了解这些才能真正掌握 Promise。当将回调 c 传递给 then() 方法时，then() 返回 Promise p 并安排在以后的某个时间异步调用  c。调执行一些计算并返回值 v。当回调返回时，p 用值 v 决议。当 Promise 使用不是本身的 Promise 值决议时，立即用该值兑现。因此，如果 c 返回一个非 Promise，则返回值成为 p 的值，则 p 已兑现并且任务完成。但是，如果返回值 v 本身是一个 Promise，则 p 已决议但尚未兑现。在此阶段，直到 Promise v 敲定，p 才能敲定。如果 v 已兑现，则 p 将被兑现为相同的值。如果 v 已拒绝，则 p 将因相同的原因而被拒绝。这就是一个 Promise 的“已决议（resolved）”状态的含义：Promise 已与另一个 Promise 关联或“锁定”。我们尚不知道 p 是已兑现还是拒绝，但是我们的回调 c 对此不再具有任何控制权。p 是“已决议”的，从这一方面来说它命运现在完全取决于 Promise v 会发生什么。\n\nLet’s bring this back to our URL-fetching example. When c1 returns p4, p2 is resolved. But being resolved is not the same as being fulfilled, so task 3 does not begin yet. When the full body of the HTTP response becomes available, then the .json() method can parse it and use that parsed value to fulfill p4. When p4 is fulfilled, p2 is automatically fulfilled as well, with the same parsed JSON value. At this point, the parsed JSON object is passed to c2, and task 3 begins.\n\n> 让我们回到我们的 URL-fetching 示例中。 当 c1 返回 p4 时，p2 已决议。但是已决议与已兑现并不相同，因此 task 3 尚未开始。当 HTTP 响应的全文可用时，.json() 方法可以对其进行解析，并使用该解析后的值来已兑现 p4。 当 p4 已兑现时，也会使用相同的已解析 JSON 值自动已兑现 p2。此时，已解析的 JSON 对象将传递给 c2，然后 task 3 开始。\n\nThis can be one of the trickiest parts of JavaScript to understand, and you may need to read this section more than once. Figure 13-1 presents the process in visual form and may help clarify it for you.\n\n> 这可能是 JavaScript 最难理解的部分之一，可能需要多次阅读本节。图 13-1 以可视形式显示了该过程，可能有助于对其进行说明。\n\n<Figures figure=\"13-1\">Fetching a URL with Promises</Figures>\n\n### 13.2.4 More on Promises and Errors\nEarlier in the chapter, we saw that you can pass a second callback function to the .then() method and that this second function will be invoked if the Promise is rejected. When that happens, the argument to this second callback function is a value—typically an Error object—that represents the reason for the rejection. We also learned that it is uncommon (and even unidiomatic) to pass two callbacks to a .then() method. Instead, Promise-related errors are typically handled by adding a .catch() method invocation to a Promise chain. Now that we have examined Promise chains, we can return to error handling and discuss it in more detail. To preface the discussion, I’d like to stress that careful error handling is really important when doing asynchronous programming. With synchronous code, if you leave out error-handling code, you’ll at least get an exception and a stack trace that you can use to figure out what is going wrong. With asynchronous code, unhandled exceptions will often go unreported, and errors can occur silently, making them much harder to debug. The good news is that the .catch() method makes it easy to handle errors when working with Promises.\n\n> 在本章的前面，我们看到可以将第二个回调函数传递给 .then() 方法，并且如果 Promise 被拒绝，则将调用该第二个函数。发生这种情况时，第二个回调函数的实参是一个值（通常是一个 Error 对象），它表示拒绝的原因。我们还了解到，将两个回调传递给 .then() 方法并不常见（甚至是单例的）。相反，通常通过向 Promise 链添加 .catch() 方法调用来处理与 Promise 相关的异常。现在我们已经检查了 Promise 链，我们可以返回异常处理并更详细地讨论它。在开始讨论之前，我想强调指出，进行异步编程时，仔细的异常处理非常重要。使用同步代码，如果省略了异常处理代码，则至少会得到一个异常和一个堆栈跟踪，可用于找出问题所在。对于异步代码，未处理的异常通常不会报告，异常可以静默发生，从而使调试更加困难。好消息是，使用 .catch() 方法可以更轻松地处理 Promise 的异常。\n\n#### THE CATCH AND FINALLY METHODS\nThe .catch() method of a Promise is simply a shorthand way to call .then() with null as the first argument and an error-handling callback as the second argument. Given any Promise p and a callback c, the following two lines are equivalent:\n\n> .then() 可以处理异常，使用 null 为第一个实参，而异常处理回调为第二个实参，Promise 的 .catch() 方法只是这种 .then() 调用的一种简便写法。给定 Promise p 和回调 c，以下两行代码是等效的：\n\n```js\np.then(null, c);\np.catch(c);\n```\nThe .catch() shorthand is preferred because it is simpler and because the name matches the catch clause in a try/catch exception-handling statement. As we’ve discussed, normal exceptions don’t work with asynchronous code. The .catch() method of Promises is an alternative that does work for asynchronous code. When something goes wrong in synchronous code, we can speak of an exception “bubbling up the call stack” until it finds a catch block. With an asynchronous chain of Promises, the comparable metaphor might be of an error “trickling down the chain” until it finds a .catch() invocation.\n\n> 首选 .catch() 速记，因为它更简单，并且名称与 try/catch 异常处理语句中的 catch 子句匹配。正如我们所讨论的，普通例外不适用于异步代码。Promise 的 .catch() 方法是一种适用于异步代码的替代方法。当同步代码中出现问题时，我们“使调用堆栈冒泡”描述一个异常，直到找到 catch 块为止。对于异步的 Promise 链，则是“向链下滴”，直到找到 .catch() 调用为止。\n\nIn ES2018, Promise objects also define a .finally() method whose purpose is similar to the finally clause in a try/catch/finally statement. If you add a .finally() invocation to your Promise chain, then the callback you pass to .finally() will be invoked when the Promise you called it on settles. Your callback will be invoked if the Promise fulfills or rejects, and it will not be passed any arguments, so you can’t find out whether it fulfilled or rejected. But if you need to run some kind of cleanup code (such as closing open files or network connections) in either case, a .finally() callback is the ideal way to do that. Like .then() and .catch(), .finally() returns a new Promise object. The return value of a .finally() callback is generally ignored, and the Promise returned by .finally() will typically resolve or reject with the same value that the Promise that .finally() was invoked on resolves or rejects with. If a .finally() callback throws an exception, however, then the Promise returned by .finally() will reject with that value.\n\n> 在 ES2018 中，Promise 对象还定义了一个 .finally() 方法，其目的类似于 try/catch/finally 语句中的 finally 子句。如果将 .finally() 调用添加到 Promise 链中，那么 .finally() 的调用者 Promise 的敲定的时候，传递给 .finally() 的回调将被调用。如果 Promise 已兑现或已拒绝，则将调用回调，并且它不会再作为实参传递，因此无法确定它是已兑现还是已拒绝。但是，无论哪种情况，如果都需要运行某种清理代码（例如关闭打开的文件或网络连接），则 .finally() 回调是实现此目的的理想方法。与 .then() 和 .catch() 一样，.finally() 返回一个新的 Promise 对象。.finally() 回调的返回值通常被忽略，.finally() 返回的 Promise 通常将以调用 .finally() 的 Promise 相同的值来决议或拒绝。但是，如果 .finally() 回调引发异常，则 .finally() 返回的 Promise 以该异常值拒绝。\n\nThe URL-fetching code that we studied in the previous sections did not do any error handling. Let’s correct that now with a more realistic version of the code:\n\n> 我们在上一节中研究的 URL-fetching 代码没有任何异常处理。现在，使用更可行的代码版本进行更正：\n\n```js\nfetch(\"/api/user/profile\")    // Start the HTTP request\n    .then(response => {       // Call this when status and headers are ready\n        if (!response.ok) {   // If we got a 404 Not Found or similar error\n            return null;      // Maybe user is logged out; return null profile\n        }\n\n        // Now check the headers to ensure that the server sent us JSON.\n        // If not, our server is broken, and this is a serious error!\n        let type = response.headers.get(\"content-type\");\n        if (type !== \"application/json\") {\n            throw new TypeError(`Expected JSON, got ${type}`);\n        }\n\n        // If we get here, then we got a 2xx status and a JSON content-type\n        // so we can confidently return a Promise for the response\n        // body as a JSON object.\n        return response.json();\n    })\n    .then(profile => {        // Called with the parsed response body or null\n        if (profile) {\n            displayUserProfile(profile);\n        }\n        else { // If we got a 404 error above and returned null we end up here\n            displayLoggedOutProfilePage();\n        }\n    })\n    .catch(e => {\n        if (e instanceof NetworkError) {\n            // fetch() can fail this way if the internet connection is down\n            displayErrorMessage(\"Check your internet connection.\");\n        }\n        else if (e instanceof TypeError) {\n            // This happens if we throw TypeError above\n            displayErrorMessage(\"Something is wrong with our server!\");\n        }\n        else {\n            // This must be some kind of unanticipated error\n            console.error(e);\n        }\n    });\n```\nLet’s analyze this code by looking at what happens when things go wrong. We’ll use the naming scheme we used before: p1 is the Promise returned by the fetch() call. p2 is the Promise returned by the first .then() call, and c1 is the callback that we pass to that .then() call. p3 is the Promise returned by the second .then() call, and c2 is the callback we pass to that call. Finally, c3 is the callback that we pass to the .catch() call. (That call returns a Promise, but we don’t need to refer to it by name.)\n\n> 让我们通过发生异常情况情况来分析此代码。我们将使用之前使用的命名方案：p1 是 fetch() 调用返回的 Promise。p2 是第一个 .then() 调用返回的 Promise，而 c1 是我们传递给该 .then() 的回调。p3 是第二个 .then() 调用返回的 Promise，而 c2 是我们传递给该调用的回调。最后，c3 是我们传递给 .catch() 的回调。（该调用返回一个 Promise，但我们不需要按名称引用它。）\n\nThe first thing that could fail is the fetch() request itself. If the network connection is down (or for some other reason an HTTP request cannot be made), then Promise p1 will be rejected with a NetworkError object. We didn’t pass an error-handling callback function as the second argument to the .then() call, so p2 rejects as well with the same NetworkError object. (If we had passed an error handler to that first .then() call, the error handler would be invoked, and if it returned normally, p2 would be resolved and/or fulfilled with the return value from that handler.) Without a handler, though, p2 is rejected, and then p3 is rejected for the same reason. At this point, the c3 error-handling callback is called, and the NetworkError-specific code within it runs.\n\n> 第一个可能失败的是 fetch() 请求本身。如果网络连接断开（或由于某些其他原因而无法发出 HTTP 请求），则 Promise p1 将被 NetworkError 对象拒绝。我们没有将异常处理回调函数作为 .then() 调用的第二个实参传递，因此 p2 同样会被相同的 NetworkError 对象拒绝。（如果将异常处理程序传递给第一个 .then() 调用，则将调用该异常处理程序，并且如果该异常处理程序正常返回，伴随处理的返回值，p2 变为已决议和或或已兑现。）但是，出于相同的原因，p2 被拒绝，然后 p3 被拒绝。此时，将调用 c3 异常处理回调，并在其中运行特定于 NetworkError 的代码。\n\nAnother way our code could fail is if our HTTP request returns a 404 Not Found or another HTTP error. These are valid HTTP responses, so the fetch() call does not consider them errors. fetch() encapsulates a 404 Not Found in a Response object and fulfills p1 with that object, causing c1 to be invoked. Our code in c1 checks the ok property of the Response object to detect that it has not received a normal HTTP response and handles that case by simply returning null. Because this return value is not a Promise, it fulfills p2 right away, and c2 is invoked with this value. Our code in c2 explicitly checks for and handles falsy values by displaying a different result to the user. This is a case where we treat an abnormal condition as a nonerror and handle it without actually using an error handler.\n\n> 代码失败的另一种方式是，如果我们的 HTTP 请求返回 404 Not Found 或另一个 HTTP 异常。这些是有效的 HTTP 响应，因此 fetch() 调用不会将其视为异常。fetch() 在 Response 对象中封装了一个 404 Not Found，并用该对象已兑现 p1，从而导致 c1 被调用。我们在 c1 中的代码检查 Response 对象的 ok 属性，以检测它没有收到正常的 HTTP 响应，并通过简单地返回 null 来处理这种情况。因为此返回值不是 Promise，所以它立即已兑现 p2，并使用该值调用 c2。 我们在 c2 中的代码显式检查并处理错误值，并向用户显示不同的结果。在这种情况下，我们将异常情况视为非异常，并在不使用异常处理程序的情况下对其进行处理。\n\nA more serious error occurs in c1 if the we get a normal HTTP response code but the Content-Type header is not set appropriately. Our code expects a JSON-formatted response, so if the server is sending us HTML, XML, or plain text instead, we’re going to have a problem. c1 includes code to check the Content-Type header. If the header is wrong, it treats this as a nonrecoverable problem and throws a TypeError. When a callback passed to .then() (or .catch()) throws a value, the Promise that was the return value of the .then() call is rejected with that thrown value. In this case, the code in c1 that raises a TypeError causes p2 to be rejected with that TypeError object. Since we did not specify an error handler for p2, p3 will be rejected as well. c2 will not be called, and the TypeError will be passed to c3, which has code to explicitly check for and handle this type of error.\n\n> 如果我们获得正常的 HTTP 响应代码，但 Content-Type 标头设置不正确，则 c1 中会发生更严重的异常。我们的代码需要一个 JSON 格式的响应，因此，如果服务器发送给我们的是 HTML、XML 或纯文本，那么我们将会遇到问题。c1 包含用于检查 Content-Type 标头的代码。如果标头错误，则将其视为不可恢复的问题，并引发 TypeError。当传递给 .then()（或 .catch()）的回调引发一个值时，作为 .then() 调用的返回的 Promise 将被该抛出的值拒绝。在这种情况下，c1 中的代码引发 TypeError 导致带有 TypeError 对象的 p2 一起被拒绝。由于我们没有为 p2 指定异常处理程序，因此 p3 也将被拒绝。c2 将不会被调用，并且 TypeError 将传递给 c3，后者具有显式检查和处理此类异常的代码。\n\nThere are a couple of things worth noting about this code. First, notice that the error object thrown with a regular, synchronous throw statement ends up being handled asynchronously with a .catch() method invocation in a Promise chain. This should make it clear why this shorthand method is preferred over passing a second argument to .then(), and also why it is so idiomatic to end Promise chains with a .catch() call.\n\n> 关于此代码有些得注意。首先，请注意用常规的同步 throw 语句引发的异常对象最终会通过 Promise 链中的 .catch() 方法调用进行异步捕获。这清楚表明为什么与将第二个实参传递给 .then() 相比更偏向使用这种速记方法，以及为什么习惯以 .catch() 调用结束 Promise 链。\n\nBefore we leave the topic of error handling, I want to point out that, although it is idiomatic to end every Promise chain with a .catch() to clean up (or at least log) any errors that occurred in the chain, it is also perfectly valid to use .catch() elsewhere in a Promise chain. If one of the stages in your Promise chain can fail with an error, and if the error is some kind of recoverable error that should not stop the rest of the chain from running, then you can insert a .catch() call in the chain, resulting in code that might look like this:\n\n> 在我们结束异常处理主题之前，我想指出，尽管习惯于在每个 Promise 链中添加一个 .catch() 来清理（或至少记录日志）该链中发生的任何异常，但是在 Promise 链中的其他位置使用 .catch() 也完全有效。如果 Promise 链中的某一阶段可能因异常而失败，并且该异常是某种可恢复的异常，并且该异常不会阻止链的其余部分运行，则可以在链中插入 .catch() 调用，代码可能如下所示：\n\n```js\nstartAsyncOperation()\n    .then(doStageTwo)\n    .catch(recoverFromStageTwoError)\n    .then(doStageThree)\n    .then(doStageFour)\n    .catch(logStageThreeAndFourErrors);\n```\nRemember that the callback you pass to .catch() will only be invoked if the callback at a previous stage throws an error. If the callback returns normally, then the .catch() callback will be skipped, and the return value of the previous callback will become the input to the next .then() callback. Also remember that .catch() callbacks are not just for reporting errors, but for handling and recovering from errors. Once an error has been passed to a .catch() callback, it stops propagating down the Promise chain. A .catch() callback can throw a new error, but if it returns normally, than that return value is used to resolve and/or fulfill the associated Promise, and the error stops propagating.\n\n> 请记住，仅当前一阶段的回调引发异常时，才会调用传递给 catch() 的回调。如果该回调正常返回，则将跳过 catch() 回调，并且前一个回调的返回值将成为下一个 then() 回调的输入。还请记住，catch() 回调不仅用于异常报告，而且用于异常处理并从异常中恢复。将异常传递给 catch() 回调后，它将停止沿 Promise 链传播。catch() 回调可以引发新的异常，但是如果它正常返回，则该返回值用于决议和或或兑现关联的 Promise，并且异常停止传播。\n\nLet’s be concrete about this: in the preceding code example, if either startAsyncOperation() or doStageTwo() throws an error, then the recoverFromStageTwoError() function will be invoked. If recoverFromStageTwoError() returns normally, then its return value will be passed to doStageThree() and the asynchronous operation continues normally. On the other hand, if recoverFromStageTwoError() was unable to recover, it will itself throw an error (or it will rethrow the error that it was passed). In this case, neither doStageThree() nor doStageFour() will be invoked, and the error thrown by recoverFromStageTwoError() would be passed to logStageThreeAndFourErrors().\n\n> 让我们具体地讲一下：在前面的代码示例中，如果 startAsyncOperation() 或 doStageTwo() 引发异常，则将调用 recoveryFromStageTwoError() 函数。如果 restoreFromStageTwoError() 正常返回，则其返回值将传递给 doStageThree()，并且异步操作将正常继续。另一方面，如果 recoverFromStageTwoError() 无法恢复，则它本身将引发异常（或者重新抛出传入的异常）。在这种情况下，不会调用 doStageThree() 或 doStageFour()，并且 recoverFromStageTwoError() 引发的异常将传递给 logStageThreeAndFourErrors()。\n\nSometimes, in complex network environments, errors can occur more or less at random, and it can be appropriate to handle those errors by simply retrying the asynchronous request. Imagine you’ve written a Promise-based operation to query a database:\n\n> 有时，在复杂的网络环境中，异常可能会或多或少地随机发生，并且可以通过简单地重试异步请求来处理这些异常。假设编写了一个基于 Promise 的操作来查询数据库：\n\n```js\nqueryDatabase()\n    .then(displayTable)\n    .catch(displayDatabaseError);\n```\nNow suppose that transient network load issues are causing this to fail about 1% of the time. A simple solution might be to retry the query with a .catch() call:\n\n> 现在，假设大约 1％ 的概率瞬态网络负载问题导致其失败。一个简单的解决方案可能是使用 .catch() 调用重试查询：\n\n```js\nqueryDatabase()\n    .catch(e => wait(500).then(queryDatabase))  // On failure, wait and retry\n    .then(displayTable)\n    .catch(displayDatabaseError);\n```\nIf the hypothetical failures are truly random, then adding this one line of code should reduce your error rate from 1% to .01%.\n\n> 如果假设的失败确实是随机的，那么添加这一行代码应使错误率从 1％ 降低到 .01％。\n\n#### RETURNING FROM A PROMISE CALLBACK\nLet’s return one last time to the earlier URL-fetching example, and consider the c1 callback that we passed to the first .then() invocation. Notice that there are three ways that c1 can terminate. It can return normally with the Promise returned by the .json() call. This causes p2 to be resolved, but whether that Promise is fulfilled or rejected depends on what happens with the newly returned Promise. c1 can also return normally with the value null, which causes p2 to be fulfilled immediately. Finally, c1 can terminate by throwing an error, which causes p2 to be rejected. These are the three possible outcomes for a Promise, and the code in c1 demonstrates how the callback can cause each outcome.\n\n> 让我们最后一次返回前面的 URL-fetching 示例，并考虑传递给第一个 .then() 调用的回调 c1。注意，c1 可以通过三种方式终止。它可以通过 .json() 调用返回的 Promise 正常返回。这将导致 p2 变为已决议，但是该 Promise 是已兑现还是拒绝取决于新返回的 Promise 发生了什么。c1 也可以正常返回 null 值，这将导致 p2 立即变成已兑现。最后，c1 可以通过引发异常来终止，从而导致 p2 变成已拒绝。这是 Promise 的三个可能结果，而 c1 中的代码演示了回调如何导致每个结果。\n\nIn a Promise chain, the value returned (or thrown) at one stage of the chain becomes the input to the next stage of the chain, so it is critical to get this right. In practice, forgetting to return a value from a callback function is actually a common source of Promise-related bugs, and this is exacerbated by JavaScript’s arrow function shortcut syntax. Consider this line of code that we saw earlier:\n\n> 在 Promise 链中，在链的一个阶段返回（或抛出）的值成为链的下一阶段的输入，因此正确实现这一点至关重要。实际上，回调函数忘记返回值是与 Promise 相关的常见错误，而 JavaScript 的箭头函数快捷语法会加剧这种情况。回想一下我们之前看到的以下代码行：\n\n```js\n.catch(e => wait(500).then(queryDatabase))\n```\nRecall from Chapter 8 that arrow functions allow a lot of shortcuts. Since there is exactly one argument (the error value), we can omit the parentheses. Since the body of the function is a single expression, we can omit the curly braces around the function body, and the value of the expression becomes the return value of the function. Because of these shortcuts, the preceding code is correct. But consider this innocuous-seeming change:\n\n> 回顾第 8 章，箭头函数提供了许多快捷方式。由于仅存在一个参数（异常值），因此我们可以省略括号。由于函数的主体是单个表达式，因此我们可以省略函数主体周围的花括号，并且表达式的值成为函数的返回值。由于这些快捷方式，前面的代码是正确的。但是考虑一下这种无害的变化：\n\n```js\n.catch(e => { wait(500).then(queryDatabase) })\n```\nBy adding the curly braces, we no longer get the automatic return. This function now returns undefined instead of returning a Promise, which means that the next stage in this Promise chain will be invoked with undefined as its input rather than the result of the retried query. It is a subtle error that may not be easy to debug.\n\n> 通过添加花括号，我们不再获得自动返回。现在，此函数返回 undefined 而不是返回 Promise，这意味着将以 undefined 作为输入而不是重试查询的结果来调用此 Promise 链中的下一个阶段。这是一个细微的错误，可能不容易调试。\n\n### 13.2.5 Promises in Parallel\nWe’ve spent a lot of time talking about Promise chains for sequentially running the asynchronous steps of a larger asynchronous operation. Sometimes, though, we want to execute a number of asynchronous operations in parallel. The function Promise.all() can do this. Promise.all() takes an array of Promise objects as its input and returns a Promise. The returned Promise will be rejected if any of the input Promises are rejected. Otherwise, it will be fulfilled with an array of the fulfillment values of each of the input Promises. So, for example, if you want to fetch the text content of multiple URLs, you could use code like this:\n\n> 我们花了很多时间讨论 Promise 链，这些链可按顺序运行较大的异步的操作步骤。但是，有时我们想并行执行许多异步操作。函数 Promise.all() 可以做到这一点。Promise.all() 将 Promise 对象数组作为其输入，并返回 Promise。如果任何输入的 Promise 中有一个是已拒绝状态，则返回的 Promise 将被拒绝。否则，它将使用每个输入 Promise 的兑现值组成的数组来被兑现。因此，例如，如果要获取多个 URL 的文本内容，则可以使用如下代码：\n\n```js\n// We start with an array of URLs\nconst urls = [ /* zero or more URLs here */ ];\n// And convert it to an array of Promise objects\npromises = urls.map(url => fetch(url).then(r => r.text()));\n// Now get a Promise to run all those Promises in parallel\nPromise.all(promises)\n    .then(bodies => { /* do something with the array of strings */ })\n    .catch(e => console.error(e));\n```\nPromise.all() is slightly more flexible than described before. The input array can contain both Promise objects and non-Promise values. If an element of the array is not a Promise, it is treated as if it is the value of an already fulfilled Promise and is simply copied unchanged into the output array.\n\n> Promise.all() 比前面描述的要灵活一些。输入数组可以包含 Promise 对象和非 Promise 值。如果数组的元素不是 Promise，则将其视为已兑现的 Promise 的值，并原封不动地复制到输出数组中。\n\nThe Promise returned by Promise.all() rejects when any of the input Promises is rejected. This happens immediately upon the first rejection and can happen while other input Promises are still pending. In ES2020, Promise.allSettled() takes an array of input Promises and returns a Promise, just like Promise.all() does. But Promise.allSettled() never rejects the returned Promise, and it does not fulfill that Promise until all of the input Promises have settled. The Promise resolves to an array of objects, with one object for each input Promise. Each of these returned objects has a status property set to “fulfilled” or “rejected.” If the status is “fulfilled”, then the object will also have a value property that gives the fulfillment value. And if the status is “rejected”, then the object will also have a reason property that gives the error or rejection value of the corresponding Promise:\n\n> 当任何输入的 Promise 被拒绝时，Promise.all() 返回的 Promise 也会被拒绝。这在第一次拒绝时立即发生，可能其他输入 Promise 仍是待定状态。在 ES2020 中，Promise.allSettled() 接受输入的 Promise 数组，并返回 Promise，就像 Promise.all() 一样。但是 Promise.allSettled() 永远不会拒绝返回的 Promise，并且不会兑现这个 Promise，直到所有输入 Promise 全部已敲定。Promise 解析为一组对象，每个输入 Promise 都有一个对象。每个返回的对象中有一个状态属性设置为“已兑现”或“已拒绝”。如果状态为“已兑现”，则对象还将具有一个 value 属性，该属性提供兑现值。并且，如果状态为“已拒绝”，则对象还将具有一个 reason 属性，该属性给出相应的 Promise 的异常或拒绝值：\n\n```js\nPromise.allSettled([Promise.resolve(1), Promise.reject(2), 3]).then(results => {\n    results[0]  // => { status: \"fulfilled\", value: 1 }\n    results[1]  // => { status: \"rejected\", reason: 2 }\n    results[2]  // => { status: \"fulfilled\", value: 3 }\n});\n```\nOccasionally, you may want to run a number of Promises at once but may only care about the value of the first one to fulfill. In that case, you can use Promise.race() instead of Promise.all(). It returns a Promise that is fulfilled or rejected when the first of the Promises in the input array is fulfilled or rejected. (Or, if there are any non-Promise values in the input array, it simply returns the first of those.)\n\n> 有时，可能想一次运行多个 Promise，但可能只关心第一个要兑现的值。在这种情况下，可以使用 Promise.race() 代替 Promise.all()。当输入数组中的第一个 Promise 是已兑现或已拒绝状态时，它返回一个已兑现或已拒绝的 Promise。（或者，如果输入数组中有任何非 Promise 值，则只返回其中的第一个。）\n\n### 13.2.6 Making Promises\nWe’ve used the Promise-returning function fetch() in many of the previous examples because it is one of the simplest functions built in to web browsers that returns a Promise. Our discussion of Promises has also relied on hypothetical Promise-returning functions getJSON() and wait(). Functions written to return Promises really are quite useful, and this section shows how you can create your own Promise-based APIs. In particular, we’ll show implementations of getJSON() and wait().\n\n> 在之前的许多示例中，我们都使用了 Promise 返回函数 fetch()，因为它是内置于 Web 浏览器中的最简单的返回 Promise 的函数之一。我们对 Promise 的讨论还依赖于假设的 Promise 返回函数 getJSON() 和 wait()。编写用于返回 Promise 的函数确实非常有用，本节说明如何创建自己的基于 Promise 的 API。特别是，我们将展示 getJSON() 和 wait() 的实现。\n\n#### PROMISES BASED ON OTHER PROMISES\nIt is easy to write a function that returns a Promise if you have some other Promise-returning function to start with. Given a Promise, you can always create (and return) a new one by calling .then(). So if we use the existing fetch() function as a starting point, we can write getJSON() like this:\n\n> 如果以 Promise 返回函数作为开头来写一个返回 Promise 的函数是很容易的。有了 Promise，总是可以通过调用 .then() 创建（并返回）一个新的 Promise。因此，如果我们使用现有的 fetch() 函数作为起点，则可以这样编写 getJSON()：\n\n```js\nfunction getJSON(url) {\n    return fetch(url).then(response => response.json());\n}\n```\nThe code is trivial because the Response object of the fetch() API has a predefined json() method. The json() method returns a Promise, which we return from our callback (the callback is an arrow function with a single-expression body, so the return is implicit), so the Promise returned by getJSON() resolves to the Promise returned by response.json(). When that Promise fulfills, the Promise returned by getJSON() fulfills to the same value. Note that there is no error handling in this getJSON() implementation. Instead of checking response.ok and the Content-Type header, we instead just allow the json() method to reject the Promise it returned with a SyntaxError if the response body cannot be parsed as JSON.\n\n> 该代码很简单，因为 fetch() API 的 Response 对象具有预定义的 json() 方法。我们从回调（该回调是带有单个表达式主体的箭头函数，因此返回值是隐式的）中通过 json() 方法返回一个 Promise，因此 getJSON() 返回的 Promise 解析为 response.json()。当该 Promise 兑现时，由 getJSON() 返回的 Promise 将兑现为相同的值。请注意，此 getJSON() 实现中没有异常处理。如果不能将 response 主体解析为 JSON，则无需检查 response.ok 和 Content-Type 标头，而只需允许 json() 方法拒绝它的 Promise 并返回 SyntaxError。\n\nLet’s write another Promise-returning function, this time using getJSON() as the source of the initial Promise:\n\n> 让我们编写另一个 Promise 返回函数，这次使用 getJSON() 作为初始 Promise 的来源：\n\n```js\nfunction getHighScore() {\n    return getJSON(\"/api/user/profile\").then(profile => profile.highScore);\n}\n```\nWe’re assuming that this function is part of some sort of web-based game and that the URL “/api/user/profile” returns a JSON-formatted data structure that includes a highScore property.\n\n> 我们假设此函数是某种基于网络的游戏的一部分，并且 URL“/api/user/profile”返回的是包含 highScore 属性的 JSON 格式的数据结构。\n\n#### PROMISES BASED ON SYNCHRONOUS VALUES\nSometimes, you may need to implement an existing Promise-based API and return a Promise from a function, even though the computation to be performed does not actually require any asynchronous operations. In that case, the static methods Promise.resolve() and Promise.reject() will do what you want. Promise.resolve() takes a value as its single argument and returns a Promise that will immediately (but asynchronously) be fulfilled to that value. Similarly, Promise.reject() takes a single argument and returns a Promise that will be rejected with that value as the reason. (To be clear: the Promises returned by these static methods are not already fulfilled or rejected when they are returned, but they will fulfill or reject immediately after the current synchronous chunk of code has finished running. Typically, this happens within a few milliseconds unless there are many pending asynchronous tasks waiting to run.)\n\n> 有时，即使要执行的计算实际上不需要任何异步操作，也可能需要实现现有的基于 Promise 的 API 并从函数返回 Promise。在这种情况下，静态方法 Promise.resolve() 和 Promise.reject() 会做您想要的。Promise.resolve() 采用一个单个实参值，并返回一个 Promise，该 Promise 将立即（但异步地）兑现为该值。同样，Promise.reject() 接受一个实参值，并返回一个 Promise，该值将作为被拒绝的原因。（请注意：这些静态方法返回的 Promise 在返回时尚未兑现或拒绝，但是它们将在当前同步代码块运行完毕后立即兑现或拒绝。通常，这种情况会在几毫秒内发生，除非有许多等待执行的待处理异步任务。）\n\nRecall from §13.2.3 that a resolved Promise is not the same thing as a fulfilled Promise. When we call Promise.resolve(), we typically pass the fulfillment value to create a Promise object that will very soon fulfill to that value. The method is not named Promise.fulfill(), however. If you pass a Promise p1 to Promise.resolve(), it will return a new Promise p2, which is immediately resolved, but which will not be fulfilled or rejected until p1 is fulfilled or rejected.\n\n> 回顾 §13.2.3，已决议的 Promise 与已兑现的 Promise 不同。当我们调用 Promise.resolve() 时，通常会传递兑现值以创建一个 Promise 对象，该对象将很快兑现该值。但是，该方法未命名为 Promise.fulfill()。如果将 Promise p1 传递给 Promise.resolve()，它将返回一个新的 Promise p2，该 p2 立即被决议，但是直到 p1 被兑现或拒绝，该 Promise p2 才会被兑现或拒绝。 \n\nIt is possible, but unusual, to write a Promise-based function where the value is computed synchronously and returned asynchronously with Promise.resolve(). It is fairly common, however, to have synchronous special cases within an asynchronous function, and you can handle these special cases with Promise.resolve() and Promise.reject(). In particular, if you detect error conditions (such as bad argument values) before beginning an asynchronous operation, you can report that error by returning a Promise created with Promise.reject(). (You could also just throw an error synchronously in that case, but that is considered poor form because then the caller of your function needs to write both a synchronous catch clause and use an asynchronous .catch() method to handle errors.) Finally, Promise.resolve() is sometimes useful to create the initial Promise in a chain of Promises. We’ll see a couple of examples that use it this way.\n\n> 可以（但不常见）编写一个基于 Promise 的函数，同步计算的值通过通过 Promise.resolve() 异步返回。但是，在异步函数中包含同步特殊情况是很常见的，可以使用 Promise.resolve() 和 Promise.reject() 处理这些特殊情况。特别是，如果在开始异步操作之前检测到异常条件（例如异常的实参值），则可以通过返回使用 Promise.reject() 创建的 Promise 来报告该异常。（在这种情况下，也可以同步引发异常，但这被认为是较差的形式，因为函数的调用者需要同时编写同步 catch 子句并使用异步 .catch() 方法来处理异常。）最后，Promise.resolve() 有时可用于在 Promise 链中创建初始 Promise。我们将看到几个使用这种方式的示例。 \n\n#### PROMISES FROM SCRATCH\nFor both getJSON() and getHighScore(), we started off by calling an existing function to get an initial Promise, and created and returned a new Promise by calling the .then() method of that initial Promise. But what about writing a Promise-returning function when you can’t use another Promise-returning function as the starting point? In that case, you use the Promise() constructor to create a new Promise object that you have complete control over. Here’s how it works: you invoke the Promise() constructor and pass a function as its only argument. The function you pass should be written to expect two parameters, which, by convention, should be named resolve and reject. The constructor synchronously calls your function with function arguments for the resolve and reject parameters. After calling your function, the Promise() constructor returns the newly created Promise. That returned Promise is under the control of the function you passed to the constructor. That function should perform some asynchronous operation and then call the resolve function to resolve or fulfill the returned Promise or call the reject function to reject the returned Promise. Your function does not have to be asynchronous: it can call resolve or reject synchronously, but the Promise will still be resolved, fulfilled, or rejected asynchronously if you do this.\n\n> 对于 getJSON() 和 getHighScore()，我们首先调用现有函数以获取初始 Promise，然后通过调用该初始 Promise 的 .then() 方法创建并返回新的 Promise。但是，当不能使用另一个返回 Promise 函数作为起点时，如何编写返回 Promise 函数呢？在这种情况下，可以使用 Promise() 构造函数创建一个可以完全控制的新 Promise 对象。它是这样工作的：调用 Promise() 构造函数并将一个函数作为唯一实参传递。传递的函数应编写为包含两个参数，按照惯例，应将其命名为 resolve 和 reject。构造函数会同步调用使用 resolve 和 reject 参数的函数。调用函数后，Promise() 构造函数将返回新创建的 Promise。返回的 Promise 受传递给构造函数的函数的控制。该函数应该执行一些异步操作，然后调用 resolve 函数来决议或兑现返回的 Promise，或者调用 reject 函数来拒绝返回的 Promise。函数不必是异步的：它可以同步地调用 resolve 或拒绝，但是如果这样做，Promise 仍将被异步地决议，兑现或拒绝。\n\nIt can be hard to understand the functions passed to a function passed to a constructor by just reading about it, but hopefully some examples will make this clear. Here’s how to write the Promise-based wait() function that we used in various examples earlier in the chapter:\n\n> 仅仅阅读一下很难理解传递给构造函数的函数，但是希望有一些例子可以使这一点变得清楚。这是在本章前面的各种示例中使用的基于 Promise 的 wait() 函数的编写方法：\n\n```js\nfunction wait(duration) {\n    // Create and return a new Promise\n    return new Promise((resolve, reject) => { // These control the Promise\n        // If the argument is invalid, reject the Promise\n        if (duration < 0) {\n            reject(new Error(\"Time travel not yet implemented\"));\n        }\n        // Otherwise, wait asynchronously and then resolve the Promise.\n        // setTimeout will invoke resolve() with no arguments, which means\n        // that the Promise will fulfill with the undefined value.\n        setTimeout(resolve, duration);\n    });\n}\n```\nNote that the pair of functions that you use to control the fate of a Promise created with the Promise() constructor are named resolve() and reject(), not fulfill() and reject(). If you pass a Promise to resolve(), the returned Promise will resolve to that new Promise. Often, however, you will pass a non-Promise value, which fulfills the returned Promise with that value.\n\n> 请注意，用于控制由 Promise() 构造函数创建的 Promise 命运的一对函数分别命名为 resolve() 和 reject()，而不是 fulfill() 和 reject()。如果传递一个 Promise 给 resolve()，则返回的 Promise 将决议于该新的 Promise。但是，通常会传递一个非 Promise 值，返回的 Promise 会兑现这个值。\n\nExample 13-1 is another example of using the Promise() constructor. This one implements our getJSON() function for use in Node, where the fetch() API is not built in. Remember that we started this chapter with a discussion of asynchronous callbacks and events. This example uses both callbacks and event handlers and is a good demonstration, therefore, of how we can implement Promise-based APIs on top of other styles of asynchronous programming.\n\n> 示例 13-1 是使用 Promise() 构造函数的另一个示例。这一节实现了 Node 中在未内置 fetch() API 使用的 getJSON() 函数。请记住，本章开始时讨论了异步回调和事件。此示例同时使用了回调和事件处理程序，因此很好地演示了如何在其他风格的异步编程之上实现基于 Promise 的 API。\n\nExample 13-1. An asynchronous getJSON() function\n\n> 示例 13-1 异步 getJSON() 函数\n\n```js\nconst http = require(\"http\");\n\nfunction getJSON(url) {\n    // Create and return a new Promise\n    return new Promise((resolve, reject) => {\n        // Start an HTTP GET request for the specified URL\n        request = http.get(url, response => { // called when response starts\n            // Reject the Promise if the HTTP status is wrong\n            if (response.statusCode !== 200) {\n                reject(new Error(`HTTP status ${response.statusCode}`));\n                response.resume();  // so we don't leak memory\n            }\n            // And reject if the response headers are wrong\n            else if (response.headers[\"content-type\"] !== \"application/json\") {\n                reject(new Error(\"Invalid content-type\"));\n                response.resume();  // don't leak memory\n            }\n            else {\n                // Otherwise, register events to read the body of the response\n                let body = \"\";\n                response.setEncoding(\"utf-8\");\n                response.on(\"data\", chunk => { body += chunk; });\n                response.on(\"end\", () => {\n                    // When the response body is complete, try to parse it\n                    try {\n                        let parsed = JSON.parse(body);\n                        // If it parsed successfully, fulfill the Promise\n                        resolve(parsed);\n                    } catch(e) {\n                        // If parsing failed, reject the Promise\n                        reject(e);\n                    }\n                });\n            }\n        });\n        // We also reject the Promise if the request fails before we\n        // even get a response (such as when the network is down)\n        request.on(\"error\", error => {\n            reject(error);\n        });\n    });\n}\n```\n### 13.2.7 Promises in Sequence\nPromise.all() makes it easy to run an arbitrary number of Promises in parallel. And Promise chains make it easy to express a sequence of a fixed number of Promises. Running an arbitrary number of Promises in sequence is trickier, however. Suppose, for example, that you have an array of URLs to fetch, but that to avoid overloading your network, you want to fetch them one at a time. If the array is of arbitrary length and unknown content, you can’t write out a Promise chain in advance, so you need to build one dynamically, with code like this:\n\n> Promise.all() 使得并行运行任意数量的 Promise 变得容易。Promise 链使表达固定数量的 Promise 序列变得容易。但是，依次执行任意数量的 Promise 会比较棘手。例如，假设您要获取一组 URL，但是为了避免网络过载，希望一次获取一个 URL。如果数组的长度是任意的且内容未知，则无法提前写出 Promise 链，因此需要使用以下代码动态构建一个：\n\n```js\nfunction fetchSequentially(urls) {\n    // We'll store the URL bodies here as we fetch them\n    const bodies = [];\n\n    // Here's a Promise-returning function that fetches one body\n    function fetchOne(url) {\n        return fetch(url)\n            .then(response => response.text())\n            .then(body => {\n                // We save the body to the array, and we're purposely\n                // omitting a return value here (returning undefined)\n                bodies.push(body);\n            });\n    }\n\n    // Start with a Promise that will fulfill right away (with value undefined)\n    let p = Promise.resolve(undefined);\n\n    // Now loop through the desired URLs, building a Promise chain\n    // of arbitrary length, fetching one URL at each stage of the chain\n    for(url of urls) {\n        p = p.then(() => fetchOne(url));\n    }\n\n    // When the last Promise in that chain is fulfilled, then the\n    // bodies array is ready. So let's return a Promise for that\n    // bodies array. Note that we don't include any error handlers:\n    // we want to allow errors to propagate to the caller.\n    return p.then(() => bodies);\n}\n```\nWith this fetchSequentially() function defined, we could fetch the URLs one at a time with code much like the fetch-in-parallel code we used earlier to demonstrate Promise.all():\n\n> 定义了这个 fetchSequentially() 函数之后，我们可以用一个代码一次来获取多个 URL，就像我们之前用来演示 Promise.all() 的并行获取代码一样：\n\n```js\nfetchSequentially(urls)\n    .then(bodies => { /* do something with the array of strings */ })\n    .catch(e => console.error(e));\n```\nThe fetchSequentially() function starts by creating a Promise that will fulfill immediately after it returns. It then builds a long, linear Promise chain off of that initial Promise and returns the last Promise in the chain. It is like setting up a row of dominoes and then knocking the first one over.\n\n> fetchSequentially() 函数首先创建一个 Promise，该 Promise 将在返回后立即兑现。然后，它从该初始 Promise 构建一个长的线性 Promise 链，并返回链中的最后一个 Promise。这就像设置一排多米诺骨牌，然后将第一个多米诺骨牌撞倒一样。\n\nThere is another (possibly more elegant) approach that we can take. Rather than creating the Promises in advance, we can have the callback for each Promise create and return the next Promise. That is, instead of creating and chaining a bunch of Promises, we instead create Promises that resolve to other Promises. Rather than creating a domino-like chain of Promises, we are instead creating a sequence of Promises nested one inside the other like a set of matryoshka dolls. With this approach, our code can return the first (outermost) Promise, knowing that it will eventually fulfill (or reject!) to the same value that the last (innermost) Promise in the sequence does. The promiseSequence() function that follows is written to be generic and is not specific to URL fetching. It is here at the end of our discussion of Promises because it is complicated. If you’ve read this chapter carefully, however, I hope you’ll be able to understand how it works. In particular, note that the nested function inside promiseSequence() appears to call itself recursively, but because the “recursive” call is through a then() method, there is not actually any traditional recursion happening:\n\n> 我们可以采用另一种方法（可能更优雅）。除了提前创建 Promise 外，我们还可以为每个 Promise 创建回调，并返回下一个 Promise。也就是说，我们没有创建和链接一堆 Promise，而是创建了决议于其他承诺的承诺。与其创建类似多米诺骨牌的 Promise 链，不如创建一系列彼此嵌套在一起的 Promise 序列，就像俄罗斯套娃一样。使用这种方法，我们的代码可以知道第一个（最外面的）Promise 最终将兑现（或拒绝！），使其返回序列中最后一个（最里面的）Promise 相同的值。后面是通用的 promiseSequence() 函数，并不特定于 URL 提取。因为它很复杂，所以将它放在我们对 Promise 的讨论的结尾。但是，如果仔细阅读了本章，希望能理解它的工作原理。特别要注意的是，promiseSequence() 中的嵌套函数类似递归地调用自身，但是由于“递归”调用是通过 then() 方法进行的，因此实际上没有发生任何传统的递归：\n\n```js\n// This function takes an array of input values and a \"promiseMaker\" function.\n// For any input value x in the array, promiseMaker(x) should return a Promise\n// that will fulfill to an output value. This function returns a Promise\n// that fulfills to an array of the computed output values.\n//\n// Rather than creating the Promises all at once and letting them run in\n// parallel, however, promiseSequence() only runs one Promise at a time\n// and does not call promiseMaker() for a value until the previous Promise\n// has fulfilled.\nfunction promiseSequence(inputs, promiseMaker) {\n    // Make a private copy of the array that we can modify\n    inputs = [...inputs];\n\n    // Here's the function that we'll use as a Promise callback\n    // This is the pseudorecursive magic that makes this all work.\n    function handleNextInput(outputs) {\n        if (inputs.length === 0) {\n            // If there are no more inputs left, then return the array\n            // of outputs, finally fulfilling this Promise and all the\n            // previous resolved-but-not-fulfilled Promises.\n            return outputs;\n        } else {\n            // If there are still input values to process, then we'll\n            // return a Promise object, resolving the current Promise\n            // with the future value from a new Promise.\n            let nextInput = inputs.shift(); // Get the next input value,\n            return promiseMaker(nextInput)  // compute the next output value,\n                // Then create a new outputs array with the new output value\n                .then(output => outputs.concat(output))\n                // Then \"recurse\", passing the new, longer, outputs array\n                .then(handleNextInput);\n        }\n    }\n\n    // Start with a Promise that fulfills to an empty array and use\n    // the function above as its callback.\n    return Promise.resolve([]).then(handleNextInput);\n}\n```\nThis promiseSequence() function is intentionally generic. We can use it to fetch URLs with code like this:\n\n> promiseSequence() 函数是通用的。我们可以使用它通过以下代码来获取 URL：\n\n```js\n// Given a URL, return a Promise that fulfills to the URL body text\nfunction fetchBody(url) { return fetch(url).then(r => r.text()); }\n// Use it to sequentially fetch a bunch of URL bodies\npromiseSequence(urls, fetchBody)\n    .then(bodies => { /* do something with the array of strings */ })\n    .catch(console.error);\n```\n## 13.3 async and await\nES2017 introduces two new keywords—async and await—that represent a paradigm shift in asynchronous JavaScript programming. These new keywords dramatically simplify the use of Promises and allow us to write Promise-based, asynchronous code that looks like synchronous code that blocks while waiting for network responses or other asynchronous events. Although it is still important to understand how Promises work, much of their complexity (and sometimes even their very presence!) vanishes when you use them with async and await.\n\n> ES2017 引入了两个新的关键字（async 和 await）描述异步 JavaScript 编程中的模式转变。这些新关键字极大地简化了 Promises 的使用，使我们能够编写基于 Promise 的异步代码看起来像是等待网络响应或其他异步事件而阻塞的同步代码。尽管了解 Promises 的工作原理仍然很重要，但是当将它们与 async 和 await 一起使用时，它们的大部分复杂性（有时甚至是它们的存在！）就消失了。 \n\nAs we discussed earlier in the chapter, asynchronous code can’t return a value or throw an exception the way that regular synchronous code can. And this is why Promises are designed the way the are. The value of a fulfilled Promise is like the return value of a synchronous function. And the value of a rejected Promise is like a value thrown by a synchronous function. This latter similarity is made explicit by the naming of the .catch() method. async and await take efficient, Promise-based code and hide the Promises so that your asynchronous code can be as easy to read and as easy to reason about as inefficient, blocking, synchronous code.\n\n> 如本章前面所述，异步代码无法像常规同步代码那样返回值或引发异常。这就是为什么 Promise 如此设计的原因。已兑现的 Promise 的值类似于同步函数的返回值。而且已拒绝的 Promise 的值就像同步函数抛出的值。后者通过类似的 .catch() 方法命名，使得表述更清晰。async 和 await 使用高效的、基于 Promise 的代码并隐藏 Promise，以便异步代码可以像低效、阻塞、同步代码一样容易阅读和推理。 \n\n### 13.3.1 await Expressions\nThe await keyword takes a Promise and turns it back into a return value or a thrown exception. Given a Promise object p, the expression await p waits until p settles. If p fulfills, then the value of await p is the fulfillment value of p. On the other hand, if p is rejected, then the await p expression throws the rejection value of p. We don’t usually use await with a variable that holds a Promise; instead, we use it before the invocation of a function that returns a Promise:\n\n> 关键字 await 接受一个 Promise，并将其转换为返回值或引发的异常。给定一个 Promise 对象 p，表达式 await p 等待直到 p 敲定。如果 p 兑现，则等待 p 的值就是 p 的兑现值。另一方面，如果 p 被拒绝，则 await p 表达式将抛出 p 的拒绝值。我们通常不将 await 与保存 Promise 的变量一起使用；相反，我们在调用返回 Promise 的函数之前使用它：\n\n```js\nlet response = await fetch(\"/api/user/profile\");\nlet profile = await response.json();\n```\nIt is critical to understand right away that the await keyword does not cause your program to block and literally do nothing until the specified Promise settles. The code remains asynchronous, and the await simply disguises this fact. This means that any code that uses await is itself asynchronous.\n\n> 立即了解至关重要的一点是，在指定的 Promise 敲定之前，await 关键字不会导致程序阻塞，并且实际上什么也不做。代码保持异步，并且 await 只是掩盖了这一事实。这意味着使用 await 的任何代码本身都是异步的。 \n\n### 13.3.2 async Functions\nBecause any code that uses await is asynchronous, there is one critical rule: you can only use the await keyword within functions that have been declared with the async keyword. Here’s a version of the getHighScore() function from earlier in the chapter, rewritten to use async and await:\n\n> 因为任何使用 await 的代码都是异步的，所以有一个关键规则：只能在使用 async 关键字声明的函数中使用 await 关键字。下面是本章前面的 getHighScore() 函数的一个版本，使用 async 和 await 重写：\n\n```js\nasync function getHighScore() {\n    let response = await fetch(\"/api/user/profile\");\n    let profile = await response.json();\n    return profile.highScore;\n}\n```\nDeclaring a function async means that the return value of the function will be a Promise even if no Promise-related code appears in the body of the function. If an async function appears to return normally, then the Promise object that is the real return value of the function will resolve to that apparent return value. And if an async function appears to throw an exception, then the Promise object that it returns will be rejected with that exception.\n\n> 异步声明函数意味着函数的返回值将是一个 Promise，即使函数体中没有出现与 Promise 相关的代码。如果异步函数看起来正常返回，那么作为函数实际返回值的 Promise 对象将决议为该返回值。如果一个异步函数出现抛出异常，那么它返回的 Promise 对象将被那个异常拒绝。\n\nThe getHighScore() function is declared async, so it returns a Promise. And because it returns a Promise, we can use the await keyword with it:\n\n> getHighScore() 函数被声明为异步，因此它返回一个承诺。因为它返回一个承诺，所以我们可以使用 await 关键字：\n\n```js\ndisplayHighScore(await getHighScore());\n```\nBut remember, that line of code will only work if it is inside another async function! You can nest await expressions within async functions as deeply as you want. But if you’re at the top level [^2] or are inside a function that is not async for some reason, then you can’t use await and have to deal with a returned Promise in the regular way:\n\n> 但是请记住，只有在另一个异步函数中，该行代码才有效！可以根据需要在异步函数中嵌套任何层 await 表达式。但是，如果处于最高级别 [^2] 或由于某种原因而处于不异步的函数内，那么您无法使用 await 并且必须以常规方式处理返回的 Promise： \n\n```js\ngetHighScore().then(displayHighScore).catch(console.error);\n```\nYou can use the async keyword with any kind of function. It works with the function keyword as a statement or as an expression. It works with arrow functions and with the method shortcut form in classes and object literals. (See Chapter 8 for more about the various ways to write functions.)\n\n> 可以将 async 关键字与任何函数一起使用。它用作于 function 关键字作为语句或表达式。它可与箭头函数以及类和对象字面量中的速记方法方式一起使用。（有关如何编写函数的各种方法，请参见第 8 章。） \n\n### 13.3.3 Awaiting Multiple Promises\nSuppose that we’ve written our getJSON() function using async:\n\n> 假设我们已经使用 async 编写了 getJSON() 函数： \n\n```js\nasync function getJSON(url) {\n    let response = await fetch(url);\n    let body = await response.json();\n    return body;\n}\n```\nAnd now suppose that we want to fetch two JSON values with this function:\n\n> 并且现在假设我们要用这个方法获取两个 JSON 值：\n\n```js\nlet value1 = await getJSON(url1);\nlet value2 = await getJSON(url2);\n```\nThe problem with this code is that it is unnecessarily sequential: the fetch of the second URL will not begin until the first fetch is complete. If the second URL does not depend on the value obtained from the first URL, then we should probably try to fetch the two values at the same time. This is a case where the Promise-based nature of async functions shows. In order to await a set of concurrently executing async functions, we use Promise.all() just as we would if working with Promises directly:\n\n> 此代码的问题在于，它不必要地是连续的：第二个 URL 的获取要等到第一次获取完成后才能开始。如果第二个 URL 不依赖于从第一个 URL 获得的值，那么我们可能应该尝试同时获取两个值。这是基于 Promise 的异步函数本质的一种情况。为了等待一组并发执行的异步函数，我们使用 Promise.all() 就像直接使用 Promise 一样： \n\n```js\nlet [value1, value2] = await Promise.all([getJSON(url1), getJSON(url2)]);\n```\n### 13.3.4 Implementation Details\nFinally, in order to understand how async functions work, it may help to think about what is going on under the hood.\n\n> 最后，为了了解异步功能是如何工作的，考虑一下幕后发生了什么可能会有所帮助。\n\nSuppose you write an async function like this:\n\n> 假设写这样的一个异步函数：\n\n```js\nasync function f(x) { /* body */ }\n```\nYou can think about this as a Promise-returning function wrapped around the body of your original function:\n\n> 可以将其视为包装原始函数主体的 Promise 返回函数：\n\n```js\nfunction f(x) {\n    return new Promise(function(resolve, reject) {\n        try {\n            resolve((function(x) { /* body */ })(x));\n        }\n        catch(e) {\n            reject(e);\n        }\n    });\n}\n```\nIt is harder to express the await keyword in terms of a syntax transformation like this one. But think of the await keyword as a marker that breaks a function body up into separate, synchronous chunks. An ES2017 interpreter can break the function body up into a sequence of separate subfunctions, each of which gets passed to the then() method of the await-marked Promise that precedes it.\n\n> 用像这样的语法转换来表达 await 关键字比较困难。但是，将 await 关键字视为将函数主体分解为单独的同步块的标记。ES2017 解释器可以将函数主体分解为一系列单独的子函数，每个子函数都传递给位于其前面 await 标记的 Promise 的 then() 方法。\n\n## 13.4 Asynchronous Iteration\nWe began this chapter with a discussion of callback- and event-based asynchrony, and when we introduced Promises, we noted that they were useful for single-shot asynchronous computations but were not suitable for use with sources of repetitive asynchronous events, such as setInterval(), the “click” event in a web browser, or the “data” event on a Node stream. Because single Promises do not work for sequences of asynchronous events, we also cannot use regular async functions and the await statements for these things.\n\n> 在本章的开头我们讨论了基于回调和基于事件的异步，当我们介绍 Promise 时，我们注意到它们对于单次异步计算很有用，但不适用于重复性异步事件的代码，例如 setInterval()，网络浏览器中的“click”事件或 Node 流上的“data”事件。因为单个 Promise 不适用于异步事件序列，所以我们也不能对这些事物使用常规的异步函数和 await 语句。\n\nES2018 provides a solution, however. Asynchronous iterators are like the iterators described in Chapter 12, but they are Promise-based and are meant to be used with a new form of the for/of loop: for/await.\n\n> 但是 ES2018 提供了一个解决方案。异步迭代器类似于第 12 章中描述的迭代器，但是它们基于 Promise，并且打算与 for/of 循环一起使用的新形式：for/await。\n\n### 13.4.1 The for/await Loop\nNode 12 makes its readable streams asynchronously iterable. This means you can read successive chunks of data from a stream with a for/await loop like this one:\n\n> Node 12 使其可读流可以异步迭代。这意味着可以使用如下所示的 for/await 循环从流中读取连续的数据块：\n\n```js\nconst fs = require(\"fs\");\n\nasync function parseFile(filename) {\n    let stream = fs.createReadStream(filename, { encoding: \"utf-8\"});\n    for await (let chunk of stream) {\n        parseChunk(chunk); // Assume parseChunk() is defined elsewhere\n    }\n}\n```\nLike a regular await expression, the for/await loop is Promise-based. Roughly speaking, the asynchronous iterator produces a Promise and the for/await loop waits for that Promise to fulfill, assigns the fulfillment value to the loop variable, and runs the body of the loop. And then it starts over, getting another Promise from the iterator and waiting for that new Promise to fulfill.\n\n> 像普通的 await 表达式一样，for/await 循环是基于 promise 的。粗略地说，异步迭代器产生一个 Promise，for/await 循环等待该 Promise 兑现，将兑现值分配给循环变量，然后运行循环的主体。然后重新开始，从迭代器中获得另一个 Promise，然后等待该新 Promise 兑现。\n\nSuppose you have an array of URLs:\n\n> 假设有一个 URL 数组：\n\n```js\nconst urls = [url1, url2, url3];\n```\nYou can call fetch() on each URL to get an array of Promises:\n\n> 可以在每个 URL 上调用 fetch() 以获取一个 promise 数组：\n\n```js\nconst promises = urls.map(url => fetch(url));\n```\nWe saw earlier in the chapter that we could now use Promise.all() to wait for all the Promises in the array to be fulfilled. But suppose we want the results of the first fetch as soon as they become available and don’t want to wait for all the URLs to be fetched. (Of course, the first fetch might take longer than any of the others, so this is not necessarily faster than using Promise.all().) Arrays are iterable, so we can iterate through the array of promises with a regular for/of loop:\n\n> 我们在本章的前面已经看到，我们现在可以使用 Promise.all() 等待数组中的所有 promise 都已兌現。但是，假设我们希望第一个提取的结果尽快可用，并且不想等待所有 URL 都被获取。（当然，第一次获取可能比其他任何获取都要花费更长的时间，因此不一定比使用 Promise.all() 更快。）数组是可迭代的，因此我们可以使用常规的 for/of 对数组进行遍历：\n\n```js\nfor(const promise of promises) {\n    response = await promise;\n    handle(response);\n}\n```\nThis example code uses a regular for/of loop with a regular iterator. But because this iterator returns Promises, we can also use the new for/await for slightly simpler code:\n\n> 此示例代码使用 for/of 循环遍历常规迭代器。但是因为此迭代器返回 Promise，所以我们还可以将新的 for/await 稍微简化下代码：\n\n```js\nfor await (const response of promises) {\n    handle(response);\n}\n```\nIn this case, the for/await loop just builds the await call into the loop and makes our code slightly more compact, but the two examples do exactly the same thing. Importantly, both examples will only work if they are within functions declared async; a for/await loop is no different than a regular await expression in that way.\n\n> 在这种情况下，for/await 循环仅将 await 调用构建到循环中，并使我们的代码稍微紧凑一些，但是两个示例的作用完全相同。重要的是，这两个示例只有在声明为异步的函数中时才起作用。for/await 循环与常规 await 表达式没有什么不同。\n\nIt is important to realize, however, that we’re using for/await with a regular iterator in this example. Things are more interesting with fully asynchronous iterators.\n\n> 但是，重要的是要意识到，在此示例中，我们正在使用 for/wait 作用于常规迭代器的。作用于完全异步的迭代器使事情变得更加有趣。\n\n### 13.4.2 Asynchronous Iterators\nLet’s review some terminology from Chapter 12. An iterable object is one that can be used with a for/of loop. It defines a method with the symbolic name Symbol.iterator. This method returns an iterator object. The iterator object has a next() method, which can be called repeatedly to obtain the values of the iterable object. The next() method of the iterator object returns iteration result objects. The iteration result object has a value property and/or a done property.\n\n> 让我们回顾一下第 12 章中的一些术语。可迭代对象是可以与 for/of 循环一起使用的对象。它定义了一个名称为 Symbol.iterator 的方法。此方法返回一个迭代器对象。迭代器对象具有 next() 方法，可以重复调用该方法以获得可迭代对象的值。迭代器对象的 next() 方法返回迭代结果对象。迭代结果对象具有 value 属性和或或 done 属性。\n\nAsynchronous iterators are quite similar to regular iterators, but there are two important differences. First, an asynchronously iterable object implements a method with the symbolic name Symbol.asyncIterator instead of Symbol.iterator. (As we saw earlier, for/await is compatible with regular iterable objects but it prefers asynchronously iterable objects, and tries the Symbol.asyncIterator method before it tries the Symbol.iterator method.) Second, the next() method of an asynchronous iterator returns a Promise that resolves to an iterator result object instead of returning an iterator result object directly.\n\n> 异步迭代器与常规迭代器非常相似，但是有两个重要的区别。 首先，一个异步可迭代对象以符号名称 Symbol.asyncIterator 而不是 Symbol.iterator 实现一个方法。（如前所述，for/await 与常规可迭代对象兼容，但是它更喜欢异步可迭代对象，并在尝试 Symbol.iterator 方法之前先尝试使用 Symbol.asyncIterator 方法。）其次，异步迭代器的 next() 方法返回解析为迭代器结果对象的 Promise，而不是直接返回迭代器结果对象。\n\n**NOTE**\nIn the previous section, when we used for/await on a regular, synchronously iterable array of Promises, we were working with synchronous iterator result objects in which the value property was a Promise object but the done property was synchronous. True asynchronous iterators return Promises for iteration result objects, and both the value and the done properties are asynchronous. The difference is a subtle one: with asynchronous iterators, the choice about when iteration ends can be made asynchronously.\n\n> 在上一节中，当我们在常规的、同步可迭代的 Promise 数组上使用 for/await 时，我们正在使用同步迭代器结果对象，其中 value 属性是 Promise 对象，但 done 属性是同步的。真正的异步迭代器为迭代结果对象返回 Promise，并且 value 和 done 属性都是异步的。这是一个微妙的区别：使用异步迭代器，可以异步选择何时结束迭代。\n\n### 13.4.3 Asynchronous Generators\nAs we saw in Chapter 12, the easiest way to implement an iterator is often to use a generator. The same is true for asynchronous iterators, which we can implement with generator functions that we declare async. An async generator has the features of async functions and the features of generators: you can use await as you would in a regular async function, and you can use yield as you would in a regular generator. But values that you yield are automatically wrapped in Promises. Even the syntax for async generators is a combination: async function and function * combine into async function *. Here is an example that shows how you might use an async generator and a for/await loop to repetitively run code at fixed intervals using loop syntax instead of a setInterval() callback function:\n\n> 正如我们在第 12 章中看到的那样，实现迭代器的最简单方法通常是使用生成器。异步迭代器也是如此，我们可以使用声明为异步的生成器函数来实现。异步生成器具有异步特性和生成器特性：可以像在常规异步函数中一样使用 await，并且可以像在常规生成器中一样使用 yield。但是，产生的值会自动包装在 Promise 中。甚至异步生成器的语法也是一个组合：异步函数和 function* 组合为 async function*。这是一个示例，描述如何使用异步生成器和 for/await 循环代替 setInterval() 回调函数以固定的间隔重复运行代码：\n\n```js\n// A Promise-based wrapper around setTimeout() that we can use await with.\n// Returns a Promise that fulfills in the specified number of milliseconds\nfunction elapsedTime(ms) {\n    return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n// An async generator function that increments a counter and yields it\n// a specified (or infinite) number of times at a specified interval.\nasync function* clock(interval, max=Infinity) {\n    for(let count = 1; count <= max; count++) { // regular for loop\n        await elapsedTime(interval);            // wait for time to pass\n        yield count;                            // yield the counter\n    }\n}\n\n// A test function that uses the async generator with for/await\nasync function test() {                       // Async so we can use for/await\n    for await (let tick of clock(300, 100)) { // Loop 100 times every 300ms\n        console.log(tick);\n    }\n}\n```\n### 13.4.4 Implementing Asynchronous Iterators\nInstead of using async generators to implement asynchronous iterators, it is also possible to implement them directly by defining an object with a Symbol.asyncIterator() method that returns an object with a next() method that returns a Promise that resolves to an iterator result object. In the following code, we re-implement the clock() function from the preceding example so that it is not a generator and instead just returns an asynchronously iterable object. Notice that the next() method in this example does not explicitly return a Promise; instead, we just declare next() to be async:\n\n> 除了使用异步生成器来实现异步迭代器外，还可以通过使用 Symbol.asyncIterator() 方法定义一个对象来直接实现它们，而 Symbol.asyncIterator() 方法将返回一个对象，而 next() 方法将返回一个决议为迭代器结果对象的 Promise。在下面的代码中，我们重新实现了上一个示例中的 clock() 函数，因此它不是生成器，而仅是返回一个异步可迭代的对象。请注意，此示例中的 next() 方法未明确返回 Promise；相反，我们只声明 next() 是异步的：\n\n```js\nfunction clock(interval, max=Infinity) {\n    // A Promise-ified version of setTimeout that we can use await with.\n    // Note that this takes an absolute time instead of an interval.\n    function until(time) {\n        return new Promise(resolve => setTimeout(resolve, time - Date.now()));\n    }\n\n    // Return an asynchronously iterable object\n    return {\n        startTime: Date.now(),  // Remember when we started\n        count: 1,               // Remember which iteration we're on\n        async next() {          // The next() method makes this an iterator\n            if (this.count > max) {     // Are we done?\n                return { done: true };  // Iteration result indicating done\n            }\n            // Figure out when the next iteration should begin,\n            let targetTime = this.startTime + this.count * interval;\n            // wait until that time,\n            await until(targetTime);\n            // and return the count value in an iteration result object.\n            return { value: this.count++ };\n        },\n        // This method means that this iterator object is also an iterable.\n        [Symbol.asyncIterator]() { return this; }\n    };\n}\n```\nThis iterator-based version of the clock() function fixes a flaw in the generator-based version. Note that, in this newer code, we target the absolute time at which each iteration should begin and subtract the current time from that in order to compute the interval that we pass to setTimeout(). If we use clock() with a for/await loop, this version will run loop iterations more precisely at the specified interval because it accounts for the time required to actually run the body of the loop. But this fix isn’t just about timing accuracy. The for/await loop always waits for the Promise returned by one iteration to be fulfilled before it begins the next iteration. But if you use an asynchronous iterator without a for/await loop, there is nothing to prevent you from calling the next() method whenever you want. With the generator-based version of clock(), if you call the next() method three times sequentially, you’ll get three Promises that will all fulfill at almost exactly the same time, which is probably not what you want. The iterator-based version we’ve implemented here does not have that problem.\n\n> 这个基于迭代器版本的 clock() 函数修复了基于生成器版本中的一个缺陷。请注意，在此代码中，我们在每次迭代开始时设置了绝对目标时间，并将其与当前时间的差值作为间隔传递给 setTimeout()。如果我们将 clock() 与 for/await 循环一起使用，则此版本将在指定的时间间隔内更精确地运行循环迭代，因为它考虑了实际运行循环主体所需的时间。但是，此修补程序不仅涉及定时精度。for/await 循环始终在开始下一次迭代之前等待一次迭代返回的 Promise 被兑现。但是，如果使用不带 for/await 循环的异步迭代器，则没有什么可以阻止在需要时调用 next() 方法。使用基于生成器的 clock() 版本，如果依次调用 next() 方法三遍，将获得三个 Promise，这些 Promise 几乎都在同一时间完成，这可能不是想要的结果。我们在这里实现的基于迭代器的版本没有这个问题。\n\nThe benefit of asynchronous iterators is that they allow us to represent streams of asynchronous events or data. The clock() function discussed previously was fairly simple to write because the source of the asynchrony was the setTimeout() calls we were making ourselves. But when we are trying to work with other asynchronous sources, such as the triggering of event handlers, it becomes substantially harder to implement asynchronous iterators—we typically have a single event handler function that responds to events, but each call to the iterator’s next() method must return a distinct Promise object, and multiple calls to next() may occur before the first Promise resolves. This means that any asynchronous iterator method must be able to maintain an internal queue of Promises that it resolves in order as asynchronous events are occurring. If we encapsulate this Promise-queueing behavior into an AsyncQueue class, then it becomes much easier to write asynchronous iterators based on AsyncQueue. [^3]\n\n> 异步迭代器的好处在于，它们允许我们表示异步事件或数据流。前面讨论的 clock() 函数编写起来非常简单，因为异步源是我们自己进行的 setTimeout() 调用。但是，当我们尝试与其他异步源一起使用时，例如事件处理程序的触发，实现异步迭代器的难度将大大提高————我们通常只有一个事件处理程序函数来响应事件，但是每次调用迭代器的 next() 方法一定返回一个不同的 Promise 对象，并且在第一个 Promise 决议之前，可能会多次调用 next()。这意味着，任何异步迭代器方法都必须能够维护一个内部的 Promise 队列，该队列将在响应异步事件时按顺序进行决议。如果我们将此 Promise 有序列的行为封装到 AsyncQueue 类中，那么基于 AsyncQueue 编写异步迭代器将变得更加容易。 [^3]\n\nThe AsyncQueue class that follows has enqueue() and dequeue() methods as you’d expect for a queue class. The dequeue() method returns a Promise rather than an actual value, however, which means that it is OK to call dequeue() before enqueue() has ever been called. The AsyncQueue class is also an asynchronous iterator, and is intended to be used with a for/await loop whose body runs once each time a new value is asynchronously enqueued. (AsyncQueue has a close() method. Once called, no more values can be enqueued. When a closed queue is empty, the for/await loop will stop looping.)\n\n> 正如期望的那样，后面的 AsyncQueue 类具有 enqueue() 和 dequeue() 方法。但是，dequeue() 方法返回 Promise 而不是实际值，这意味着可以在调用 enqueue() 之前调用 dequeue()。AsyncQueue 类也是一个异步迭代器，旨在与 for/await 循环一起使用，该循环的主体在每次将新值异步排队时都运行一次。（AsyncQueue 有一个 close() 方法。一旦被调用，就不能将更多的值加入队列。当关闭的队列为空时，for/await 循环将停止遍历。）\n\nNote that the implementation of AsyncQueue does not use async or await and instead works directly with Promises. The code is somewhat complicated, and you can use it to test your understanding of the material we’ve covered in this long chapter. Even if you don’t fully understand the AsyncQueue implementation, do take a look at the shorter example that follows it: it implements a simple but very interesting asynchronous iterator on top of AsyncQueue.\n\n> 请注意，AsyncQueue 的实现不使用异步或等待，而是直接与 Promise 一起使用。该代码有些复杂，可以使用它来测试对本长篇文章所涉及内容的理解。即使您不完全了解 AsyncQueue 的实现，也请看一下它后面的简短示例：它在 AsyncQueue 之上实现了一个简单但非常有趣的异步迭代器。\n\n```js\n/**\n * An asynchronously iterable queue class. Add values with enqueue()\n * and remove them with dequeue(). dequeue() returns a Promise, which\n * means that values can be dequeued before they are enqueued. The\n * class implements [Symbol.asyncIterator] and next() so that it can\n * be used with the for/await loop (which will not terminate until\n * the close() method is called.)\n */\nclass AsyncQueue {\n    constructor() {\n        // Values that have been queued but not dequeued yet are stored here\n        this.values = [];\n        // When Promises are dequeued before their corresponding values are\n        // queued, the resolve methods for those Promises are stored here.\n        this.resolvers = [];\n        // Once closed, no more values can be enqueued, and no more unfulfilled\n        // Promises returned.\n        this.closed = false;\n    }\n\n    enqueue(value) {\n        if (this.closed) {\n            throw new Error(\"AsyncQueue closed\");\n        }\n        if (this.resolvers.length > 0) {\n            // If this value has already been promised, resolve that Promise\n            const resolve = this.resolvers.shift();\n            resolve(value);\n        }\n        else {\n            // Otherwise, queue it up\n            this.values.push(value);\n        }\n    }\n\n    dequeue() {\n        if (this.values.length > 0) {\n            // If there is a queued value, return a resolved Promise for it\n            const value = this.values.shift();\n            return Promise.resolve(value);\n        }\n        else if (this.closed) {\n            // If no queued values and we're closed, return a resolved\n            // Promise for the \"end-of-stream\" marker\n            return Promise.resolve(AsyncQueue.EOS);\n        }\n        else {\n            // Otherwise, return an unresolved Promise,\n            // queuing the resolver function for later use\n            return new Promise((resolve) => { this.resolvers.push(resolve); });\n        }\n    }\n\n    close() {\n        // Once the queue is closed, no more values will be enqueued.\n        // So resolve any pending Promises with the end-of-stream marker\n        while(this.resolvers.length > 0) {\n            this.resolvers.shift()(AsyncQueue.EOS);\n        }\n        this.closed = true;\n    }\n\n    // Define the method that makes this class asynchronously iterable\n    [Symbol.asyncIterator]() { return this; }\n\n    // Define the method that makes this an asynchronous iterator. The\n    // dequeue() Promise resolves to a value or the EOS sentinel if we're\n    // closed. Here, we need to return a Promise that resolves to an\n    // iterator result object.\n    next() {\n        return this.dequeue().then(value => (value === AsyncQueue.EOS)\n                                   ? { value: undefined, done: true }\n                                   : { value: value, done: false });\n    }\n}\n\n// A sentinel value returned by dequeue() to mark \"end of stream\" when closed\nAsyncQueue.EOS = Symbol(\"end-of-stream\");\n```\nBecause this AsyncQueue class defines the asynchronous iteration basics, we can create our own, more interesting asynchronous iterators simply by asynchronously queueing values. Here’s an example that uses AsyncQueue to produce a stream of web browser events that can be handled with a for/await loop:\n\n> 因为 AsyncQueue 类定义了异步迭代基础，所以我们可以简单地通过异步排队值来创建更有趣的自定义异步迭代器。这是一个使用 AsyncQueue 生成可通过 for/await 循环处理的 web 浏览器事件流的示例：\n\n```js\n// Push events of the specified type on the specified document element\n// onto an AsyncQueue object, and return the queue for use as an event stream\nfunction eventStream(elt, type) {\n    const q = new AsyncQueue();                  // Create a queue\n    elt.addEventListener(type, e=>q.enqueue(e)); // Enqueue events\n    return q;\n}\n\nasync function handleKeys() {\n    // Get a stream of keypress events and loop once for each one\n    for await (const event of eventStream(document, \"keypress\")) {\n        console.log(event.key);\n    }\n}\n```\n## 13.5 Summary\nIn this chapter, you have learned:\n\n> 在本章中，您学习了：\n\n- Most real-world JavaScript programming is asynchronous.\n\n> 大多数真实的 JavaScript 程序都是异步的。\n\n- Traditionally, asynchrony has been handled with events and callback functions. This can get complicated, however, because you can end up with multiple levels of callbacks nested inside other callbacks, and because it is difficult to do robust error handling.\n\n> 传统上，异步是通过事件和回调函数来处理的。但是，这可能会变得复杂，因为最终可能会嵌套在其他回调中嵌套的多个级别的回调，并且因为很难进行可靠的异常处理。\n\n- Promises provide a new way of structuring callback functions. If used correctly (and unfortunately, Promises are easy to use incorrectly), they can convert asynchronous code that would have been nested into linear chains of then() calls where one asynchronous step of a computation follows another. Also, Promises allow you to centralize your error-handling code into a single catch() call at the end of a chain of then() calls.\n\n> Promise 提供了构造回调函数的新方法。如果正确使用（不幸的是，Promise 易于错误使用），它们可以将异步代码转换为嵌套在调用 then() 的线性链中的代码，一个计算的异步步骤跟随在其他之后。而且，Promise 允许将异常处理代码集中到一个 then() 调用链末尾的单个 catch() 调用中。\n\n- The async and await keywords allow us to write asynchronous code that is Promise-based under the hood but that looks like synchronous code. This makes the code easier to understand and reason about. If a function is declared async, it will implicitly return a Promise. Inside an async function, you can await a Promise (or a function that returns a Promise) as if the Promise value was synchronously computed.\n\n> async 和 await 关键字允许我们编写异步代码，该代码基于 Promise，但看起来像同步代码。这使代码更易于理解和推理。如果一个函数被声明为 async，它将隐式返回一个 Promise。在异步函数内部，可以 await Promise（或返回 Promise 的函数），就像 Promise 值是同步计算的一样。\n\n- Objects that are asynchronously iterable can be used with a for/await loop. You can create asynchronously iterable objects by implementing a [Symbol.asyncIterator]() method or by invoking an async function * generator function. Asynchronous iterators provide an alternative to “data” events on streams in Node and can be used to represent a stream of user input events in client-side JavaScript.\n\n> 异步可迭代的对象可以与 for/await 循环一起使用。可以通过实现 \\[Symbol.asyncIterator]() 方法或调用 async function* 生成器函数来创建异步可迭代对象。异步迭代器为 Node 中流的“data”事件提供了一种替代方法，可用于表示客户端 JavaScript 中的用户输入事件的流。\n\n[^1]: The XMLHttpRequest class has nothing in particular to do with XML. In modern client-side JavaScript, it has largely been replaced by the fetch() API, which is covered in §15.11.1. The code example shown here is the last XMLHttpRequest-based example remaining in this book.\n[^2]: You can typically use await at the top level in a browser’s developer console. And there is a pending proposal to allow top-level await in a future version of JavaScript.\n[^3]: I learned about this approach to asynchronous iteration from the blog of Dr. Axel Rauschmayer, [https://2ality.com]().\n\n> [^1]: XMLHttpRequest 类与 XML 无关。在现代的客户端 JavaScript 中，它已被 fetch() API 取代，该 API 已在 §15.11.1 中进行了介绍。此处显示的代码示例是本书中最后一个基于 XMLHttpRequest 的示例。 \n> [^2]: 通常，可以在浏览器的开发人员控制台的顶层使用 await。还有一个悬而未决的建议，允许在将来的 JavaScript 版本中进行顶级 await。 \n> [^3]: 我从 Dr. Axel Rauschmayer 的博客[https://2ality.com]()中了解了这种异步迭代方法。"
  },
  {
    "path": "content/posts/ch14.md",
    "content": "---\ntitle: \"第 14 章 元编程\"\ndate: 2020-11-02T22:18:29+08:00\n---\n\nThis chapter covers a number of advanced JavaScript features that are not commonly used in day-to-day programming but that may be valuable to programmers writing reusable libraries and of interest to anyone who wants to tinker with the details about how JavaScript objects behave.\n\n> 本章涵盖了一些高级的JavaScript特性，这些特性在日常编程中并不常用，但对于编写可重用库的程序员来说可能很有价值，对于那些想要修改JavaScript对象行为细节的人来说也很有兴趣。\n\nMany of the features described here can loosely be described as “metaprogramming”: if regular programming is writing code to manipulate data, then metaprogramming is writing code to manipulate other code. In a dynamic language like JavaScript, the lines between programming and metaprogramming are blurry—even the simple ability to iterate over the properties of an object with a for/in loop might be considered “meta” by programmers accustomed to more static languages.\n\n> 这里描述的许多特性可以粗略地描述为“元编程”:如果常规编程是编写代码来操作数据，那么元编程就是编写代码来操作其他代码。在像JavaScript这样的动态语言中，编程和元编程之间的界限是模糊的——即使是使用for/ In循环迭代对象属性的简单能力，对于习惯了更多静态语言的程序员来说也可能被认为是“元”的。\n\nThe metaprogramming topics covered in this chapter include:\n\n> 本章涉及的元编程主题包括:\n\n- §14.1 Controlling the enumerability, deleteability, and configurability of object properties\n- §14.2 Controlling the extensibility of objects, and creating “sealed” and “frozen” objects\n- §14.3 Querying and setting the prototypes of objects\n- §14.4 Fine-tuning the behavior of your types with well-known Symbols\n- §14.5 Creating DSLs (domain-specific languages) with template tag functions\n- §14.6 Probing objects with reflect methods\n- §14.7 Controlling object behavior with Proxy\n\n---\n\n> - §14.1控制对象属性的可枚举性、可删除性和可配置性\n> - §14.2控制对象的可扩展性，并创建“密封”和“冻结”对象\n> - §14.3查询和设置对象的原型\n> - §14.4用众所周知的符号微调类型的行为\n> - §14.5用模板标签函数创建dsl(领域特定语言\n> - §14.6用反射方法探测对象\n> - §14.7用代理控制对象行为\n\n## 14.1 Property Attributes\n\nThe properties of a JavaScript object have names and values, of course, but each property also has three associated attributes that specify how that property behaves and what you can do with it:\n\n> 当然，JavaScript对象的属性有名称和值，但是每个属性也有三个相关的属性，它们指定属性的行为方式以及你可以用它做什么:\n\n- The writable attribute specifies whether or not the value of a property can change.\n- The enumerable attribute specifies whether the property is enumerated by the for/in loop and the Object.keys() method.\n- The configurable attribute specifies whether a property can be deleted and also whether the property’s attributes can be changed.\n\n---\n\n> - writable属性指定属性的值是否可以更改。\n> - enumerable属性指定该属性是否由for/in循环和Object.keys()方法枚举。\n> - 可配置属性指定是否一个属性可以删除，也是否属性的属性可以改变。\n\nProperties defined in object literals or by ordinary assignment to an object are writable, enumerable, and configurable. But many of the properties defined by the JavaScript standard library are not.\n\n> 用对象文本或通过对对象的普通赋值定义的属性是可写的、可枚举的和可配置的。但是JavaScript标准库定义的许多属性不是这样的。\n\nThis section explains the API for querying and setting property attributes. This API is particularly important to library authors because:\n\n> 本节解释用于查询和设置属性属性的API。这个API对于库的作者来说特别重要，因为:\n\n- It allows them to add methods to prototype objects and make them non-enumerable, like built-in methods.\n- It allows them to “lock down” their objects, defining properties that cannot be changed or deleted.\n\n---\n\n> - 它允许向原型对象添加方法并使其不可枚举，就像内置方法一样。\n> - 它允许他们“锁定”他们的对象，定义不能改变或删除的属性。\n\nRecall from §6.10.6 that, while “data properties” have a value, “accessor properties” have a getter and/or a setter method instead. For the purposes of this section, we are going to consider the getter and setter methods of an accessor property to be property attributes. Following this logic, we’ll even say that the value of a data property is an attribute as well. Thus, we can say that a property has a name and four attributes. The four attributes of a data property are value, writable, enumerable, and configurable. Accessor properties don’t have a value attribute or a writable attribute: their writability is determined by the presence or absence of a setter. So the four attributes of an accessor property are get, set, enumerable, and configurable.\n\nThe JavaScript methods for querying and setting the attributes of a property use an object called a property descriptor to represent the set of four attributes. A property descriptor object has properties with the same names as the attributes of the property it describes. Thus, the property descriptor object of a data property has properties named value, writable, enumerable, and configurable. And the descriptor for an accessor property has get and set properties instead of value and writable. The writable, enumerable, and configurable properties are boolean values, and the get and set properties are function values.\n\nTo obtain the property descriptor for a named property of a specified object, call Object.getOwnPropertyDescriptor():\n```js\n// Returns {value: 1, writable:true, enumerable:true, configurable:true}\nObject.getOwnPropertyDescriptor({x: 1}, \"x\");\n\n// Here is an object with a read-only accessor property\nconst random = {\n    get octet() { return Math.floor(Math.random()*256); },\n};\n\n// Returns { get: /*func*/, set:undefined, enumerable:true, configurable:true}\nObject.getOwnPropertyDescriptor(random, \"octet\");\n\n// Returns undefined for inherited properties and properties that don't exist.\nObject.getOwnPropertyDescriptor({}, \"x\")        // => undefined; no such prop\nObject.getOwnPropertyDescriptor({}, \"toString\") // => undefined; inherited\n```\nAs its name implies, Object.getOwnPropertyDescriptor() works only for own properties. To query the attributes of inherited properties, you must explicitly traverse the prototype chain. (See Object.getPrototypeOf() in §14.3); see also the similar Reflect.getOwnPropertyDescriptor() function in §14.6.)\n\nTo set the attributes of a property or to create a new property with the specified attributes, call Object.defineProperty(), passing the object to be modified, the name of the property to be created or altered, and the property descriptor object:\n```js\nlet o = {};  // Start with no properties at all\n// Add a non-enumerable data property x with value 1.\nObject.defineProperty(o, \"x\", {\n    value: 1,\n    writable: true,\n    enumerable: false,\n    configurable: true\n});\n\n// Check that the property is there but is non-enumerable\no.x            // => 1\nObject.keys(o) // => []\n\n// Now modify the property x so that it is read-only\nObject.defineProperty(o, \"x\", { writable: false });\n\n// Try to change the value of the property\no.x = 2;      // Fails silently or throws TypeError in strict mode\no.x           // => 1\n\n// The property is still configurable, so we can change its value like this:\nObject.defineProperty(o, \"x\", { value: 2 });\no.x           // => 2\n\n// Now change x from a data property to an accessor property\nObject.defineProperty(o, \"x\", { get: function() { return 0; } });\no.x           // => 0\n```\nThe property descriptor you pass to Object.defineProperty() does not have to include all four attributes. If you’re creating a new property, then omitted attributes are taken to be false or undefined. If you’re modifying an existing property, then the attributes you omit are simply left unchanged. Note that this method alters an existing own property or creates a new own property, but it will not alter an inherited property. See also the very similar function Reflect.defineProperty() in §14.6.\n\nIf you want to create or modify more than one property at a time, use Object.defineProperties(). The first argument is the object that is to be modified. The second argument is an object that maps the names of the properties to be created or modified to the property descriptors for those properties. For example:\n```js\nlet p = Object.defineProperties({}, {\n    x: { value: 1, writable: true, enumerable: true, configurable: true },\n    y: { value: 1, writable: true, enumerable: true, configurable: true },\n    r: {\n        get() { return Math.sqrt(this.x*this.x + this.y*this.y); },\n        enumerable: true,\n        configurable: true\n    }\n});\np.r  // => Math.SQRT2\n```\nThis code starts with an empty object, then adds two data properties and one read-only accessor property to it. It relies on the fact that Object.defineProperties() returns the modified object (as does Object.defineProperty()).\n\nThe Object.create() method was introduced in §6.2. We learned there that the first argument to that method is the prototype object for the newly created object. This method also accepts a second optional argument, which is the same as the second argument to Object.defineProperties(). If you pass a set of property descriptors to Object.create(), then they are used to add properties to the newly created object.\n\nObject.defineProperty() and Object.defineProperties() throw TypeError if the attempt to create or modify a property is not allowed. This happens if you attempt to add a new property to a non-extensible (see §14.2) object. The other reasons that these methods might throw TypeError have to do with the attributes themselves. The writable attribute governs attempts to change the value attribute. And the configurable attribute governs attempts to change the other attributes (and also specifies whether a property can be deleted). The rules are not completely straightforward, however. It is possible to change the value of a nonwritable property if that property is configurable, for example. Also, it is possible to change a property from writable to nonwritable even if that property is nonconfigurable. Here are the complete rules. Calls to Object.defineProperty() or Object.defineProperties() that attempt to violate them throw a TypeError:\n\nIf an object is not extensible, you can edit its existing own properties, but you cannot add new properties to it.\n\nIf a property is not configurable, you cannot change its configurable or enumerable attributes.\n\nIf an accessor property is not configurable, you cannot change its getter or setter method, and you cannot change it to a data property.\n\nIf a data property is not configurable, you cannot change it to an accessor property.\n\nIf a data property is not configurable, you cannot change its writable attribute from false to true, but you can change it from true to false.\n\nIf a data property is not configurable and not writable, you cannot change its value. You can change the value of a property that is configurable but nonwritable, however (because that would be the same as making it writable, then changing the value, then converting it back to nonwritable).\n\n§6.7 described the Object.assign() function that copies property values from one or more source objects into a target object. Object.assign() only copies enumerable properties, and property values, not property attributes. This is normally what we want, but it does mean, for example, that if one of the source objects has an accessor property, it is the value returned by the getter function that is copied to the target object, not the getter function itself. Example 14-1 demonstrates how we can use Object.getOwnPropertyDescriptor() and Object.defineProperty() to create a variant of Object.assign() that copies entire property descriptors rather than just copying property values.\n\nExample 14-1. Copying properties and their attributes from one object to another\n```js\n/*\n * Define a new Object.assignDescriptors() function that works like\n * Object.assign() except that it copies property descriptors from\n * source objects into the target object instead of just copying\n * property values. This function copies all own properties, both\n * enumerable and non-enumerable. And because it copies descriptors,\n * it copies getter functions from source objects and overwrites setter\n * functions in the target object rather than invoking those getters and\n * setters.\n *\n * Object.assignDescriptors() propagates any TypeErrors thrown by\n * Object.defineProperty(). This can occur if the target object is sealed\n * or frozen or if any of the source properties try to change an existing\n * non-configurable property on the target object.\n *\n * Note that the assignDescriptors property is added to Object with\n * Object.defineProperty() so that the new function can be created as\n * a non-enumerable property like Object.assign().\n */\nObject.defineProperty(Object, \"assignDescriptors\", {\n    // Match the attributes of Object.assign()\n    writable: true,\n    enumerable: false,\n    configurable: true,\n    // The function that is the value of the assignDescriptors property.\n    value: function(target, ...sources) {\n        for(let source of sources) {\n            for(let name of Object.getOwnPropertyNames(source)) {\n                let desc = Object.getOwnPropertyDescriptor(source, name);\n                Object.defineProperty(target, name, desc);\n            }\n\n            for(let symbol of Object.getOwnPropertySymbols(source)) {\n                let desc = Object.getOwnPropertyDescriptor(source, symbol);\n                Object.defineProperty(target, symbol, desc);\n            }\n        }\n        return target;\n    }\n});\n\nlet o = {c: 1, get count() {return this.c++;}}; // Define object with getter\nlet p = Object.assign({}, o);                   // Copy the property values\nlet q = Object.assignDescriptors({}, o);        // Copy the property descriptors\np.count   // => 1: This is now just a data property so\np.count   // => 1: ...the counter does not increment.\nq.count   // => 2: Incremented once when we copied it the first time,\nq.count   // => 3: ...but we copied the getter method so it increments.\n```\n## 14.2 Object Extensibility\nThe extensible attribute of an object specifies whether new properties can be added to the object or not. Ordinary JavaScript objects are extensible by default, but you can change that with the functions described in this section.\n\nTo determine whether an object is extensible, pass it to Object.isExtensible(). To make an object non-extensible, pass it to Object.preventExtensions(). Once you have done this, any attempt to add a new property to the object will throw a TypeError in strict mode and simply fail silently without an error in non-strict mode. In addition, attempting to change the prototype (see §14.3) of a non-extensible object will always throw a TypeError.\n\nNote that there is no way to make an object extensible again once you have made it non-extensible. Also note that calling Object.preventExtensions() only affects the extensibility of the object itself. If new properties are added to the prototype of a non-extensible object, the non-extensible object will inherit those new properties.\n\nTwo similar functions, Reflect.isExtensible() and Reflect.preventExtensions(), are described in §14.6.\n\nThe purpose of the extensible attribute is to be able to “lock down” objects into a known state and prevent outside tampering. The extensible attribute of objects is often used in conjunction with the configurable and writable attributes of properties, and JavaScript defines functions that make it easy to set these attributes together:\n\nObject.seal() works like Object.preventExtensions(), but in addition to making the object non-extensible, it also makes all of the own properties of that object nonconfigurable. This means that new properties cannot be added to the object, and existing properties cannot be deleted or configured. Existing properties that are writable can still be set, however. There is no way to unseal a sealed object. You can use Object.isSealed() to determine whether an object is sealed.\n\nObject.freeze() locks objects down even more tightly. In addition to making the object non-extensible and its properties nonconfigurable, it also makes all of the object’s own data properties read-only. (If the object has accessor properties with setter methods, these are not affected and can still be invoked by assignment to the property.) Use Object.isFrozen() to determine if an object is frozen.\n\nIt is important to understand that Object.seal() and Object.freeze() affect only the object they are passed: they have no effect on the prototype of that object. If you want to thoroughly lock down an object, you probably need to seal or freeze the objects in the prototype chain as well.\n\nObject.preventExtensions(), Object.seal(), and Object.freeze() all return the object that they are passed, which means that you can use them in nested function invocations:\n```js\n// Create a sealed object with a frozen prototype and a non-enumerable property\nlet o = Object.seal(Object.create(Object.freeze({x: 1}),\n                                  {y: {value: 2, writable: true}}));\n```\nIf you are writing a JavaScript library that passes objects to callback functions written by the users of your library, you might use Object.freeze() on those objects to prevent the user’s code from modifying them. This is easy and convenient to do, but there are trade-offs: frozen objects can interfere with common JavaScript testing strategies, for example.\n\n## 14.3 The prototype Attribute\nAn object’s prototype attribute specifies the object from which it inherits properties. (Review §6.2.3 and §6.3.2 for more on prototypes and property inheritance.) This is such an important attribute that we usually simply say “the prototype of o\" rather than “the prototype attribute of o.” Remember also that when prototype appears in code font, it refers to an ordinary object property, not to the prototype attribute: Chapter 9 explained that the prototype property of a constructor function specifies the prototype attribute of the objects created with that constructor.\n\nThe prototype attribute is set when an object is created. Objects created from object literals use Object.prototype as their prototype. Objects created with new use the value of the prototype property of their constructor function as their prototype. And objects created with Object.create() use the first argument to that function (which may be null) as their prototype.\n\nYou can query the prototype of any object by passing that object to Object.getPrototypeOf():\n```js\nObject.getPrototypeOf({})      // => Object.prototype\nObject.getPrototypeOf([])      // => Array.prototype\nObject.getPrototypeOf(()=>{})  // => Function.prototype\n```\nA very similar function, Reflect.getPrototypeOf(), is described in §14.6.\n\nTo determine whether one object is the prototype of (or is part of the prototype chain of) another object, use the isPrototypeOf() method:\n```js\nlet p = {x: 1};                   // Define a prototype object.\nlet o = Object.create(p);         // Create an object with that prototype.\np.isPrototypeOf(o)                // => true: o inherits from p\nObject.prototype.isPrototypeOf(p) // => true: p inherits from Object.prototype\nObject.prototype.isPrototypeOf(o) // => true: o does too\n```\nNote that isPrototypeOf() performs a function similar to the instanceof operator (see §4.9.4).\n\nThe prototype attribute of an object is set when the object is created and normally remains fixed. You can, however, change the prototype of an object with Object.setPrototypeOf():\n```js\nlet o = {x: 1};\nlet p = {y: 2};\nObject.setPrototypeOf(o, p); // Set the prototype of o to p\no.y      // => 2: o now inherits the property y\nlet a = [1, 2, 3];\nObject.setPrototypeOf(a, p); // Set the prototype of array a to p\na.join   // => undefined: a no longer has a join() method\n```\nThere is generally no need to ever use Object.setPrototypeOf(). JavaScript implementations may make aggressive optimizations based on the assumption that the prototype of an object is fixed and unchanging. This means that if you ever call Object.setPrototypeOf(), any code that uses the altered objects may run much slower than it would normally.\n\nA similar function, Reflect.setPrototypeOf(), is described in §14.6.\n\nSome early browser implementations of JavaScript exposed the prototype attribute of an object through the __proto__ property (written with two underscores at the start and end). This has long since been deprecated, but enough existing code on the web depends on __proto__ that the ECMAScript standard mandates it for all JavaScript implementations that run in web browsers. (Node supports it, too, though the standard does not require it for Node.) In modern JavaScript, __proto__ is readable and writeable, and you can (though you shouldn’t) use it as an alternative to Object.getPrototypeOf() and Object.setPrototypeOf(). One interesting use of __proto__, however, is to define the prototype of an object literal:\n```js\nlet p = {z: 3};\nlet o = {\n    x: 1,\n    y: 2,\n    __proto__: p\n};\no.z  // => 3: o inherits from p\n```\n## 14.4 Well-Known Symbols\nThe Symbol type was added to JavaScript in ES6, and one of the primary reasons for doing so was to safely add extensions to the language without breaking compatibility with code already deployed on the web. We saw an example of this in Chapter 12, where we learned that you can make a class iterable by implementing a method whose “name” is the Symbol Symbol.iterator.\n\nSymbol.iterator is the best-known example of the “well-known Symbols.” These are a set of Symbol values stored as properties of the Symbol() factory function that are used to allow JavaScript code to control certain low-level behaviors of objects and classes. The subsections that follow describe each of these well-known Symbols and explain how they can be used.\n\n### 14.4.1 Symbol.iterator and Symbol.asyncIterator\nThe Symbol.iterator and Symbol.asyncIterator Symbols allow objects or classes to make themselves iterable or asynchronously iterable. They were covered in detail in Chapter 12 and §13.4.2, respectively, and are mentioned again here only for completeness.\n\n### 14.4.2 Symbol.hasInstance\nWhen the instanceof operator was described in §4.9.4, we said that the righthand side must be a constructor function and that the expression o instanceof f was evaluated by looking for the value f.prototype within the prototype chain of o. That is still true, but in ES6 and beyond, Symbol.hasInstance provides an alternative. In ES6, if the righthand side of instanceof is any object with a [Symbol.hasInstance] method, then that method is invoked with the lefthand side value as its argument, and the return value of the method, converted to a boolean, becomes the value of the instanceof operator. And, of course, if the value on the righthand side does not have a [Symbol.hasInstance] method but is a function, then the instanceof operator behaves in its ordinary way.\n\nSymbol.hasInstance means that we can use the instanceof operator to do generic type checking with suitably defined pseudotype objects. For example:\n```js\n// Define an object as a \"type\" we can use with instanceof\nlet uint8 = {\n    [Symbol.hasInstance](x) {\n        return Number.isInteger(x) && x >= 0 && x <= 255;\n    }\n};\n128 instanceof uint8     // => true\n256 instanceof uint8     // => false: too big\nMath.PI instanceof uint8 // => false: not an integer\n```\nNote that this example is clever but confusing because it uses a nonclass object where a class would normally be expected. It would be just as easy—and clearer to readers of your code—to write a isUint8() function instead of relying on this Symbol.hasInstance behavior.\n\n### 14.4.3 Symbol.toStringTag\nIf you invoke the toString() method of a basic JavaScript object, you get the string “[object Object]”:\n```js\n{}.toString()  // => \"[object Object]\"\n```\nIf you invoke this same Object.prototype.toString() function as a method of instances of built-in types, you get some interesting results:\n```js\nObject.prototype.toString.call([])     // => \"[object Array]\"\nObject.prototype.toString.call(/./)    // => \"[object RegExp]\"\nObject.prototype.toString.call(()=>{}) // => \"[object Function]\"\nObject.prototype.toString.call(\"\")     // => \"[object String]\"\nObject.prototype.toString.call(0)      // => \"[object Number]\"\nObject.prototype.toString.call(false)  // => \"[object Boolean]\"\n```\nIt turns out that you can use this Object.prototype.toString().call() technique with any JavaScript value to obtain the “class attribute” of an object that contains type information that is not otherwise available. The following classof() function is arguably more useful than the typeof operator, which makes no distinction between types of objects:\n```js\nfunction classof(o) {\n    return Object.prototype.toString.call(o).slice(8,-1);\n}\n\nclassof(null)       // => \"Null\"\nclassof(undefined)  // => \"Undefined\"\nclassof(1)          // => \"Number\"\nclassof(10n**100n)  // => \"BigInt\"\nclassof(\"\")         // => \"String\"\nclassof(false)      // => \"Boolean\"\nclassof(Symbol())   // => \"Symbol\"\nclassof({})         // => \"Object\"\nclassof([])         // => \"Array\"\nclassof(/./)        // => \"RegExp\"\nclassof(()=>{})     // => \"Function\"\nclassof(new Map())  // => \"Map\"\nclassof(new Set())  // => \"Set\"\nclassof(new Date()) // => \"Date\"\n```\nPrior to ES6, this special behavior of the Object.prototype.toString() method was available only to instances of built-in types, and if you called this classof() function on an instance of a class you had defined yourself, it would simply return “Object”. In ES6, however, Object.prototype.toString() looks for a property with the symbolic name Symbol.toStringTag on its argument, and if such a property exists, it uses the property value in its output. This means that if you define a class of your own, you can easily make it work with functions like classof():\n```js\nclass Range {\n    get [Symbol.toStringTag]() { return \"Range\"; }\n    // the rest of this class is omitted here\n}\nlet r = new Range(1, 10);\nObject.prototype.toString.call(r)   // => \"[object Range]\"\nclassof(r)                          // => \"Range\"\n```\n### 14.4.4 Symbol.species\nPrior to ES6, JavaScript did not provide any real way to create robust subclasses of built-in classes like Array. In ES6, however, you can extend any built-in class simply by using the class and extends keywords. §9.5.2 demonstrated that with this simple subclass of Array:\n```js\n// A trivial Array subclass that adds getters for the first and last elements.\nclass EZArray extends Array {\n    get first() { return this[0]; }\n    get last() { return this[this.length-1]; }\n}\n\nlet e = new EZArray(1,2,3);\nlet f = e.map(x => x * x);\ne.last  // => 3: the last element of EZArray e\nf.last  // => 9: f is also an EZArray with a last property\n```\nArray defines methods concat(), filter(), map(), slice(), and splice(), which return arrays. When we create an array subclass like EZArray that inherits these methods, should the inherited method return instances of Array or instances of EZArray? Good arguments can be made for either choice, but the ES6 specification says that (by default) the five array-returning methods will return instances of the subclass.\n\nHere’s how it works:\n\nIn ES6 and later, the Array() constructor has a property with the symbolic name Symbol.species. (Note that this Symbol is used as the name of a property of the constructor function. Most of the other well-known Symbols described here are used as the name of methods of a prototype object.)\n\nWhen we create a subclass with extends, the resulting subclass constructor inherits properties from the superclass constructor. (This is in addition to the normal kind of inheritance, where instances of the subclass inherit methods of the superclass.) This means that the constructor for every subclass of Array also has an inherited property with name Symbol.species. (Or a subclass can define its own property with this name, if it wants.)\n\nMethods like map() and slice() that create and return new arrays are tweaked slightly in ES6 and later. Instead of just creating a regular Array, they (in effect) invoke new this.constructor[Symbol.species]() to create the new array.\n\nNow here’s the interesting part. Suppose that Array[Symbol.species] was just a regular data property, defined like this:\n```js\nArray[Symbol.species] = Array;\n```\nIn that case, then subclass constructors would inherit the Array() constructor as their “species,” and invoking map() on an array subclass would return an instance of the superclass rather than an instance of the subclass. That is not how ES6 actually behaves, however. The reason is that Array[Symbol.species] is a read-only accessor property whose getter function simply returns this. Subclass constructors inherit this getter function, which means that by default, every subclass constructor is its own “species.”\n\nSometimes this default behavior is not what you want, however. If you wanted the array-returning methods of EZArray to return regular Array objects, you just need to set EZArray[Symbol.species] to Array. But since the inherited property is a read-only accessor, you can’t just set it with an assignment operator. You can use defineProperty(), however:\n```js\nEZArray[Symbol.species] = Array; // Attempt to set a read-only property fails\n\n// Instead we can use defineProperty():\nObject.defineProperty(EZArray, Symbol.species, {value: Array});\nThe simplest option is probably to explicitly define your own Symbol.species getter when creating the subclass in the first place:\n\nclass EZArray extends Array {\n    static get [Symbol.species]() { return Array; }\n    get first() { return this[0]; }\n    get last() { return this[this.length-1]; }\n}\n\nlet e = new EZArray(1,2,3);\nlet f = e.map(x => x - 1);\ne.last  // => 3\nf.last  // => undefined: f is a regular array with no last getter\n```\nCreating useful subclasses of Array was the primary use case that motivated the introduction of Symbol.species, but it is not the only place that this well-known Symbol is used. Typed array classes use the Symbol in the same way that the Array class does. Similarly, the slice() method of ArrayBuffer looks at the Symbol.species property of this.constructor instead of simply creating a new ArrayBuffer. And Promise methods like then() that return new Promise objects create those objects via this species protocol as well. Finally, if you find yourself subclassing Map (for example) and defining methods that return new Map objects, you might want to use Symbol.species yourself for the benefit of subclasses of your subclass.\n\n### 14.4.5 Symbol.isConcatSpreadable\nThe Array method concat() is one of the methods described in the previous section that uses Symbol.species to determine what constructor to use for the returned array. But concat() also uses Symbol.isConcatSpreadable. Recall from §7.8.3 that the concat() method of an array treats its this value and its array arguments differently than its nonarray arguments: nonarray arguments are simply appended to the new array, but the this array and any array arguments are flattened or “spread” so that the elements of the array are concatenated rather than the array argument itself.\n\nBefore ES6, concat() just used Array.isArray() to determine whether to treat a value as an array or not. In ES6, the algorithm is changed slightly: if the argument (or the this value) to concat() is an object and has a property with the symbolic name Symbol.isConcatSpreadable, then the boolean value of that property is used to determine whether the argument should be “spread.” If no such property exists, then Array.isArray() is used as in previous versions of the language.\n\nThere are two cases when you might want to use this Symbol:\n\nIf you create an Array-like (see §7.9) object and want it to behave like a real array when passed to concat(), you can simply add the symbolic property to your object:\n```js\nlet arraylike = {\n    length: 1,\n    0: 1,\n    [Symbol.isConcatSpreadable]: true\n};\n[].concat(arraylike)  // => [1]: (would be [[1]] if not spread)\n```\nArray subclasses are spreadable by default, so if you are defining an array subclass that you do not want to act like an array when used with concat(), then you can1 add a getter like this to your subclass:\n```js\nclass NonSpreadableArray extends Array {\n    get [Symbol.isConcatSpreadable]() { return false; }\n}\nlet a = new NonSpreadableArray(1,2,3);\n[].concat(a).length // => 1; (would be 3 elements long if a was spread)\n```\n### 14.4.6 Pattern-Matching Symbols\n§11.3.2 documented the String methods that perform pattern-matching operations using a RegExp argument. In ES6 and later, these methods have been generalized to work with RegExp objects or any object that defines pattern-matching behavior via properties with symbolic names. For each of the string methods match(), matchAll(), search(), replace(), and split(), there is a corresponding well-known Symbol: Symbol.match, Symbol.search, and so on.\n\nRegExps are a general and very powerful way to describe textual patterns, but they can be complicated and not well suited to fuzzy matching. With the generalized string methods, you can define your own pattern classes using the well-known Symbol methods to provide custom matching. For example, you could perform string comparisons using Intl.Collator (see §11.7.3) to ignore accents when matching. Or you could define a pattern class based on the Soundex algorithm to match words based on their approximate sounds or to loosely match strings up to a given Levenshtein distance.\n\nIn general, when you invoke one of these five String methods on a pattern object like this:\n\nstring.method(pattern, arg)\nthat invocation turns into an invocation of a symbolically named method on your pattern object:\n\npattern[symbol](string, arg)\nAs an example, consider the pattern-matching class in the next example, which implements pattern matching using the simple * and ? wildcards that you are probably familar with from filesystems. This style of pattern matching dates back to the very early days of the Unix operating system, and the patterns are often called globs:\n```js\nclass Glob {\n    constructor(glob) {\n        this.glob = glob;\n\n        // We implement glob matching using RegExp internally.\n        // ? matches any one character except /, and * matches zero or more\n        // of those characters. We use capturing groups around each.\n        let regexpText = glob.replace(\"?\", \"([^/])\").replace(\"*\", \"([^/]*)\");\n\n        // We use the u flag to get Unicode-aware matching.\n        // Globs are intended to match entire strings, so we use the ^ and $\n        // anchors and do not implement search() or matchAll() since they\n        // are not useful with patterns like this.\n        this.regexp = new RegExp(`^${regexpText}$`, \"u\");\n    }\n\n    toString() { return this.glob; }\n\n    [Symbol.search](s) { return s.search(this.regexp); }\n    [Symbol.match](s)  { return s.match(this.regexp); }\n    [Symbol.replace](s, replacement) {\n        return s.replace(this.regexp, replacement);\n    }\n}\n\nlet pattern = new Glob(\"docs/*.txt\");\n\"docs/js.txt\".search(pattern)   // => 0: matches at character 0\n\"docs/js.htm\".search(pattern)   // => -1: does not match\nlet match = \"docs/js.txt\".match(pattern);\nmatch[0]     // => \"docs/js.txt\"\nmatch[1]     // => \"js\"\nmatch.index  // => 0\n\"docs/js.txt\".replace(pattern, \"web/$1.htm\")  // => \"web/js.htm\"\n```\n### 14.4.7 Symbol.toPrimitive\n§3.9.3 explained that JavaScript has three slightly different algorithms for converting objects to primitive values. Loosely speaking, for conversions where a string value is expected or preferred, JavaScript invokes an object’s toString() method first and falls back on the valueOf() method if toString() is not defined or does not return a primitive value. For conversions where a numeric value is preferred, JavaScript tries the valueOf() method first and falls back on toString() if valueOf() is not defined or if it does not return a primitive value. And finally, in cases where there is no preference, it lets the class decide how to do the conversion. Date objects convert using toString() first, and all other types try valueOf() first.\n\nIn ES6, the well-known Symbol Symbol.toPrimitive allows you to override this default object-to-primitive behavior and gives you complete control over how instances of your own classes will be converted to primitive values. To do this, define a method with this symbolic name. The method must return a primitive value that somehow represents the object. The method you define will be invoked with a single string argument that tells you what kind of conversion JavaScript is trying to do on your object:\n\nIf the argument is \"string\", it means that JavaScript is doing the conversion in a context where it would expect or prefer (but not require) a string. This happens when you interpolate the object into a template literal, for example.\n\nIf the argument is \"number\", it means that JavaScript is doing the conversion in a context where it would expect or prefer (but not require) a numeric value. This happens when you use the object with a < or > operator or with arithmetic operators like - and *.\n\nIf the argument is \"default\", it means that JavaScript is converting your object in a context where either a numeric or string value could work. This happens with the +, ==, and != operators.\n\nMany classes can ignore the argument and simply return the same primitive value in all cases. If you want instances of your class to be comparable and sortable with < and >, then that is a good reason to define a [Symbol.toPrimitive] method.\n\n### 14.4.8 Symbol.unscopables\nThe final well-known Symbol that we’ll cover here is an obscure one that was introduced as a workaround for compatibility issues caused by the deprecated with statement. Recall that the with statement takes an object and executes its statement body as if it were in a scope where the properties of that object were variables. This caused compatibility problems when new methods were added to the Array class, and it broke some existing code. Symbol.unscopables is the result. In ES6 and later, the with statement has been slightly modified. When used with an object o, a with statement computes Object.keys(o[Symbol.unscopables]||{}) and ignores properties whose names are in the resulting array when creating the simulated scope in which to execute its body. ES6 uses this to add new methods to Array.prototype without breaking existing code on the web. This means that you can find a list of the newest Array methods by evaluating:\n```js\nlet newArrayMethods = Object.keys(Array.prototype[Symbol.unscopables]);\n```\n## 14.5 Template Tags\nStrings within backticks are known as “template literals” and were covered in §3.3.4. When an expression whose value is a function is followed by a template literal, it turns into a function invocation, and we call it a “tagged template literal.” Defining a new tag function for use with tagged template literals can be thought of as metaprogramming, because tagged templates are often used to define DSLs—domain-specific languages—and defining a new tag function is like adding new syntax to JavaScript. Tagged template literals have been adopted by a number of frontend JavaScript packages. The GraphQL query language uses a gql`` tag function to allow queries to be embedded within JavaScript code. And the Emotion library uses a css`` tag function to enable CSS styles to be embedded in JavaScript. This section demonstrates how to write your own tag functions like these.\n\nThere is nothing special about tag functions: they are ordinary JavaScript functions, and no special syntax is required to define them. When a function expression is followed by a template literal, the function is invoked. The first argument is an array of strings, and this is followed by zero or more additional arguments, which can have values of any type.\n\nThe number of arguments depends on the number of values that are interpolated into the template literal. If the template literal is simply a constant string with no interpolations, then the tag function will be called with an array of that one string and no additional arguments. If the template literal includes one interpolated value, then the tag function is called with two arguments. The first is an array of two strings, and the second is the interpolated value. The strings in that initial array are the string to the left of the interpolated value and the string to its right, and either one of them may be the empty string. If the template literal includes two interpolated values, then the tag function is invoked with three arguments: an array of three strings and the two interpolated values. The three strings (any or all of which may be empty) are the text to the left of the first value, the text between the two values, and the text to the right of the second value. In the general case, if the template literal has n interpolated values, then the tag function will be invoked with n+1 arguments. The first argument will be an array of n+1 strings, and the remaining arguments are the n interpolated values, in the order that they appear in the template literal.\n\nThe value of a template literal is always a string. But the value of a tagged template literal is whatever value the tag function returns. This may be a string, but when the tag function is used to implement a DSL, the return value is typically a non-string data structure that is a parsed representation of the string.\n\nAs an example of a template tag function that returns a string, consider the following html`` template, which is useful when you want to safely interpolate values into a string of HTML. The tag performs HTML escaping on each of the values before using it to build the final string:\n```js\nfunction html(strings, ...values) {\n    // Convert each value to a string and escape special HTML characters\n    let escaped = values.map(v => String(v)\n                                  .replace(\"&\", \"&amp;\")\n                                  .replace(\"<\", \"&lt;\")\n                                  .replace(\">\", \"&gt;\")\n                                  .replace('\"', \"&quot;\")\n                                  .replace(\"'\", \"&#39;\"));\n\n    // Return the concatenated strings and escaped values\n    let result = strings[0];\n    for(let i = 0; i < escaped.length; i++) {\n        result += escaped[i] + strings[i+1];\n    }\n    return result;\n}\n\nlet operator = \"<\";\nhtml`<b>x ${operator} y</b>`             // => \"<b>x &lt; y</b>\"\n\nlet kind = \"game\", name = \"D&D\";\nhtml`<div class=\"${kind}\">${name}</div>` // =>'<div class=\"game\">D&amp;D</div>'\n```\nFor an example of a tag function that does not return a string but instead a parsed representation of a string, think back to the Glob pattern class defined in §14.4.6. Since the Glob() constructor takes a single string argument, we can define a tag function for creating new Glob objects:\n```js\nfunction glob(strings, ...values) {\n    // Assemble the strings and values into a single string\n    let s = strings[0];\n    for(let i = 0; i < values.length; i++) {\n        s += values[i] + strings[i+1];\n    }\n    // Return a parsed representation of that string\n    return new Glob(s);\n}\n\nlet root = \"/tmp\";\nlet filePattern = glob`${root}/*.html`;  // A RegExp alternative\n\"/tmp/test.html\".match(filePattern)[1]   // => \"test\"\n```\nOne of the features mentioned in passing in §3.3.4 is the String.raw`` tag function that returns a string in its “raw” form without interpreting any of the backslash escape sequences. This is implemented using a feature of tag function invocation that we have not discussed yet. When a tag function is invoked, we’ve seen that its first argument is an array of strings. But this array also has a property named raw, and the value of that property is another array of strings, with the same number of elements. The argument array includes strings that have had escape sequences interpreted as usual. And the raw array includes strings in which escape sequences are not interpreted. This obscure feature is important if you want to define a DSL with a grammar that uses backslashes. For example, if we wanted our glob`` tag function to support pattern matching on Windows-style paths (which use backslashes instead of forward slashes) and we did not want users of the tag to have to double every backslash, we could rewrite that function to use strings.raw[] instead of strings[]. The downside, of course, would be that we could no longer use escapes like \\u in our glob literals.\n\n## 14.6 The Reflect API\nThe Reflect object is not a class; like the Math object, its properties simply define a collection of related functions. These functions, added in ES6, define an API for “reflecting upon” objects and their properties. There is little new functionality here: the Reflect object defines a convenient set of functions, all in a single namespace, that mimic the behavior of core language syntax and duplicate the features of various pre-existing Object functions.\n\nAlthough the Reflect functions do not provide any new features, they do group the features together in one convenient API. And, importantly, the set of Reflect functions maps one-to-one with the set of Proxy handler methods that we’ll learn about in §14.7.\n\nThe Reflect API consists of the following functions:\n\nReflect.apply(f, o, args)\nThis function invokes the function f as a method of o (or invokes it as a function with no this value if o is null) and passes the values in the args array as arguments. It is equivalent to f.apply(o, args).\n\nReflect.construct(c, args, newTarget)\nThis function invokes the constructor c as if the new keyword had been used and passes the elements of the array args as arguments. If the optional newTarget argument is specified, it is used as the value of new.target within the constructor invocation. If not specified, then the new.target value will be c.\n\nReflect.defineProperty(o, name, descriptor)\nThis function defines a property on the object o, using name (a string or symbol) as the name of the property. The Descriptor object should define the value (or getter and/or setter) and attributes of the property. Reflect.defineProperty() is very similar to Object.defineProperty() but returns true on success and false on failures. (Object.defineProperty() returns o on success and throws TypeError on failure.)\n\nReflect.deleteProperty(o, name)\nThis function deletes the property with the specified string or symbolic name from the object o, returning true if successful (or if no such property existed) and false if the property could not be deleted. Calling this function is similar to writing delete o[name].\n\nReflect.get(o, name, receiver)\nThis function returns the value of the property of o with the specified name (a string or symbol). If the property is an accessor method with a getter, and if the optional receiver argument is specified, then the getter function is called as a method of receiver instead of as a method of o. Calling this function is similar to evaluating o[name].\n\nReflect.getOwnPropertyDescriptor(o, name)\nThis function returns a property descriptor object that describes the attributes of the property named name of the object o, or returns undefined if no such property exists. This function is nearly identical to Object.getOwnPropertyDescriptor(), except that the Reflect API version of the function requires that the first argument be an object and throws TypeError if it is not.\n\nReflect.getPrototypeOf(o)\nThis function returns the prototype of object o or null if the object has no prototype. It throws a TypeError if o is a primitive value instead of an object. This function is almost identical to Object.getPrototypeOf() except that Object.getPrototypeOf() only throws a TypeError for null and undefined arguments and coerces other primitive values to their wrapper objects.\n\nReflect.has(o, name)\nThis function returns true if the object o has a property with the specified name (which must be a string or a symbol). Calling this function is similar to evaluating name in o.\n\nReflect.isExtensible(o)\nThis function returns true if the object o is extensible (§14.2) and false if it is not. It throws a TypeError if o is not an object. Object.isExtensible() is similar but simply returns false when passed an argument that is not an object.\n\nReflect.ownKeys(o)\nThis function returns an array of the names of the properties of the object o or throws a TypeError if o is not an object. The names in the returned array will be strings and/or symbols. Calling this function is similar to calling Object.getOwnPropertyNames() and Object.getOwnPropertySymbols() and combining their results.\n\nReflect.preventExtensions(o)\nThis function sets the extensible attribute (§14.2) of the object o to false and returns true to indicate success. It throws a TypeError if o is not an object. Object.preventExtensions() has the same effect but returns o instead of true and does not throw TypeError for nonobject arguments.\n\nReflect.set(o, name, value, receiver)\nThis function sets the property with the specified name of the object o to the specified value. It returns true on success and false on failure (which can happen if the property is read-only). It throws TypeError if o is not an object. If the specified property is an accessor property with a setter function, and if the optional receiver argument is passed, then the setter will be invoked as a method of receiver instead of being invoked as a method of o. Calling this function is usually the same as evaluating o[name] = value.\n\nReflect.setPrototypeOf(o, p)\nThis function sets the prototype of the object o to p, returning true on success and false on failure (which can occur if o is not extensible or if the operation would cause a circular prototype chain). It throws a TypeError if o is not an object or if p is neither an object nor null. Object.setPrototypeOf() is similar, but returns o on success and throws TypeError on failure. Remember that calling either of these functions is likely to make your code slower by disrupting JavaScript interpreter optimizations.\n\n## 14.7 Proxy Objects\nThe Proxy class, available in ES6 and later, is JavaScript’s most powerful metaprogramming feature. It allows us to write code that alters the fundamental behavior of JavaScript objects. The Reflect API described in §14.6 is a set of functions that gives us direct access to a set of fundamental operations on JavaScript objects. What the Proxy class does is allows us a way to implement those fundamental operations ourselves and create objects that behave in ways that are not possible for ordinary objects.\n\n> ES6 之后引入的 Proxy 类是 JavaScript 非常强大的元编程特性。它允许我们通过编写代码来改变 JavaScript 对象的基本行为。§14.6 描述的 Reflect API 是一组让我们可以直接访问 JavaScript 对象的基本操作的函数。而 Proxy 类是允许我们自己实现那些基本操作的一种方法，并且创建对象 TODO\n\nWhen we create a Proxy object, we specify two other objects, the target object and the handlers object:\n\n> 当创建一个 Proxy 对象时需要指定另外两个对象，target 对象和 handlers 对象：\n\n```js\nlet proxy = new Proxy(target, handlers);\n```\nThe resulting Proxy object has no state or behavior of its own. Whenever you perform an operation on it (read a property, write a property, define a new property, look up the prototype, invoke it as a function), it dispatches those operations to the handlers object or to the target object.\n\n> Proxy 对象返回值没有状态和行为。每当使用它执行一个操作时（读属性，写属性，定义一个新的属性，寻找 prototype，将其作为函数调用），它会将操作派发给 handlers 或者 target 对象。\n\nThe operations supported by Proxy objects are the same as those defined by the Reflect API. Suppose that p is a Proxy object and you write delete p.x. The Reflect.deleteProperty() function has the same behavior as the delete operator. And when you use the delete operator to delete a property of a Proxy object, it looks for a deleteProperty() method on the handlers object. If such a method exists, it invokes it. And if no such method exists, then the Proxy object performs the property deletion on the target object instead.\n\n> Proxy 对象支持的操作和 Reflect API 定义的操作一样。假设 p 是一个 Proxy 对象，并且你写一段代码 `delete p.x`。Reflect.deleteProperty() 和 delete 操作符有同样的行为。而且当使用 delete 操作符去删除一个 Proxy 对象的属性时，它会去寻找 handlers 对象的 deleteProperty() 方法。如果存在这样一个方法，它将被调用。如果方法不存在，那么 Proxy 对象将在 target 对象上执行属性删除。\n\nProxies work this way for all of the fundamental operations: if an appropriate method exists on the handlers object, it invokes that method to perform the operation. (The method names and signatures are the same as those of the Reflect functions covered in §14.6.) And if that method does not exist on the handlers object, then the Proxy performs the fundamental operation on the target object. This means that a Proxy can obtain its behavior from the target object or from the handlers object. If the handlers object is empty, then the proxy is essentially a transparent wrapper around the target object:\n\n> Proxy 这种方式作用于所有的基本操作：如果有一个适当的方法存在于 handlers 对象，它就会调用这个方法来执行这个操作。（方法的方法名和签名与 §14.6 Reflect 包含的函数一致）如果那个方法不存在于 handlers 对象，那么 Proxy 会在 target 对象上执行该基本操作。这意味 Proxy 可以从 target 对象或 handlers 对象上获取它的行为。如果 handlers 对象是空的，Proxy 本质上是 target 对象的透明包装器：\n\n```js\nlet t = { x: 1, y: 2 };\nlet p = new Proxy(t, {});\np.x          // => 1\ndelete p.y   // => true: delete property y of the proxy\nt.y          // => undefined: this deletes it in the target, too\np.z = 3;     // Defining a new property on the proxy\nt.z          // => 3: defines the property on the target\n```\nThis kind of transparent wrapper proxy is essentially equivalent to the underlying target object, which means that there really isn’t a reason to use it instead of the wrapped object. Transparent wrappers can be useful, however, when created as “revocable proxies.” Instead of creating a Proxy with the Proxy() constructor, you can use the Proxy.revocable() factory function. This function returns an object that includes a Proxy object and also a revoke() function. Once you call the revoke() function, the proxy immediately stops working:\n\n> 这种透明的包装 proxy 本质上等同于底层的 target 对象，这意味着确实没有理由使用它来代替被包装的对象。但创建类似于可回收的代理时，透明的包装是很实用的。可以使用 Proxy.revocable() 工厂函数创建 Proxy。这个函数返回一个 Proxy 对象和 revoke() 函数。当调用 revoke() 函数时，代理立即停止工作：\n\n```js\nfunction accessTheDatabase() { /* implementation omitted */ return 42; }\nlet {proxy, revoke} = Proxy.revocable(accessTheDatabase, {});\n\nproxy()   // => 42: The proxy gives access to the underlying target function\nrevoke(); // But that access can be turned off whenever we want\nproxy();  // !TypeError: we can no longer call this function\n```\nNote that in addition to demonstrating revocable proxies, the preceding code also demonstrates that proxies can work with target functions as well as target objects. But the main point here is that revocable proxies are a building block for a kind of code isolation, and you might use them when dealing with untrusted third-party libraries, for example. If you have to pass a function to a library that you don’t control, you can pass a revocable proxy instead and then revoke the proxy when you are finished with the library. This prevents the library from keeping a reference to your function and calling it at unexpected times. This kind of defensive programming is not typical in JavaScript programs, but the Proxy class at least makes it possible.\n\n> 注意除了上面描述的可回收代理，前面的代码同样也论述了代理既可以代理 target 函数也可以代理 target 对象。但是这里主要关注点是可回收代理是一种代码隔离的构件，例如可以使用它们与不信任的第三方库进行交互。如果给一个库传递一个你无法控制的函数，可以传递一个可回收代理，当使用完这个库时 revoke 掉这个代理。这种防御式编程在 JavaScript 编程中并不常见，但是 Proxy 类至少可以实现。\n\nIf we pass a non-empty handlers object to the Proxy() constructor, then we are no longer defining a transparent wrapper object and are instead implementing custom behavior for our proxy. With the right set of handlers, the underlying target object essentially becomes irrelevant.\n\n> 如果传一个非空 handlers 对象给 Proxy 的构造函数，我们不在是定义一个透明的包装代理，而是为我们的代理实现定制的行为。正确设定 handler 时，底层的 target 对象基本上就无关了。\n\nIn the following code, for example, is how we could implement an object that appears to have an infinite number of read-only properties, where the value of each property is the same as the name of the property:\n\n> 在下面的代码中，我们演示如何实现一个看起来具有无限只读属性的对象，其中每个属性的值与属性的名称相同： \n\n```js\n// We use a Proxy to create an object that appears to have every\n// possible property, with the value of each property equal to its name\nlet identity = new Proxy({}, {\n    // Every property has its own name as its value\n    get(o, name, target) { return name; },\n    // Every property name is defined\n    has(o, name) { return true; },\n    // There are too many properties to enumerate, so we just throw\n    ownKeys(o) { throw new RangeError(\"Infinite number of properties\"); },\n    // All properties exist and are not writable, configurable or enumerable.\n    getOwnPropertyDescriptor(o, name) {\n        return {\n            value: name,\n            enumerable: false,\n            writable: false,\n            configurable: false\n        };\n    },\n    // All properties are read-only so they can't be set\n    set(o, name, value, target) { return false; },\n    // All properties are non-configurable, so they can't be deleted\n    deleteProperty(o, name) { return false; },\n    // All properties exist and are non-configurable so we can't define more\n    defineProperty(o, name, desc) { return false; },\n    // In effect, this means that the object is not extensible\n    isExtensible(o) { return false; },\n    // All properties are already defined on this object, so it couldn't\n    // inherit anything even if it did have a prototype object.\n    getPrototypeOf(o) { return null; },\n    // The object is not extensible, so we can't change the prototype\n    setPrototypeOf(o, proto) { return false; },\n});\n\nidentity.x                // => \"x\"\nidentity.toString         // => \"toString\"\nidentity[0]               // => \"0\"\nidentity.x = 1;           // Setting properties has no effect\nidentity.x                // => \"x\"\ndelete identity.x         // => false: can't delete properties either\nidentity.x                // => \"x\"\nObject.keys(identity);    // !RangeError: can't list all the keys\nfor(let p of identity) ;  // !RangeError\n```\nProxy objects can derive their behavior from the target object and from the handlers object, and the examples we have seen so far have used one object or the other. But it is typically more useful to define proxies that use both objects.\n\n> Proxy 对象可以从 target 和 handlers 对象中驱动它的行为，并且到目前为止我们的例子都只用一个对象。但是两个对象都定义通常会更有作用。\n\nThe following code, for example, uses Proxy to create a read-only wrapper for a target object. When code tries to read values from the object, those reads are forwarded to the target object normally. But if any code tries to modify the object or its properties, methods of the handler object throw a TypeError. A proxy like this might be helpful for writing tests: suppose you’ve written a function that takes an object argument and want to ensure that your function does not make any attempt to modify the input argument. If your test passes in a read-only wrapper object, then any writes will throw exceptions that cause the test to fail:\n\n> 例如下面的代码，使用 Proxy 创建为 target 对象创建一个只读的封装。当代码试图从对象中读值时，这些读操作正常会传递给 taregt 对象。但是如果有代码试图修改对象或者对象的属性，handler 对象的方法会抛出一个 TypeError 一场。这样的代理可能会更有助于写测试：假设你写了个接收一个对象实参的函数，并且确保你的函数不会试图修改这个输入实参。如何你测试传入一个只读封装对象，那么任何写操作都会抛出异常并导致测试失败：\n\n```js\nfunction readOnlyProxy(o) {\n    function readonly() { throw new TypeError(\"Readonly\"); }\n    return new Proxy(o, {\n        set: readonly,\n        defineProperty: readonly,\n        deleteProperty: readonly,\n        setPrototypeOf: readonly,\n    });\n}\n\nlet o = { x: 1, y: 2 };    // Normal writable object\nlet p = readOnlyProxy(o);  // Readonly version of it\np.x                        // => 1: reading properties works\np.x = 2;                   // !TypeError: can't change properties\ndelete p.y;                // !TypeError: can't delete properties\np.z = 3;                   // !TypeError: can't add properties\np.__proto__ = {};          // !TypeError: can't change the prototype\n```\nAnother technique when writing proxies is to define handler methods that intercept operations on an object but still delegate the operations to the target object. The functions of the Reflect API (§14.6) have exactly the same signatures as the handler methods, so they make it easy to do that kind of delegation.\n\n> 另外一种使用方式，当写代理 定义 handler 方法拦截操作\n\nHere, for example, is a proxy that delegates all operations to the target object but uses handler methods to log the operations:\n```js\n/*\n * Return a Proxy object that wraps o, delegating all operations to\n * that object after logging each operation. objname is a string that\n * will appear in the log messages to identify the object. If o has own\n * properties whose values are objects or functions, then if you query\n * the value of those properties, you'll get a loggingProxy back, so that\n * logging behavior of this proxy is \"contagious\".\n */\nfunction loggingProxy(o, objname) {\n    // Define handlers for our logging Proxy object.\n    // Each handler logs a message and then delegates to the target object.\n    const handlers = {\n        // This handler is a special case because for own properties\n        // whose value is an object or function, it returns a proxy rather\n        // than returning the value itself.\n        get(target, property, receiver) {\n            // Log the get operation\n            console.log(`Handler get(${objname},${property.toString()})`);\n\n            // Use the Reflect API to get the property value\n            let value = Reflect.get(target, property, receiver);\n\n            // If the property is an own property of the target and\n            // the value is an object or function then return a Proxy for it.\n            if (Reflect.ownKeys(target).includes(property) &&\n                (typeof value === \"object\" || typeof value === \"function\")) {\n                return loggingProxy(value, `${objname}.${property.toString()}`);\n            }\n\n            // Otherwise return the value unmodified.\n            return value;\n        },\n\n        // There is nothing special about the following three methods:\n        // they log the operation and delegate to the target object.\n        // They are a special case simply so we can avoid logging the\n        // receiver object which can cause infinite recursion.\n        set(target, prop, value, receiver) {\n            console.log(`Handler set(${objname},${prop.toString()},${value})`);\n            return Reflect.set(target, prop, value, receiver);\n        },\n        apply(target, receiver, args) {\n            console.log(`Handler ${objname}(${args})`);\n            return Reflect.apply(target, receiver, args);\n        },\n        construct(target, args, receiver) {\n            console.log(`Handler ${objname}(${args})`);\n            return Reflect.construct(target, args, receiver);\n        }\n    };\n\n    // We can automatically generate the rest of the handlers.\n    // Metaprogramming FTW!\n    Reflect.ownKeys(Reflect).forEach(handlerName => {\n        if (!(handlerName in handlers)) {\n            handlers[handlerName] = function(target, ...args) {\n                // Log the operation\n                console.log(`Handler ${handlerName}(${objname},${args})`);\n                // Delegate the operation\n                return Reflect[handlerName](target, ...args);\n            };\n        }\n    });\n\n    // Return a proxy for the object using these logging handlers\n    return new Proxy(o, handlers);\n}\n```\nThe loggingProxy() function defined earlier creates proxies that log all of the ways they are used. If you are trying to understand how an undocumented function uses the objects you pass it, using a logging proxy can help.\n\nConsider the following examples, which result in some genuine insights about array iteration:\n```js\n// Define an array of data and an object with a function property\nlet data = [10,20];\nlet methods = { square: x => x*x };\n\n// Create logging proxies for the array and the object\nlet proxyData = loggingProxy(data, \"data\");\nlet proxyMethods = loggingProxy(methods, \"methods\");\n\n// Suppose we want to understand how the Array.map() method works\ndata.map(methods.square)        // => [100, 400]\n\n// First, let's try it with a logging Proxy array\nproxyData.map(methods.square)   // => [100, 400]\n// It produces this output:\n// Handler get(data,map)\n// Handler get(data,length)\n// Handler get(data,constructor)\n// Handler has(data,0)\n// Handler get(data,0)\n// Handler has(data,1)\n// Handler get(data,1)\n\n// Now lets try with a proxy methods object\ndata.map(proxyMethods.square)   // => [100, 400]\n// Log output:\n// Handler get(methods,square)\n// Handler methods.square(10,0,10,20)\n// Handler methods.square(20,1,10,20)\n\n// Finally, let's use a logging proxy to learn about the iteration protocol\nfor(let x of proxyData) console.log(\"Datum\", x);\n// Log output:\n// Handler get(data,Symbol(Symbol.iterator))\n// Handler get(data,length)\n// Handler get(data,0)\n// Datum 10\n// Handler get(data,length)\n// Handler get(data,1)\n// Datum 20\n// Handler get(data,length)\n```\nFrom the first chunk of logging output, we learn that the Array.map() method explicitly checks for the existence of each array element (causing the has() handler to be invoked) before actually reading the element value (which triggers the get() handler). This is presumably so that it can distinguish nonexistent array elements from elements that exist but are undefined.\n\nThe second chunk of logging output might remind us that the function we pass to Array.map() is invoked with three arguments: the element’s value, the element’s index, and the array itself. (There is a problem in our logging output: the Array.toString() method does not include square brackets in its output, and the log messages would be clearer if they were included in the argument list (10,0,[10,20]).)\n\nThe third chunk of logging output shows us that the for/of loop works by looking for a method with symbolic name [Symbol.iterator]. It also demonstrates that the Array class’s implementation of this iterator method is careful to check the array length at every iteration and does not assume that the array length remains constant during the iteration.\n\n### 14.7.1 Proxy Invariants\nThe readOnlyProxy() function defined earlier creates Proxy objects that are effectively frozen: any attempt to alter a property value or property attribute or to add or remove properties will throw an exception. But as long as the target object is not frozen, we’ll find that if we can query the proxy with Reflect.isExtensible() and Reflect.getOwnPropertyDescriptor(), and it will tell us that we should be able to set, add, and delete properties. So readOnlyProxy() creates objects in an inconsistent state. We could fix this by adding isExtensible() and getOwnPropertyDescriptor() handlers, or we can just live with this kind of minor inconsistency.\n\nThe Proxy handler API allows us to define objects with major inconsistencies, however, and in this case, the Proxy class itself will prevent us from creating Proxy objects that are inconsistent in a bad way. At the start of this section, we described proxies as objects with no behavior of their own because they simply forward all operations to the handlers object and the target object. But this is not entirely true: after forwarding an operation, the Proxy class performs some sanity checks on the result to ensure important JavaScript invariants are not being violated. If it detects a violation, the proxy will throw a TypeError instead of letting the operation proceed.\n\nAs an example, if you create a proxy for a non-extensible object, the proxy will throw a TypeError if the isExtensible() handler ever returns true:\n```js\nlet target = Object.preventExtensions({});\nlet proxy = new Proxy(target, { isExtensible() { return true; }});\nReflect.isExtensible(proxy);  // !TypeError: invariant violation\n```\nRelatedly, proxy objects for non-extensible targets may not have a getPrototypeOf() handler that returns anything other than the real prototype object of the target. Also, if the target object has nonwritable, nonconfigurable properties, then the Proxy class will throw a TypeError if the get() handler returns anything other than the actual value:\n```js\nlet target = Object.freeze({x: 1});\nlet proxy = new Proxy(target, { get() { return 99; }});\nproxy.x;         // !TypeError: value returned by get() doesn't match target\n```\nProxy enforces a number of additional invariants, almost all of them having to do with non-extensible target objects and nonconfigurable properties on the target object.\n\n## 14.8 Summary\nIn this chapter, you have learned:\n\nJavaScript objects have an extensible attribute and object properties have writable, enumerable, and configurable attributes, as well as a value and a getter and/or setter attribute. You can use these attributes to “lock down” your objects in various ways, including creating “sealed” and “frozen” objects.\n\nJavaScript defines functions that allow you to traverse the prototype chain of an object and even to change the prototype of an object (though doing this can make your code slower).\n\nThe properties of the Symbol object have values that are “well-known Symbols,” which you can use as property or method names for the objects and classes that you define. Doing so allows you to control how your object interacts with JavaScript language features and with the core library. For example, well-known Symbols allow you to make your classes iterable and control the string that is displayed when an instance is passed to Object.prototype.toString(). Prior to ES6, this kind of customization was available only to the native classes that were built in to an implementation.\n\nTagged template literals are a function invocation syntax, and defining a new tag function is kind of like adding a new literal syntax to the language. Defining a tag function that parses its template string argument allows you to embed DSLs within JavaScript code. Tag functions also provide access to a raw, unescaped form of string literals where backslashes have no special meaning.\n\nThe Proxy class and the related Reflect API allow low-level control over the fundamental behaviors of JavaScript objects. Proxy objects can be used as optionally revocable wrappers to improve code encapsulation, and they can also be used to implement nonstandard object behaviors (like some of the special case APIs defined by early web browsers).\n\n1 A bug in the V8 JavaScript engine means that this code does not work correctly in Node 13."
  },
  {
    "path": "content/posts/ch15.md",
    "content": "---\ntitle: \"第 15 章 Web 浏览器中的 JavaScript\"\ndate: 2020-11-02T22:18:28+08:00\n---\n\nThe JavaScript language was created in 1994 with the express purpose of enabling dynamic behavior in the documents displayed by web browsers. The language has evolved significantly since then, and at the same time, the scope and capabilities of the web platform have grown explosively. Today, JavaScript programmers can think of the web as a full-featured platform for application development. Web browsers specialize in the display of formatted text and images, but, like native operating systems, browsers also provide other services, including graphics, video, audio, networking, storage, and threading. JavaScript is the language that enables web applications to use the services provided by the web platform, and this chapter demonstrates how you can use the most important of these services.\n\n> JavaScript语言创建于1994年，其主要目的是在web浏览器显示的文档中启用动态行为。从那时起，该语言已经发生了显著的发展，同时，web平台的范围和功能也爆炸式地增长。如今，JavaScript程序员可以将web看作是应用程序开发的功能齐全的平台。Web浏览器专门用于显示格式化的文本和图像，但是，与本机操作系统一样，浏览器也提供其他服务，包括图形、视频、音频、网络、存储和线程处理。JavaScript是一种使web应用程序能够使用web平台提供的服务的语言，本章将演示如何使用这些服务中最重要的一种。\n\nThe chapter begins with the web platform’s programming model, explaining how scripts are embedded within HTML pages (§15.1) and how JavaScript code is triggered asynchronously by events (§15.2). The sections that follow this introductory material document the core JavaScript APIs that enable your web applications to:\n\n> 本章从web平台的编程模型开始，说明了脚本是如何嵌入到HTML页面中的(§15.1)，以及JavaScript代码是如何被事件异步触发的(§15.2)。接下来的章节介绍了核心JavaScript api，使您的web应用程序:\n\n- Control document content (§15.3) and style (§15.4)\n- Determine the on-screen position of document elements (§15.5)\n- Create reusable user interface components (§15.6)\n- Draw graphics (§15.7 and §15.8)\n- Play and generate sounds (§15.9)\n- Manage browser navigation and history (§15.10)\n- Exchange data over the network (§15.11)\n- Store data on the user’s computer (§15.12)\n- Perform concurrent computation with threads (§15.13)\n\n---\n\n> - 控制文件内容(§15.3)和样式(§15.4)\n> - 确定文档元素在屏幕上的位置(§15.5)\n> - 创建可重用的用户界面组件(§15.6)\n> - 绘制图形(§15.7和§15.8)\n> - 播放和生成声音(§15.9)\n> - 管理浏览器导航和历史记录(§15.10)\n> - 通过网络交换数据(§15.11)\n> - 在用户的计算机上存储数据(§15.12)\n> - 用线程执行并发计算(§15.13)\n\n#### CLIENT-SIDE JAVASCRIPT\n\nIn this book, and on the web, you’ll see the term “client-side JavaScript.” The term is simply a synonym for JavaScript written to run in a web browser, and it stands in contrast to “server-side” code, which runs in web servers.\n\n> 在本书和web上，您将看到术语“客户端JavaScript”。这个术语只是在web浏览器中运行的JavaScript的同义词，它与在web服务器中运行的“服务器端”代码形成对比。\n\nThe two “sides” refer to the two ends of the network connection that separate the web server and the web browser, and software development for the web typically requires code to be written on both “sides.” Client-side and server-side are also often called “frontend” and “backend.”\n\n> 这两个“端”指的是网络连接的两端，它将web服务器和web浏览器分开，而web的软件开发通常需要在这两个“端”编写代码。客户端和服务器端通常也称为前端和后端。\n\nPrevious editions of this book attempted to comprehensively cover all JavaScript APIs defined by web browsers, and as a result, this book was too long a decade ago. The number and complexity of web APIs has continued to grow, and I no longer think it makes sense to attempt to cover them all in one book. As of the seventh edition, my goal is to cover the JavaScript language definitively and to provide an in-depth introduction to using the language with Node and with web browsers. This chapter cannot cover all the web APIs, but it introduces the most important ones in enough detail that you can start using them right away. And, having learned about the core APIs covered here, you should be able to pick up new APIs (like those summarized in §15.15) when and if you need them.\n\n> 本书的前几个版本试图全面地涵盖web浏览器定义的所有JavaScript api，因此，这本书在十年前就显得太长了。web api的数量和复杂性一直在增长，我认为在一本书中涵盖它们已经没有意义了。在第7版中，我的目标是详细介绍JavaScript语言，并深入介绍如何在Node和web浏览器中使用该语言。本章不能涵盖所有的web api，但它介绍了最重要的api，足够详细，您可以立即开始使用它们。并且，在了解了这里所涵盖的核心api之后，您应该能够在需要的时候获得新的api(如§15.15中总结的那些api)。\n\nNode has a single implementation and a single authoritative source for documentation. Web APIs, by contrast, are defined by consensus among the major web browser vendors, and the authoritative documentation takes the form of a specification intended for the C++ programmers who implement the API, not for the JavaScript programmers who will use it. Fortunately, Mozilla’s “MDN web docs” project is a reliable and comprehensive source1 for web API documentation.\n\n> Node只有一个实现和一个权威的文档源。相比之下，Web API是由主要Web浏览器供应商的共识定义的，权威文档采用了一种规范的形式，这种规范是为实现API的c++程序员设计的，而不是为将使用它的JavaScript程序员设计的。幸运的是，Mozilla的“MDN web docs”项目是一个可靠而全面的web API文档来源。\n\n#### LEGACY APIS\n\nIn the 25 years since JavaScript was first released, browser vendors have been adding features and APIs for programmers to use. Many of those APIs are now obsolete. They include:\n\n- Proprietary APIs that were never standardized and/or never implemented by other browser vendors. Microsoft’s Internet Explorer defined a lot of these APIs. Some (like the innerHTML property) proved useful and were eventually standardized. Others (like the attachEvent() method) have been obsolete for years.\n- Inefficient APIs (like the document.write() method) that have such a severe performance impact that their use is no longer considered acceptable.\n- Outdated APIs that have long since been replaced by new APIs for achieving the same thing. An example is document.bgColor, which was defined to allow JavaScript to set the background color of a document. With the advent of CSS, document.bgColor became a quaint special case with no real purpose.\n- Poorly designed APIs that have been replaced by better ones. In the early days of the web, standards committees defined the key Document Object Model API in a language-agnostic way so that the same API could be used in Java programs to work with XML documents on and in JavaScript programs to work with HTML documents. This resulted in an API that was not well suited to the JavaScript language and that had features that web programmers didn’t particularly care about. It took decades to recover from those early design mistakes, but today’s web browsers support a much-improved Document Object Model.\n\nBrowser vendors may need to support these legacy APIs for the foreseeable future in order to ensure backward compatibility, but there is no longer any need for this book to document them or for you to learn about them. The web platform has matured and stabilized, and if you are a seasoned web developer who remembers the fourth or fifth edition of this book, then you may have as much outdated knowledge to forget as you have new material to learn.\n\n## 15.1 Web Programming Basics\nThis section explains how JavaScript programs for the web are structured, how they are loaded into a web browser, how they obtain input, how they produce output, and how they run asynchronously by responding to events.\n\n### 15.1.1 JavaScript in HTML `<script>` Tags\nWeb browsers display HTML documents. If you want a web browser to execute JavaScript code, you must include (or reference) that code from an HTML document, and this is what the HTML `<script>` tag does.\n\nJavaScript code can appear inline within an HTML file between `<script>` and `</script>` tags. Here, for example, is an HTML file that includes a script tag with JavaScript code that dynamically updates one element of the document to make it behave like a digital clock:\n```html\n<!DOCTYPE html>                 <!-- This is an HTML5 file -->\n<html>                          <!-- The root element -->\n<head>                          <!-- Title, scripts & styles can go here -->\n<title>Digital Clock</title>\n<style>                         /* A CSS stylesheet for the clock */\n#clock {                        /* Styles apply to element with id=\"clock\" */\n  font: bold 24px sans-serif;   /* Use a big bold font */\n  background: #ddf;             /* on a light bluish-gray background. */\n  padding: 15px;                /* Surround it with some space */\n  border: solid black 2px;      /* and a solid black border */\n  border-radius: 10px;          /* with rounded corners. */\n}\n</style>\n</head>\n<body>                    <!-- The body holds the content of the document. -->\n<h1>Digital Clock</h1>    <!-- Display a title. -->\n<span id=\"clock\"></span>  <!-- We will insert the time into this element. -->\n<script>\n// Define a function to display the current time\nfunction displayTime() {\n    let clock = document.querySelector(\"#clock\"); // Get element with id=\"clock\"\n    let now = new Date();                         // Get current time\n    clock.textContent = now.toLocaleTimeString(); // Display time in the clock\n}\ndisplayTime()                    // Display the time right away\nsetInterval(displayTime, 1000);  // And then update it every second.\n</script>\n</body>\n</html>\n```\nAlthough JavaScript code can be embedded directly within a `<script>` tag, it is more common to instead use the src attribute of the `<script>` tag to specify the URL (an absolute URL or a URL relative to the URL of the HTML file being displayed) of a file containing JavaScript code. If we took the JavaScript code out of this HTML file and stored it in its own scripts/digital_clock.js file, then the `<script>` tag might reference that file of code like this:\n```html\n<script src=\"scripts/digital_clock.js\"></script>\n```\nA JavaScript file contains pure JavaScript, without `<script>` tags or any other HTML. By convention, files of JavaScript code have names that end with .js.\n\nA `<script>` tag with the a src attribute behaves exactly as if the contents of the specified JavaScript file appeared directly between the `<script>` and `</script>` tags. Note that the closing `</script>` tag is required in HTML documents even when the src attribute is specified: HTML does not support a `<script/>` tag.\n\nThere are a number of advantages to using the src attribute:\n\n- It simplifies your HTML files by allowing you to remove large blocks of JavaScript code from them—that is, it helps keep content and behavior separate.\n- When multiple web pages share the same JavaScript code, using the src attribute allows you to maintain only a single copy of that code, rather than having to edit each HTML file when the code changes.\n- If a file of JavaScript code is shared by more than one page, it only needs to be downloaded once, by the first page that uses it—subsequent pages can retrieve it from the browser cache.\n- Because the src attribute takes an arbitrary URL as its value, a JavaScript program or web page from one web server can employ code exported by other web servers. Much internet advertising relies on this fact.\n\nMODULES\n§10.3 documents JavaScript modules and covers their import and export directives. If you have written your JavaScript program using modules (and have not used a code-bundling tool to combine all your modules into a single nonmodular file of JavaScript), then you must load the top-level module of your program with a `<script>` tag that has a type=\"module\" attribute. If you do this, then the module you specify will be loaded, and all of the modules it imports will be loaded, and (recursively) all of the modules they import will be loaded. See §10.3.5 for complete details.\n\nSPECIFYING SCRIPT TYPE\nIn the early days of the web, it was thought that browsers might some day implement languages other than JavaScript, and programmers added attributes like language=\"javascript\" and type=\"application/javascript\" to their `<script>` tags. This is completely unnecessary. JavaScript is the default (and only) language of the web. The language attribute is deprecated, and there are only two reasons to use a type attribute on a `<script>` tag:\n\n- To specify that the script is a module\n- To embed data into a web page without displaying it (see §15.3.4)\n\nWHEN SCRIPTS RUN: ASYNC AND DEFERRED\nWhen JavaScript was first added to web browsers, there was no API for traversing and manipulating the structure and content of an already rendered document. The only way that JavaScript code could affect the content of a document was to generate that content on the fly while the document was in the process of loading. It did this by using the document.write() method to inject HTML text into the document at the location of the script.\n\nThe use of document.write() is no longer considered good style, but the fact that it is possible means that when the HTML parser encounters a `<script>` element, it must, by default, run the script just to be sure that it doesn’t output any HTML before it can resume parsing and rendering the document. This can dramatically slow down parsing and rendering of the web page.\n\nFortunately, this default synchronous or blocking script execution mode is not the only option. The `<script>` tag can have defer and async attributes, which cause scripts to be executed differently. These are boolean attributes—they don’t have a value; they just need to be present on the `<script>` tag. Note that these attributes are only meaningful when used in conjunction with the src attribute:\n```html\n<script defer src=\"deferred.js\"></script>\n<script async src=\"async.js\"></script>\n```\nBoth the defer and async attributes are ways of telling the browser that the linked script does not use document.write() to generate HTML output, and that the browser, therefore, can continue to parse and render the document while downloading the script. The defer attribute causes the browser to defer execution of the script until after the document has been fully loaded and parsed and is ready to be manipulated. The async attribute causes the browser to run the script as soon as possible but does not block document parsing while the script is being downloaded. If a `<script>` tag has both attributes, the async attribute takes precedence.\n\nNote that deferred scripts run in the order in which they appear in the document. Async scripts run as they load, which means that they may execute out of order.\n\nScripts with the type=\"module\" attribute are, by default, executed after the document has loaded, as if they had a defer attribute. You can override this default with the async attribute, which will cause the code to be executed as soon as the module and all of its dependencies have loaded.\n\nA simple alternative to the async and defer attributes—especially for code that is included directly in the HTML—is to simply put your scripts at the end of the HTML file. That way, the script can run knowing that the document content before it has been parsed and is ready to be manipulated.\n\nLOADING SCRIPTS ON DEMAND\nSometimes, you may have JavaScript code that is not used when a document first loads and is only needed if the user takes some action like clicking on a button or opening a menu. If you are developing your code using modules, you can load a module on demand with import(), as described in §10.3.6.\n\nIf you are not using modules, you can load a file of JavaScript on demand simply by adding a `<script>` tag to your document when you want the script to load:\n```js\n// Asynchronously load and execute a script from a specified URL\n// Returns a Promise that resolves when the script has loaded.\nfunction importScript(url) {\n    return new Promise((resolve, reject) => {\n        let s = document.createElement(\"script\"); // Create a <script> element\n        s.onload = () => { resolve(); };          // Resolve promise when loaded\n        s.onerror = (e) => { reject(e); };        // Reject on failure\n        s.src = url;                              // Set the script URL\n        document.head.append(s);                  // Add <script> to document\n    });\n}\n```\nThis importScript() function uses DOM APIs (§15.3) to create a new `<script>` tag and add it to the document `<head>`. And it uses event handlers (§15.2) to determine when the script has loaded successfully or when loading has failed.\n\n### 15.1.2 The Document Object Model\nOne of the most important objects in client-side JavaScript programming is the Document object—which represents the HTML document that is displayed in a browser window or tab. The API for working with HTML documents is known as the Document Object Model, or DOM, and it is covered in detail in §15.3. But the DOM is so central to client-side JavaScript programming that it deserves to be introduced here.\n\nHTML documents contain HTML elements nested within one another, forming a tree. Consider the following simple HTML document:\n```html\n<html>\n  <head>\n    <title>Sample Document</title>\n  </head>\n  <body>\n    <h1>An HTML Document</h1>\n    <p>This is a <i>simple</i> document.\n  </body>\n</html>\n```\nThe top-level `<html>` tag contains `<head>` and `<body>` tags. The `<head>` tag contains a `<title>` tag. And the `<body>` tag contains `<h1>` and `<p>` tags. The `<title>` and `<h1>` tags contain strings of text, and the `<p>` tag contains two strings of text with an `<i>` tag between them.\n\nThe DOM API mirrors the tree structure of an HTML document. For each HTML tag in the document, there is a corresponding JavaScript Element object, and for each run of text in the document, there is a corresponding Text object. The Element and Text classes, as well as the Document class itself, are all subclasses of the more general Node class, and Node objects are organized into a tree structure that JavaScript can query and traverse using the DOM API. The DOM representation of this document is the tree pictured in Figure 15-1.\n\n<Figures figure=\"15-1\">The tree representation of an HTML document</Figures>\n\nIf you are not already familiar with tree structures in computer programming, it is helpful to know that they borrow terminology from family trees. The node directly above a node is the parent of that node. The nodes one level directly below another node are the children of that node. Nodes at the same level, and with the same parent, are siblings. The set of nodes any number of levels below another node are the descendants of that node. And the parent, grandparent, and all other nodes above a node are the ancestors of that node.\n\nThe DOM API includes methods for creating new Element and Text nodes, and for inserting them into the document as children of other Element objects. There are also methods for moving elements within the document and for removing them entirely. While a server-side application might produce plain-text output by writing strings with console.log(), a client-side JavaScript application can produce formatted HTML output by building or manipulating the document tree document using the DOM API.\n\nThere is a JavaScript class corresponding to each HTML tag type, and each occurrence of the tag in a document is represented by an instance of the class. The `<body>` tag, for example, is represented by an instance of HTMLBodyElement, and a `<table>` tag is represented by an instance of HTMLTableElement. The JavaScript element objects have properties that correspond to the HTML attributes of the tags. For example, instances of HTMLImageElement, which represent `<img>` tags, have a src property that corresponds to the src attribute of the tag. The initial value of the src property is the attribute value that appears in the HTML tag, and setting this property with JavaScript changes the value of the HTML attribute (and causes the browser to load and display a new image). Most of the JavaScript element classes just mirror the attributes of an HTML tag, but some define additional methods. The HTMLAudioElement and HTMLVideoElement classes, for example, define methods like play() and pause() for controlling playback of audio and video files.\n\n### 15.1.3 The Global Object in Web Browsers\nThere is one global object per browser window or tab (§3.7). All of the JavaScript code (except code running in worker threads; see §15.13) running in that window shares this single global object. This is true regardless of how many scripts or modules are in the document: all the scripts and modules of a document share a single global object; if one script defines a property on that object, that property is visible to all the other scripts as well.\n\nThe global object is where JavaScript’s standard library is defined—the parseInt() function, the Math object, the Set class, and so on. In web browsers, the global object also contains the main entry points of various web APIs. For example, the document property represents the currently displayed document, the fetch() method makes HTTP network requests, and the Audio() constructor allows JavaScript programs to play sounds.\n\nIn web browsers, the global object does double duty: in addition to defining built-in types and functions, it also represents the current web browser window and defines properties like history (§15.10.2), which represent the window’s browsing history, and innerWidth, which holds the window’s width in pixels. One of the properties of this global object is named window, and its value is the global object itself. This means that you can simply type window to refer to the global object in your client-side code. When using window-specific features, it is often a good idea to include a window. prefix: window.innerWidth is clearer than innerWidth, for example.\n\n### 15.1.4 Scripts Share a Namespace\nWith modules, the constants, variables, functions, and classes defined at the top level (i.e., outside of any function or class definition) of the module are private to the module unless they are explicitly exported, in which case, they can be selectively imported by other modules. (Note that this property of modules is honored by code-bundling tools as well.)\n\nWith non-module scripts, however, the situation is completely different. If the top-level code in a script defines a constant, variable, function, or class, that declaration will be visible to all other scripts in the same document. If one script defines a function f() and another script defines a class c, then a third script can invoke the function and instantiate the class without having to take any action to import them. So if you are not using modules, the independent scripts in your document share a single namespace and behave as if they are all part of a single larger script. This can be convenient for small programs, but the need to avoid naming conflicts can become problematic for larger programs, especially when some of the scripts are third-party libraries.\n\nThere are some historical quirks with how this shared namespace works. var and function declarations at the top level create properties in the shared global object. If one script defines a top-level function f(), then another script in the same document can invoke that function as f() or as window.f(). On the other hand, the ES6 declarations const, let, and class, when used at the top level, do not create properties in the global object. They are still defined in a shared namespace, however: if one script defines a class C, other scripts will be able to create instances of that class with new C(), but not with new window.C().\n\nTo summarize: in modules, top-level declarations are scoped to the module and can be explicitly exported. In nonmodule scripts, however, top-level declarations are scoped to the containing document, and the declarations are shared by all scripts in the document. Older var and function declarations are shared via properties of the global object. Newer const, let, and class declarations are also shared and have the same document scope, but they do not exist as properties of any object that JavaScript code has access to.\n\n### 15.1.5 Execution of JavaScript Programs\nThere is no formal definition of a program in client-side JavaScript, but we can say that a JavaScript program consists of all the JavaScript code in, or referenced from, a document. These separate bits of code share a single global Window object, which gives them access to the same underlying Document object representing the HTML document. Scripts that are not modules additionally share a top-level namespace.\n\nIf a web page includes an embedded frame (using the `<iframe>` element), the JavaScript code in the embedded document has a different global object and Document object than the code in the embedding document, and it can be considered a separate JavaScript program. Remember, though, that there is no formal definition of what the boundaries of a JavaScript program are. If the container document and the contained document are both loaded from the same server, the code in one document can interact with the code in the other, and you can treat them as two interacting parts of a single program, if you wish. §15.13.6 explains how a JavaScript program can send and receive messages to and from JavaScript code running in an `<iframe>`.\n\nYou can think of JavaScript program execution as occurring in two phases. In the first phase, the document content is loaded, and the code from `<script>` elements (both inline scripts and external scripts) is run. Scripts generally run in the order in which they appear in the document, though this default order can be modified by the async and defer attributes we’ve described. The JavaScript code within any single script is run from top to bottom, subject, of course, to JavaScript’s conditionals, loops, and other control statements. Some scripts don’t really do anything during this first phase and instead just define functions and classes for use in the second phase. Other scripts might do significant work during the first phase and then do nothing in the second. Imagine a script at the very end of a document that finds all `<h1>` and `<h2>` tags in the document and modifies the document by generating and inserting a table of contents at the beginning of the document. This could be done entirely in the first phase. (See §15.3.6 for an example that does exactly this.)\n\nOnce the document is loaded and all scripts have run, JavaScript execution enters its second phase. This phase is asynchronous and event-driven. If a script is going to participate in this second phase, then one of the things it must have done during the first phase is to register at least one event handler or other callback function that will be invoked asynchronously. During this event-driven second phase, the web browser invokes event handler functions and other callbacks in response to events that occur asynchronously. Event handlers are most commonly invoked in response to user input (mouse clicks, keystrokes, etc.) but may also be triggered by network activity, document and resource loading, elapsed time, or errors in JavaScript code. Events and event handlers are described in detail in §15.2.\n\nSome of the first events to occur during the event-driven phase are the “DOMContentLoaded” and “load” events. “DOMContentLoaded” is triggered when the HTML document has been completely loaded and parsed. The “load” event is triggered when all of the document’s external resources—such as images—are also fully loaded. JavaScript programs often use one of these events as a trigger or starting signal. It is common to see programs whose scripts define functions but take no action other than registering an event handler function to be triggered by the “load” event at the beginning of the event-driven phase of execution. It is this “load” event handler that then manipulates the document and does whatever it is that the program is supposed to do. Note that it is common in JavaScript programming for an event handler function such as the “load” event handler described here to register other event handlers.\n\nThe loading phase of a JavaScript program is relatively short: ideally less than a second. Once the document is loaded, the event-driven phase lasts for as long as the document is displayed by the web browser. Because this phase is asynchronous and event-driven, there may be long periods of inactivity where no JavaScript is executed, punctuated by bursts of activity triggered by user or network events. We’ll cover these two phases in more detail next.\n\nCLIENT-SIDE JAVASCRIPT THREADING MODEL\nJavaScript is a single-threaded language, and single-threaded execution makes for much simpler programming: you can write code with the assurance that two event handlers will never run at the same time. You can manipulate document content knowing that no other thread is attempting to modify it at the same time, and you never need to worry about locks, deadlock, or race conditions when writing JavaScript code.\n\nSingle-threaded execution means that web browsers stop responding to user input while scripts and event handlers are executing. This places a burden on JavaScript programmers: it means that JavaScript scripts and event handlers must not run for too long. If a script performs a computationally intensive task, it will introduce a delay into document loading, and the user will not see the document content until the script completes. If an event handler performs a computationally intensive task, the browser may become nonresponsive, possibly causing the user to think that it has crashed.\n\nThe web platform defines a controlled form of concurrency called a “web worker.” A web worker is a background thread for performing computationally intensive tasks without freezing the user interface. The code that runs in a web worker thread does not have access to document content, does not share any state with the main thread or with other workers, and can only communicate with the main thread and other workers through asynchronous message events, so the concurrency is not detectable to the main thread, and web workers do not alter the basic single-threaded execution model of JavaScript programs. See §15.13 for full details on the web’s safe threading mechanism.\n\nCLIENT-SIDE JAVASCRIPT TIMELINE\nWe’ve already seen that JavaScript programs begin in a script-execution phase and then transition to an event-handling phase. These two phases can be further broken down into the following steps:\n\n1. The web browser creates a Document object and begins parsing the web page, adding Element objects and Text nodes to the document as it parses HTML elements and their textual content. The document.readyState property has the value “loading” at this stage.\n2. When the HTML parser encounters a `<script>` tag that does not have any of the async, defer, or type=\"module\" attributes, it adds that script tag to the document and then executes the script. The script is executed synchronously, and the HTML parser pauses while the script downloads (if necessary) and runs. A script like this can use document.write() to insert text into the input stream, and that text will become part of the document when the parser resumes. A script like this often simply defines functions and registers event handlers for later use, but it can traverse and manipulate the document tree as it exists at that time. That is, non-module scripts that do not have an async or defer attribute can see their own `<script>` tag and document content that comes before it.\n3. When the parser encounters a `<script>` element that has the async attribute set, it begins downloading the script text (and if the script is a module, it also recursively downloads all of the script’s dependencies) and continues parsing the document. The script will be executed as soon as possible after it has downloaded, but the parser does not stop and wait for it to download. Asynchronous scripts must not use the document.write() method. They can see their own `<script>` tag and all document content that comes before it, and may or may not have access to additional document content.\n4. When the document is completely parsed, the document.readyState property changes to “interactive.”\n5. Any scripts that had the defer attribute set (along with any module scripts that do not have an async attribute) are executed in the order in which they appeared in the document. Async scripts may also be executed at this time. Deferred scripts have access to the complete document and they must not use the document.write() method.\n6. The browser fires a “DOMContentLoaded” event on the Document object. This marks the transition from synchronous script-execution phase to the asynchronous, event-driven phase of program execution. Note, however, that there may still be async scripts that have not yet executed at this point.\n7. The document is completely parsed at this point, but the browser may still be waiting for additional content, such as images, to load. When all such content finishes loading, and when all async scripts have loaded and executed, the document.readyState property changes to “complete” and the web browser fires a “load” event on the Window object.\n8. From this point on, event handlers are invoked asynchronously in response to user input events, network events, timer expirations, and so on.\n\n### 15.1.6 Program Input and Output\nLike any program, client-side JavaScript programs process input data to produce output data. There are a variety of inputs available:\n\n- The content of the document itself, which JavaScript code can access with the DOM API (§15.3).\n- User input, in the form of events, such as mouse clicks (or touch-screen taps) on HTML `<button>` elements, or text entered into HTML `<textarea>` elements, for example. §15.2 demonstrates how JavaScript programs can respond to user events like these.\n- The URL of the document being displayed is available to client-side JavaScript as document.URL. If you pass this string to the URL() constructor (§11.9), you can easily access the path, query, and fragment sections of the URL.\n- The content of the HTTP “Cookie” request header is available to client-side code as document.cookie. Cookies are usually used by server-side code for maintaining user sessions, but client-side code can also read (and write) them if necessary. See §15.12.2 for further details.\n- The global navigator property provides access to information about the web browser, the OS it’s running on top of, and the capabilities of each. For example, navigator.userAgent is a string that identifies the web browser, navigator.language is the user’s preferred language, and navigator.hardwareConcurrency returns the number of logical CPUs available to the web browser. Similarly, the global screen property provides access to the user’s display size via the screen.width and screen.height properties. In a sense, these navigator and screen objects are to web browsers what environment variables are to Node programs.\n\nClient-side JavaScript typically produces output, when it needs to, by manipulating the HTML document with the DOM API (§15.3) or by using a higher-level framework such as React or Angular to manipulate the document. Client-side code can also use console.log() and related methods (§11.8) to produce output. But this output is only visible in the web developer console, so it is useful when debugging, but not for user-visible output.\n\n### 15.1.7 Program Errors\nUnlike applications (such as Node applications) that run directly on top of the OS, JavaScript programs in a web browser can’t really “crash.” If an exception occurs while your JavaScript program is running, and if you do not have a catch statement to handle it, an error message will be displayed in the developer console, but any event handlers that have been registered keep running and responding to events.\n\nIf you would like to define an error handler of last resort to be invoked when this kind of uncaught exception occurs, set the onerror property of the Window object to an error handler function. When an uncaught exception propagates all the way up the call stack and an error message is about to be displayed in the developer console, the window.onerror function will be invoked with three string arguments. The first argument to window.onerror is a message describing the error. The second argument is a string that contains the URL of the JavaScript code that caused the error. The third argument is the line number within the document where the error occurred. If the onerror handler returns true, it tells the browser that the handler has handled the error and that no further action is necessary—in other words, the browser should not display its own error message.\n\nWhen a Promise is rejected and there is no .catch() function to handle it, that is a situation much like an unhandled exception: an unanticipated error or a logic error in your program. You can detect this by defining a window.onunhandledrejection function or by using window.addEventListener() to register a handler for “unhandledrejection” events. The event object passed to this handler will have a promise property whose value is the Promise object that rejected and a reason property whose value is what would have been passed to a .catch() function. As with the error handlers described earlier, if you call preventDefault() on the unhandled rejection event object, it will be considered handled and won’t cause an error message in the developer console.\n\nIt is not often necessary to define onerror or onunhandledrejection handlers, but it can be quite useful as a telemetry mechanism if you want to report client-side errors to the server (using the fetch() function to make an HTTP POST request, for example) so that you can get information about unexpected errors that happen in your users’ browsers.\n\n### 15.1.8 The Web Security Model\nThe fact that web pages can execute arbitrary JavaScript code on your personal device has clear security implications, and browser vendors have worked hard to balance two competing goals:\n\n- Defining powerful client-side APIs to enable useful web applications\n- Preventing malicious code from reading or altering your data, compromising your privacy, scamming you, or wasting your time\n\nThe subsections that follow give a quick overview of the security restrictions and issues that you, as a JavaScript programmer, should to be aware of.\n\nWHAT JAVASCRIPT CAN’T DO\nWeb browsers’ first line of defense against malicious code is that they simply do not support certain capabilities. For example, client-side JavaScript does not provide any way to write or delete arbitrary files or list arbitrary directories on the client computer. This means a JavaScript program cannot delete data or plant viruses.\n\nSimilarly, client-side JavaScript does not have general-purpose networking capabilities. A client-side JavaScript program can make HTTP requests (§15.11.1). And another standard, known as WebSockets (§15.11.3), defines a socket-like API for communicating with specialized servers. But neither of these APIs allows unmediated access to the wider network. General-purpose internet clients and servers cannot be written in client-side JavaScript.\n\nTHE SAME-ORIGIN POLICY\nThe same-origin policy is a sweeping security restriction on what web content JavaScript code can interact with. It typically comes into play when a web page includes `<iframe>` elements. In this case, the same-origin policy governs the interactions of JavaScript code in one frame with the content of other frames. Specifically, a script can read only the properties of windows and documents that have the same origin as the document that contains the script.\n\nThe origin of a document is defined as the protocol, host, and port of the URL from which the document was loaded. Documents loaded from different web servers have different origins. Documents loaded through different ports of the same host have different origins. And a document loaded with the http: protocol has a different origin than one loaded with the https: protocol, even if they come from the same web server. Browsers typically treat every file: URL as a separate origin, which means that if you’re working on a program that displays more than one document from the same server, you may not be able to test it locally using file: URLs and will have to run a static web server during development.\n\nIt is important to understand that the origin of the script itself is not relevant to the same-origin policy: what matters is the origin of the document in which the script is embedded. Suppose, for example, that a script hosted by host A is included (using the src property of a `<script>` element) in a web page served by host B. The origin of that script is host B, and the script has full access to the content of the document that contains it. If the document contains an `<iframe>` that contains a second document from host B, then the script also has full access to the content of that second document. But if the top-level document contains another `<iframe>` that displays a document from host C (or even one from host A), then the same-origin policy comes into effect and prevents the script from accessing this nested document.\n\nThe same-origin policy also applies to scripted HTTP requests (see §15.11.1). JavaScript code can make arbitrary HTTP requests to the web server from which the containing document was loaded, but it does not allow scripts to communicate with other web servers (unless those web servers opt in with CORS, as we describe next).\n\nThe same-origin policy poses problems for large websites that use multiple subdomains. For example, scripts with origin orders.example.com might need to read properties from documents on example.com. To support multidomain websites of this sort, scripts can alter their origin by setting document.domain to a domain suffix. So a script with origin https://orders.example.com can change its origin to https://example.com by setting document.domain to “example.com.” But that script cannot set document.domain to “orders.example”, “ample.com”, or “com”.\n\nThe second technique for relaxing the same-origin policy is Cross-Origin Resource Sharing, or CORS, which allows servers to decide which origins they are willing to serve. CORS extends HTTP with a new Origin: request header and a new Access-Control-Allow-Origin response header. It allows servers to use a header to explicitly list origins that may request a file or to use a wildcard and allow a file to be requested by any site. Browsers honor these CORS headers and do not relax same-origin restrictions unless they are present.\n\nCROSS-SITE SCRIPTING\nCross-site scripting, or XSS, is a term for a category of security issues in which an attacker injects HTML tags or scripts into a target website. Client-side JavaScript programmers must be aware of, and defend against, cross-site scripting.\n\nA web page is vulnerable to cross-site scripting if it dynamically generates document content and bases that content on user-submitted data without first “sanitizing” that data by removing any embedded HTML tags from it. As a trivial example, consider the following web page that uses JavaScript to greet the user by name:\n```html\n<script>\nlet name = new URL(document.URL).searchParams.get(\"name\");\ndocument.querySelector('h1').innerHTML = \"Hello \" + name;\n</script>\n```\nThis two-line script extracts input from the “name” query parameter of the document URL. It then uses the DOM API to inject an HTML string into the first `<h1>` tag in the document. This page is intended to be invoked with a URL like this:\n```\nhttp://www.example.com/greet.html?name=David\n```\nWhen used like this, it displays the text “Hello David.” But consider what happens when it is invoked with this query parameter:\n```\nname=%3Cimg%20src=%22x.png%22%20onload=%22alert(%27hacked%27)%22/%3E\n```\nWhen the URL-escaped parameters are decoded, this URL causes the following HTML to be injected into the document:\n```\nHello <img src=\"x.png\" onload=\"alert('hacked')\"/>\n```\nAfter the image loads, the string of JavaScript in the onload attribute is executed. The global alert() function displays a modal dialogue box. A single dialogue box is relatively benign but demonstrates that arbitrary code execution is possible on this site because it displays unsanitized HTML.\n\nCross-site scripting attacks are so called because more than one site is involved. Site B includes a specially crafted link (like the one in the previous example) to site A. If site B can convince users to click the link, they will be taken to site A, but that site will now be running code from site B. That code might deface the page or cause it to malfunction. More dangerously, the malicious code could read cookies stored by site A (perhaps account numbers or other personally identifying information) and send that data back to site B. The injected code could even track the user’s keystrokes and send that data back to site B.\n\nIn general, the way to prevent XSS attacks is to remove HTML tags from any untrusted data before using it to create dynamic document content. You can fix the greet.html file shown earlier by replacing special HTML characters in the untrusted input string with their equivalent HTML entities:\n```js\nname = name\n    .replace(/&/g, \"&amp;\")\n    .replace(/</g, \"&lt;\")\n    .replace(/>/g, \"&gt;\")\n    .replace(/\"/g, \"&quot;\")\n    .replace(/'/g, \"&#x27;\")\n    .replace(/\\//g, \"&#x2F;\")\n```\nAnother approach to the problem of XSS is to structure your web applications so that untrusted content is always displayed in an `<iframe>` with the sandbox attribute set to disable scripting and other capabilities.\n\nCross-site scripting is a pernicious vulnerability whose roots go deep into the architecture of the web. It is worth understanding this vulnerability in-depth, but further discussion is beyond the scope of this book. There are many online resources to help you defend against cross-site scripting.\n\n## 15.2 Events\nClient-side JavaScript programs use an asynchronous event-driven programming model. In this style of programming, the web browser generates an event whenever something interesting happens to the document or browser or to some element or object associated with it. For example, the web browser generates an event when it finishes loading a document, when the user moves the mouse over a hyperlink, or when the user strikes a key on the keyboard. If a JavaScript application cares about a particular type of event, it can register one or more functions to be invoked when events of that type occur. Note that this is not unique to web programming: all applications with graphical user interfaces are designed this way—they sit around waiting to be interacted with (i.e., they wait for events to occur), and then they respond.\n\nIn client-side JavaScript, events can occur on any element within an HTML document, and this fact makes the event model of web browsers significantly more complex than Node’s event model. We begin this section with some important definitions that help to explain that event model:\n\nevent type\nThis string specifies what kind of event occurred. The type “mousemove,” for example, means that the user moved the mouse. The type “keydown” means that the user pressed a key on the keyboard down. And the type “load” means that a document (or some other resource) has finished loading from the network. Because the type of an event is just a string, it’s sometimes called an event name, and indeed, we use this name to identify the kind of event we’re talking about.\n\nevent target\nThis is the object on which the event occurred or with which the event is associated. When we speak of an event, we must specify both the type and the target. A load event on a Window, for example, or a click event on a `<button>` Element. Window, Document, and Element objects are the most common event targets in client-side JavaScript applications, but some events are triggered on other kinds of objects. For example, a Worker object (a kind of thread, covered §15.13) is a target for “message” events that occur when the worker thread sends a message to the main thread.\n\nevent handler, or event listener\nThis function handles or responds to an event.2 Applications register their event handler functions with the web browser, specifying an event type and an event target. When an event of the specified type occurs on the specified target, the browser invokes the handler function. When event handlers are invoked for an object, we say that the browser has “fired,” “triggered,” or “dispatched” the event. There are a number of ways to register event handlers, and the details of handler registration and invocation are explained in §15.2.2 and §15.2.3.\n\nevent object\nThis object is associated with a particular event and contains details about that event. Event objects are passed as an argument to the event handler function. All event objects have a type property that specifies the event type and a target property that specifies the event target. Each event type defines a set of properties for its associated event object. The object associated with a mouse event includes the coordinates of the mouse pointer, for example, and the object associated with a keyboard event contains details about the key that was pressed and the modifier keys that were held down. Many event types define only a few standard properties—such as type and target—and do not carry much other useful information. For those events, it is the simple occurrence of the event, not the event details, that matter.\n\nevent propagation\nThis is the process by which the browser decides which objects to trigger event handlers on. For events that are specific to a single object—such as the “load” event on the Window object or a “message” event on a Worker object—no propagation is required. But when certain kinds of events occur on elements within the HTML document, however, they propagate or “bubble” up the document tree. If the user moves the mouse over a hyperlink, the mousemove event is first fired on the `<a>` element that defines that link. Then it is fired on the containing elements: perhaps a `<p>` element, a `<section>` element, and the Document object itself. It is sometimes more convenient to register a single event handler on a Document or other container element than to register handlers on each individual element you’re interested in. An event handler can stop the propagation of an event so that it will not continue to bubble and will not trigger handlers on containing elements. Handlers do this by invoking a method of the event object. In another form of event propagation, known as event capturing, handlers specially registered on container elements have the opportunity to intercept (or “capture”) events before they are delivered to their actual target. Event bubbling and capturing are covered in detail in §15.2.4.\n\nSome events have default actions associated with them. When a click event occurs on a hyperlink, for example, the default action is for the browser to follow the link and load a new page. Event handlers can prevent this default action by invoking a method of the event object. This is sometimes called “canceling” the event and is covered in §15.2.5.\n\n### 15.2.1 Event Categories\nClient-side JavaScript supports such a large number of event types that there is no way this chapter can cover them all. It can be useful, though, to group events into some general categories, to illustrate the scope and wide variety of supported events:\n\nDevice-dependent input events\nThese events are directly tied to a specific input device, such as the mouse or keyboard. They include event types such as “mousedown,” “mousemove,” “mouseup,” “touchstart,” “touchmove,” “touchend,” “keydown,” and “keyup.”\n\nDevice-independent input events\nThese input events are not directly tied to a specific input device. The “click” event, for example, indicates that a link or button (or other document element) has been activated. This is often done via a mouse click, but it could also be done by keyboard or (on touch-sensitive devices) with a tap. The “input” event is a device-independent alternative to the “keydown” event and supports keyboard input as well as alternatives such as cut-and-paste and input methods used for ideographic scripts. The “pointerdown,” “pointermove,” and “pointerup” event types are device-independent alternatives to mouse and touch events. They work for mouse-type pointers, for touch screens, and for pen- or stylus-style input as well.\n\nUser interface events\nUI events are higher-level events, often on HTML form elements that define a user interface for a web application. They include the “focus” event (when a text input field gains keyboard focus), the “change” event (when the user changes the value displayed by a form element), and the “submit” event (when the user clicks a Submit button in a form).\n\nState-change events\nSome events are not triggered directly by user activity, but by network or browser activity, and indicate some kind of life-cycle or state-related change. The “load” and “DOMContentLoaded” events—fired on the Window and Document objects, respectively, at the end of document loading—are probably the most commonly used of these events (see “Client-side JavaScript timeline”). Browsers fire “online” and “offline” events on the Window object when network connectivity changes. The browser’s history management mechanism (§15.10.4) fires the “popstate” event in response to the browser’s Back button.\n\nAPI-specific events\nA number of web APIs defined by HTML and related specifications include their own event types. The HTML `<video>` and `<audio>` elements define a long list of associated event types such as “waiting,” “playing,” “seeking,” “volumechange,” and so on, and you can use them to customize media playback. Generally speaking, web platform APIs that are asynchronous and were developed before Promises were added to JavaScript are event-based and define API-specific events. The IndexedDB API, for example (§15.12.3), fires “success” and “error” events when database requests succeed or fail. And although the new fetch() API (§15.11.1) for making HTTP requests is Promise-based, the XMLHttpRequest API that it replaces defines a number of API-specific event types.\n\n### 15.2.2 Registering Event Handlers\nThere are two basic ways to register event handlers. The first, from the early days of the web, is to set a property on the object or document element that is the event target. The second (newer and more general) technique is to pass the handler to the addEventListener() method of the object or element.\n\nSETTING EVENT HANDLER PROPERTIES\nThe simplest way to register an event handler is by setting a property of the event target to the desired event handler function. By convention, event handler properties have names that consist of the word “on” followed by the event name: onclick, onchange, onload, onmouseover, and so on. Note that these property names are case sensitive and are written in all lowercase,3 even when the event type (such as “mousedown”) consists of multiple words. The following code includes two event handler registrations of this kind:\n```js\n// Set the onload property of the Window object to a function.\n// The function is the event handler: it is invoked when the document loads.\nwindow.onload = function() {\n    // Look up a <form> element\n    let form = document.querySelector(\"form#shipping\");\n    // Register an event handler function on the form that will be invoked\n    // before the form is submitted. Assume isFormValid() is defined elsewhere.\n    form.onsubmit = function(event) { // When the user submits the form\n        if (!isFormValid(this)) {     // check whether form inputs are valid\n            event.preventDefault();   // and if not, prevent form submission.\n        }\n    };\n};\n```\nThe shortcoming of event handler properties is that they are designed around the assumption that event targets will have at most one handler for each type of event. It is often better to register event handlers using addEventListener() because that technique does not overwrite any previously registered handlers.\n\nSETTING EVENT HANDLER ATTRIBUTES\nThe event handler properties of document elements can also be defined directly in the HTML file as attributes on the corresponding HTML tag. (Handlers that would be registered on the Window element with JavaScript can be defined with attributes on the `<body>` tag in HTML.) This technique is generally frowned upon in modern web development, but it is possible, and it’s documented here because you may still see it in existing code.\n\nWhen defining an event handler as an HTML attribute, the attribute value should be a string of JavaScript code. That code should be the body of the event handler function, not a complete function declaration. That is, your HTML event handler code should not be surrounded by curly braces and prefixed with the function keyword. For example:\n```html\n<button onclick=\"console.log('Thank you');\">Please Click</button>\n```\nIf an HTML event handler attribute contains multiple JavaScript statements, you must remember to separate those statements with semicolons or break the attribute value across multiple lines.\n\nWhen you specify a string of JavaScript code as the value of an HTML event handler attribute, the browser converts your string into a function that works something like this one:\n```js\nfunction(event) {\n    with(document) {\n        with(this.form || {}) {\n            with(this) {\n                /* your code here */\n            }\n        }\n    }\n}\n```\nThe event argument means that your handler code can refer to the current event object as event. The with statements mean that the code of your handler can refer to the properties of the target object, the containing `<form>` (if any), and the containing Document object directly, as if they were variables in scope. The with statement is forbidden in strict mode (§5.6.3), but JavaScript code in HTML attributes is never strict. Event handlers defined in this way are executed in an environment in which unexpected variables are defined. This can be a source of confusing bugs and is a good reason to avoid writing event handlers in HTML.\n\nADDEVENTLISTENER()\nAny object that can be an event target—this includes the Window and Document objects and all document Elements—defines a method named addEventListener() that you can use to register an event handler for that target. addEventListener() takes three arguments. The first is the event type for which the handler is being registered. The event type (or name) is a string that does not include the “on” prefix used when setting event handler properties. The second argument to addEventListener() is the function that should be invoked when the specified type of event occurs. The third argument is optional and is explained below.\n\nThe following code registers two handlers for the “click” event on a `<button>` element. Note the differences between the two techniques used:\n```html\n<button id=\"mybutton\">Click me</button>\n<script>\nlet b = document.querySelector(\"#mybutton\");\nb.onclick = function() { console.log(\"Thanks for clicking me!\"); };\nb.addEventListener(\"click\", () => { console.log(\"Thanks again!\"); });\n</script>\n```\nCalling addEventListener() with “click” as its first argument does not affect the value of the onclick property. In this code, a button click will log two messages to the developer console. And if we called addEventListener() first and then set onclick, we would still log two messages, just in the opposite order. More importantly, you can call addEventListener() multiple times to register more than one handler function for the same event type on the same object. When an event occurs on an object, all of the handlers registered for that type of event are invoked in the order in which they were registered. Invoking addEventListener() more than once on the same object with the same arguments has no effect—the handler function remains registered only once, and the repeated invocation does not alter the order in which handlers are invoked.\n\naddEventListener() is paired with a removeEventListener() method that expects the same two arguments (plus an optional third) but removes an event handler function from an object rather than adding it. It is often useful to temporarily register an event handler and then remove it soon afterward. For example, when you get a “mousedown” event, you might register temporary event handlers for “mousemove” and “mouseup” events so that you can see if the user drags the mouse. You’d then deregister these handlers when the “mouseup” event arrives. In such a situation, your event handler removal code might look like this:\n```js\ndocument.removeEventListener(\"mousemove\", handleMouseMove);\ndocument.removeEventListener(\"mouseup\", handleMouseUp);\n```\nThe optional third argument to addEventListener() is a boolean value or object. If you pass true, then your handler function is registered as a capturing event handler and is invoked at a different phase of event dispatch. We’ll cover event capturing in §15.2.4. If you pass a third argument of true when you register an event listener, then you must also pass true as the third argument to removeEventListener() if you want to remove the handler.\n\nRegistering a capturing event handler is only one of the three options that addEventListener() supports, and instead of passing a single boolean value, you can also pass an object that explicitly specifies the options you want:\n```js\ndocument.addEventListener(\"click\", handleClick, {\n    capture: true,\n    once: true,\n    passive: true\n});\n```\nIf the Options object has a capture property set to true, then the event handler will be registered as a capturing handler. If that property is false or is omitted, then the handler will be non-capturing.\n\nIf the Options object has a once property set to true, then the event listener will be automatically removed after it is triggered once. If this property is false or is omitted, then the handler is never automatically removed.\n\nIf the Options object has a passive property set to true, it indicates that the event handler will never call preventDefault() to cancel the default action (see §15.2.5). This is particularly important for touch events on mobile devices—if event handlers for “touchmove” events can prevent the browser’s default scrolling action, then the browser cannot implement smooth scrolling. This passive property provides a way to register a potentially disruptive event handler of this sort but lets the web browser know that it can safely begin its default behavior—such as scrolling—while the event handler is running. Smooth scrolling is so important for a good user experience that Firefox and Chrome make “touchmove” and “mousewheel” events passive by default. So if you actually want to register a handler that calls preventDefault() for one of these events, you should explicitly set the passive property to false.\n\nYou can also pass an Options object to removeEventListener(), but the capture property is the only one that is relevant. There is no need to specify once or passive when removing a listener, and these properties are ignored.\n\n### 15.2.3 Event Handler Invocation\nOnce you’ve registered an event handler, the web browser will invoke it automatically when an event of the specified type occurs on the specified object. This section describes event handler invocation in detail, explaining event handler arguments, the invocation context (the this value), and the meaning of the return value of an event handler.\n\nEVENT HANDLER ARGUMENT\nEvent handlers are invoked with an Event object as their single argument. The properties of the Event object provide details about the event:\n\ntype\nThe type of the event that occurred.\n\ntarget\nThe object on which the event occurred.\n\ncurrentTarget\nFor events that propagate, this property is the object on which the current event handler was registered.\n\ntimeStamp\nA timestamp (in milliseconds) that represents when the event occurred but that does not represent an absolute time. You can determine the elapsed time between two events by subtracting the timestamp of the first event from the timestamp of the second.\n\nisTrusted\nThis property will be true if the event was dispatched by the web browser itself and false if the event was dispatched by JavaScript code.\n\nSpecific kinds of events have additional properties. Mouse and pointer events, for example, have clientX and clientY properties that specify the window coordinates at which the event occurred.\n\nEVENT HANDLER CONTEXT\nWhen you register an event handler by setting a property, it looks as if you are defining a new method on the target object:\n```js\ntarget.onclick = function() { /* handler code */ };\n```\nIt isn’t surprising, therefore, that event handlers are invoked as methods of the object on which they are defined. That is, within the body of an event handler, the this keyword refers to the object on which the event handler was registered.\n\nHandlers are invoked with the target as their this value, even when registered using addEventListener(). This does not work for handlers defined as arrow functions, however: arrow functions always have the same this value as the scope in which they are defined.\n\nHANDLER RETURN VALUE\nIn modern JavaScript, event handlers should not return anything. You may see event handlers that return values in older code, and the return value is typically a signal to the browser that it should not perform the default action associated with the event. If the onclick handler of a Submit button in a form returns false, for example, then the web browser will not submit the form (usually because the event handler determined that the user’s input fails client-side validation).\n\nThe standard and preferred way to prevent the browser from performing a default action is to call the preventDefault() method (§15.2.5) on the Event object.\n\nINVOCATION ORDER\nAn event target may have more than one event handler registered for a particular type of event. When an event of that type occurs, the browser invokes all of the handlers in the order in which they were registered. Interestingly, this is true even if you mix event handlers registered with addEventListener() with an event handler registered on an object property like onclick.\n\n### 15.2.4 Event Propagation\nWhen the target of an event is the Window object or some other standalone object, the browser responds to an event simply by invoking the appropriate handlers on that one object. When the event target is a Document or document Element, however, the situation is more complicated.\n\nAfter the event handlers registered on the target element are invoked, most events “bubble” up the DOM tree. The event handlers of the target’s parent are invoked. Then the handlers registered on the target’s grandparent are invoked. This continues up to the Document object, and then beyond to the Window object. Event bubbling provides an alternative to registering handlers on lots of individual document elements: instead, you can register a single handler on a common ancestor element and handle events there. You might register a “change” handler on a `<form>` element, for example, instead of registering a “change” handler for every element in the form.\n\nMost events that occur on document elements bubble. Notable exceptions are the “focus,” “blur,” and “scroll” events. The “load” event on document elements bubbles, but it stops bubbling at the Document object and does not propagate on to the Window object. (The “load” event handlers of the Window object are triggered only when the entire document has loaded.)\n\nEvent bubbling is the third “phase” of event propagation. The invocation of the event handlers of the target object itself is the second phase. The first phase, which occurs even before the target handlers are invoked, is called the “capturing” phase. Recall that addEventListener() takes an optional third argument. If that argument is true, or {capture:true}, then the event handler is registered as a capturing event handler for invocation during this first phase of event propagation. The capturing phase of event propagation is like the bubbling phase in reverse. The capturing handlers of the Window object are invoked first, then the capturing handlers of the Document object, then of the body object, and so on down the DOM tree until the capturing event handlers of the parent of the event target are invoked. Capturing event handlers registered on the event target itself are not invoked.\n\nEvent capturing provides an opportunity to peek at events before they are delivered to their target. A capturing event handler can be used for debugging, or it can be used along with the event cancellation technique described in the next section to filter events so that the target event handlers are never actually invoked. One common use for event capturing is handling mouse drags, where mouse motion events need to be handled by the object being dragged, not the document elements over which it is dragged.\n\n### 15.2.5 Event Cancellation\nBrowsers respond to many user events, even if your code does not: when the user clicks the mouse on a hyperlink, the browser follows the link. If an HTML text input element has the keyboard focus and the user types a key, the browser will enter the user’s input. If the user moves their finger across a touch-screen device, the browser scrolls. If you register an event handler for events like these, you can prevent the browser from performing its default action by invoking the preventDefault() method of the event object. (Unless you registered the handler with the passive option, which makes preventDefault() ineffective.)\n\nCanceling the default action associated with an event is only one kind of event cancellation. We can also cancel the propagation of events by calling the stopPropagation() method of the event object. If there are other handlers defined on the same object, the rest of those handlers will still be invoked, but no event handlers on any other object will be invoked after stopPropagation() is called. stopPropagation() works during the capturing phase, at the event target itself, and during the bubbling phase. stopImmediatePropagation() works like stopPropagation(), but it also prevents the invocation of any subsequent event handlers registered on the same object.\n\n### 15.2.6 Dispatching Custom Events\nClient-side JavaScript’s event API is a relatively powerful one, and you can use it to define and dispatch your own events. Suppose, for example, that your program periodically needs to perform a long calculation or make a network request and that, while this operation is pending, other operations are not possible. You want to let the user know about this by displaying “spinners” to indicate that the application is busy. But the module that is busy should not need to know where the spinners should be displayed. Instead, that module might just dispatch an event to announce that it is busy and then dispatch another event when it is no longer busy. Then, the UI module can register event handlers for those events and take whatever UI actions are appropriate to notify the user.\n\nIf a JavaScript object has an addEventListener() method, then it is an “event target,” and this means it also has a dispatchEvent() method. You can create your own event object with the CustomEvent() constructor and pass it to dispatchEvent(). The first argument to CustomEvent() is a string that specifies the type of your event, and the second argument is an object that specifies the properties of the event object. Set the detail property of this object to a string, object, or other value that represents the content of your event. If you plan to dispatch your event on a document element and want it to bubble up the document tree, add bubbles:true to the second argument:\n```js\n// Dispatch a custom event so the UI knows we are busy\ndocument.dispatchEvent(new CustomEvent(\"busy\", { detail: true }));\n\n// Perform a network operation\nfetch(url)\n  .then(handleNetworkResponse)\n  .catch(handleNetworkError)\n  .finally(() => {\n      // After the network request has succeeded or failed, dispatch\n      // another event to let the UI know that we are no longer busy.\n      document.dispatchEvent(new CustomEvent(\"busy\", { detail: false }));\n  });\n\n// Elsewhere, in your program you can register a handler for \"busy\" events\n// and use it to show or hide the spinner to let the user know.\ndocument.addEventListener(\"busy\", (e) => {\n    if (e.detail) {\n        showSpinner();\n    } else {\n        hideSpinner();\n    }\n});\n```\n## 15.3 Scripting Documents\nClient-side JavaScript exists to turn static HTML documents into interactive web applications. So scripting the content of web pages is really the central purpose of JavaScript.\n\nEvery Window object has a document property that refers to a Document object. The Document object represents the content of the window, and it is the subject of this section. The Document object does not stand alone, however. It is the central object in the DOM for representing and manipulating document content.\n\nThe DOM was introduced in §15.1.2. This section explains the API in detail. It covers:\n\n- How to query or select individual elements from a document.\n- How to traverse a document, and how to find the ancestors, siblings, and descendants of any document element.\n- How to query and set the attributes of document elements.\n- How to query, set, and modify the content of a document.\n- How to modify the structure of a document by creating, inserting, and deleting nodes.\n\n### 15.3.1 Selecting Document Elements\nClient-side JavaScript programs often need to manipulate one or more elements within the document. The global document property refers to the Document object, and the Document object has head and body properties that refer to the Element objects for the `<head>` and `<body>` tags, respectively. But a program that wants to manipulate an element embedded more deeply in the document must somehow obtain or select the Element objects that refer to those document elements.\n\nSELECTING ELEMENTS WITH CSS SELECTORS\nCSS stylesheets have a very powerful syntax, known as selectors, for describing elements or sets of elements within a document. The DOM methods querySelector() and querySelectorAll() allow us to find the element or elements within a document that match a specified CSS selector. Before we cover the methods, we’ll start with a quick tutorial on CSS selector syntax.\n\nCSS selectors can describe elements by tag name, the value of their id attribute, or the words in their class attribute:\n```js\ndiv                     // Any <div> element\n#nav                    // The element with id=\"nav\"\n.warning                // Any element with \"warning\" in its class attribute\n```\nThe # character is used to match based on the id attribute, and the . character is used to match based on the class attribute. Elements can also be selected based on more general attribute values:\n```js\np[lang=\"fr\"]            // A paragraph written in French: <p lang=\"fr\">\n*[name=\"x\"]             // Any element with a name=\"x\" attribute\n```\nNote that these examples combine a tag name selector (or the * tag name wildcard) with an attribute selector. More complex combinations are also possible:\n```js\nspan.fatal.error        // Any <span> with \"fatal\" and \"error\" in its class\nspan[lang=\"fr\"].warning // Any <span> in French with class \"warning\"\n```\nSelectors can also specify document structure:\n```js\n#log span               // Any <span> descendant of the element with id=\"log\"\n#log>span               // Any <span> child of the element with id=\"log\"\nbody>h1:first-child     // The first <h1> child of the <body>\nimg + p.caption         // A <p> with class \"caption\" immediately after an <img>\nh2 ~ p                  // Any <p> that follows an <h2> and is a sibling of it\n```\nIf two selectors are separated by a comma, it means that we’ve selected elements that match either one of the selectors:\n```js\nbutton, input[type=\"button\"] // All <button> and <input type=\"button\"> elements\n```\nAs you can see, CSS selectors allow us to refer to elements within a document by type, ID, class, attributes, and position within the document. The querySelector() method takes a CSS selector string as its argument and returns the first matching element in the document that it finds, or returns null if none match:\n```js\n// Find the document element for the HTML tag with attribute id=\"spinner\"\nlet spinner = document.querySelector(\"#spinner\");\n```\nquerySelectorAll() is similar, but it returns all matching elements in the document rather than just returning the first:\n```js\n// Find all Element objects for <h1>, <h2>, and <h3> tags\nlet titles = document.querySelectorAll(\"h1, h2, h3\");\n```\nThe return value of querySelectorAll() is not an array of Element objects. Instead, it is an array-like object known as a NodeList. NodeList objects have a length property and can be indexed like arrays, so you can loop over them with a traditional for loop. NodeLists are also iterable, so you can use them with for/of loops as well. If you want to convert a NodeList into a true array, simply pass it to Array.from().\n\nThe NodeList returned by querySelectorAll() will have a length property set to 0 if there are not any elements in the document that match the specified selector.\n\nquerySelector() and querySelectorAll() are implemented by the Element class as well as by the Document class. When invoked on an element, these methods will only return elements that are descendants of that element.\n\nNote that CSS defines ::first-line and ::first-letter pseudoelements. In CSS, these match portions of text nodes rather than actual elements. They will not match if used with querySelectorAll() or querySelector(). Also, many browsers will refuse to return matches for the :link and :visited pseudoclasses, as this could expose information about the user’s browsing history.\n\nAnother CSS-based element selection method is closest(). This method is defined by the Element class and takes a selector as its only argument. If the selector matches the element it is invoked on, it returns that element. Otherwise, it returns the closest ancestor element that the selector matches, or returns null if none matched. In a sense, closest() is the opposite of querySelector(): closest() starts at an element and looks for a match above it in the tree, while querySelector() starts with an element and looks for a match below it in the tree. closest() can be useful when you have registered an event handler at a high level in the document tree. If you are handling a “click” event, for example, you might want to know whether it is a click a hyperlink. The event object will tell you what the target was, but that target might be the text inside a link rather than the hyperlink’s `<a>` tag itself. Your event handler could look for the nearest containing hyperlink like this:\n```js\n// Find the closest enclosing <a> tag that has an href attribute.\nlet hyperlink = event.target.closest(\"a[href]\");\n```\nHere is another way you might use closest():\n```js\n// Return true if the element e is inside of an HTML list element\nfunction insideList(e) {\n    return e.closest(\"ul,ol,dl\") !== null;\n}\n```\nThe related method matches() does not return ancestors or descendants: it simply tests whether an element is matched by a CSS selector and returns true if so and false otherwise:\n```js\n// Return true if e is an HTML heading element\nfunction isHeading(e) {\n    return e.matches(\"h1,h2,h3,h4,h5,h6\");\n}\n```\nOTHER ELEMENT SELECTION METHODS\nIn addition to querySelector() and querySelectorAll(), the DOM also defines a number of older element selection methods that are more or less obsolete now. You may still see some of these methods (especially getElementById()) in use, however:\n```js\n// Look up an element by id. The argument is just the id, without\n// the CSS selector prefix #. Similar to document.querySelector(\"#sect1\")\nlet sect1 = document.getElementById(\"sect1\");\n\n// Look up all elements (such as form checkboxes) that have a name=\"color\"\n// attribute. Similar to document.querySelectorAll('*[name=\"color\"]');\nlet colors = document.getElementsByName(\"color\");\n\n// Look up all <h1> elements in the document.\n// Similar to document.querySelectorAll(\"h1\")\nlet headings = document.getElementsByTagName(\"h1\");\n\n// getElementsByTagName() is also defined on elements.\n// Get all <h2> elements within the sect1 element.\nlet subheads = sect1.getElementsByTagName(\"h2\");\n\n// Look up all elements that have class \"tooltip.\"\n// Similar to document.querySelectorAll(\".tooltip\")\nlet tooltips = document.getElementsByClassName(\"tooltip\");\n\n// Look up all descendants of sect1 that have class \"sidebar\"\n// Similar to sect1.querySelectorAll(\".sidebar\")\nlet sidebars = sect1.getElementsByClassName(\"sidebar\");\n```\nLike querySelectorAll(), the methods in this code return a NodeList (except for getElementById(), which returns a single Element object). Unlike querySelectorAll(), however, the NodeLists returned by these older selection methods are “live,” which means that the length and content of the list can change if the document content or structure changes.\n\nPRESELECTED ELEMENTS\nFor historical reasons, the Document class defines shortcut properties to access certain kinds of nodes. The images, forms, and links properties, for example, provide easy access to the `<img>`, `<form>`, and `<a>` elements (but only `<a>` tags that have an href attribute) of a document. These properties refer to HTMLCollection objects, which are much like NodeList objects, but they can additionally be indexed by element ID or name. With the document.forms property, for example, you can access the `<form id=\"address\">` tag as:\n```js\ndocument.forms.address;\n```\nAn even more outdated API for selecting elements is the document.all property, which is like an HTMLCollection for all elements in the document. document.all is deprecated, and you should no longer use it.\n\n### 15.3.2 Document Structure and Traversal\nOnce you have selected an Element from a Document, you sometimes need to find structurally related portions (parent, siblings, children) of the document. When we are primarily interested in the Elements of a document instead of the text within them (and the whitespace between them, which is also text), there is a traversal API that allows us to treat a document as a tree of Element objects, ignoring Text nodes that are also part of the document. This traversal API does not involve any methods; it is simply a set of properties on Element objects that allow us to refer to the parent, children, and siblings of a given element:\n\nparentNode\nThis property of an element refers to the parent of the element, which will be another Element or a Document object.\n\nchildren\nThis NodeList contains the Element children of an element, but excludes non-Element children like Text nodes (and Comment nodes).\n\nchildElementCount\nThe number of Element children. Returns the same value as children.length.\n\nfirstElementChild, lastElementChild\nThese properties refer to the first and last Element children of an Element. They are null if the Element has no Element children.\n\nnextElementSibling, previousElementSibling\nThese properties refer to the sibling Elements immediately before or immediately after an Element, or null if there is no such sibling.\n\nUsing these Element properties, the second child Element of the first child Element of the Document can be referred to with either of these expressions:\n```js\ndocument.children[0].children[1]\ndocument.firstElementChild.firstElementChild.nextElementSibling\n```\n(In a standard HTML document, both of those expressions refer to the `<body>` tag of the document.)\n\nHere are two functions that demonstrate how you can use these properties to recursively do a depth-first traversal of a document invoking a specified function for every element in the document:\n```js\n// Recursively traverse the Document or Element e, invoking the function\n// f on e and on each of its descendants\nfunction traverse(e, f) {\n    f(e);                             // Invoke f() on e\n    for(let child of e.children) {    // Iterate over the children\n        traverse(child, f);           // And recurse on each one\n    }\n}\n\nfunction traverse2(e, f) {\n    f(e);                             // Invoke f() on e\n    let child = e.firstElementChild;  // Iterate the children linked-list style\n    while(child !== null) {\n        traverse2(child, f);          // And recurse\n        child = child.nextElementSibling;\n    }\n}\n```\nDOCUMENTS AS TREES OF NODES\nIf you want to traverse a document or some portion of a document and do not want to ignore the Text nodes, you can use a different set of properties defined on all Node objects. This will allow you to see Elements, Text nodes, and even Comment nodes (which represent HTML comments in the document).\n\nAll Node objects define the following properties:\n\nparentNode\nThe node that is the parent of this one, or null for nodes like the Document object that have no parent.\n\nchildNodes\nA read-only NodeList that that contains all children (not just Element children) of the node.\n\nfirstChild, lastChild\nThe first and last child nodes of a node, or null if the node has no children.\n\nnextSibling, previousSibling\nThe next and previous sibling nodes of a node. These properties connect nodes in a doubly linked list.\n\nnodeType\nA number that specifies what kind of node this is. Document nodes have value 9. Element nodes have value 1. Text nodes have value 3. Comment nodes have value 8.\n\nnodeValue\nThe textual content of a Text or Comment node.\n\nnodeName\nThe HTML tag name of an Element, converted to uppercase.\n\nUsing these Node properties, the second child node of the first child of the Document can be referred to with expressions like these:\n```js\ndocument.childNodes[0].childNodes[1]\ndocument.firstChild.firstChild.nextSibling\n```\nSuppose the document in question is the following:\n```html\n<html><head><title>Test</title></head><body>Hello World!</body></html>\n```\nThen the second child of the first child is the `<body>` element. It has a nodeType of 1 and a nodeName of “BODY”.\n\nNote, however, that this API is extremely sensitive to variations in the document text. If the document is modified by inserting a single newline between the `<html>` and the `<head>` tag, for example, the Text node that represents that newline becomes the first child of the first child, and the second child is the `<head>` element instead of the `<body>` element.\n\nTo demonstrate this Node-based traversal API, here is a function that returns all of the text within an element or document:\n```js\n// Return the plain-text content of element e, recursing into child elements.\n// This method works like the textContent property\nfunction textContent(e) {\n    let s = \"\";                        // Accumulate the text here\n    for(let child = e.firstChild; child !== null; child = child.nextSibling) {\n        let type = child.nodeType;\n        if (type === 3) {              // If it is a Text node\n            s += child.nodeValue;      // add the text content to our string.\n        } else if (type === 1) {       // And if it is an Element node\n            s += textContent(child);   // then recurse.\n        }\n    }\n    return s;\n}\n```\nThis function is a demonstration only—in practice, you would simply write e.textContent to obtain the textual content of the element e.\n\n### 15.3.3 Attributes\nHTML elements consist of a tag name and a set of name/value pairs known as attributes. The `<a>` element that defines a hyperlink, for example, uses the value of its href attribute as the destination of the link.\n\nThe Element class defines general getAttribute(), setAttribute(), hasAttribute(), and removeAttribute() methods for querying, setting, testing, and removing the attributes of an element. But the attribute values of HTML elements (for all standard attributes of standard HTML elements) are available as properties of the HTMLElement objects that represent those elements, and it is usually much easier to work with them as JavaScript properties than it is to call getAttribute() and related methods.\n\nHTML ATTRIBUTES AS ELEMENT PROPERTIES\nThe Element objects that represent the elements of an HTML document usually define read/write properties that mirror the HTML attributes of the elements. Element defines properties for the universal HTML attributes such as id, title, lang, and dir and event handler properties like onclick. Element-specific subtypes define attributes specific to those elements. To query the URL of an image, for example, you can use the src property of the HTMLElement that represents the `<img>` element:\n```js\nlet image = document.querySelector(\"#main_image\");\nlet url = image.src;       // The src attribute is the URL of the image\nimage.id === \"main_image\"  // => true; we looked up the image by id\n```\nSimilarly, you might set the form-submission attributes of a `<form>` element with code like this:\n```js\nlet f = document.querySelector(\"form\");      // First <form> in the document\nf.action = \"https://www.example.com/submit\"; // Set the URL to submit it to.\nf.method = \"POST\";                           // Set the HTTP request type.\n```\nFor some elements, such as the `<input>` element, some HTML attribute names map to differently named properties. The HTML value attribute of an `<input>`, for example, is mirrored by the JavaScript defaultValue property. The JavaScript value property of the `<input>` element contains the user’s current input, but changes to the value property do not affect the defaultValue property nor the value attribute.\n\nHTML attributes are not case sensitive, but JavaScript property names are. To convert an attribute name to the JavaScript property, write it in lowercase. If the attribute is more than one word long, however, put the first letter of each word after the first in uppercase: defaultChecked and tabIndex, for example. Event handler properties like onclick are an exception, however, and are written in lowercase.\n\nSome HTML attribute names are reserved words in JavaScript. For these, the general rule is to prefix the property name with “html”. The HTML for attribute (of the `<label>` element), for example, becomes the JavaScript htmlFor property. “class” is a reserved word in JavaScript, and the very important HTML class attribute is an exception to the rule: it becomes className in JavaScript code.\n\nThe properties that represent HTML attributes usually have string values. But when the attribute is a boolean or numeric value (the defaultChecked and maxLength attributes of an `<input>` element, for example), the properties are booleans or numbers instead of strings. Event handler attributes always have functions (or null) as their values.\n\nNote that this property-based API for getting and setting attribute values does not define any way to remove an attribute from an element. In particular, the delete operator cannot be used for this purpose. If you need to delete an attribute, use the removeAttribute() method.\n\nTHE CLASS ATTRIBUTE\nThe class attribute of an HTML element is a particularly important one. Its value is a space-separated list of CSS classes that apply to the element and affect how it is styled with CSS. Because class is a reserved word in JavaScript, the value of this attribute is available through the className property on Element objects. The className property can set and return the value of the class attribute as a string. But the class attribute is poorly named: its value is a list of CSS classes, not a single class, and it is common in client-side JavaScript programming to want to add and remove individual class names from this list rather than work with the list as a single string.\n\nFor this reason, Element objects define a classList property that allows you to treat the class attribute as a list. The value of the classList property is an iterable Array-like object. Although the name of the property is classList, it behaves more like a set of classes, and defines add(), remove(), contains(), and toggle() methods:\n```js\n// When we want to let the user know that we are busy, we display\n// a spinner. To do this we have to remove the \"hidden\" class and add the\n// \"animated\" class (assuming the stylesheets are configured correctly).\nlet spinner = document.querySelector(\"#spinner\");\nspinner.classList.remove(\"hidden\");\nspinner.classList.add(\"animated\");\n```\nDATASET ATTRIBUTES\nIt is sometimes useful to attach additional information to HTML elements, typically when JavaScript code will be selecting those elements and manipulating them in some way. In HTML, any attribute whose name is lowercase and begins with the prefix “data-” is considered valid, and you can use them for any purpose. These “dataset attributes” will not affect the presentation of the elements on which they appear, and they define a standard way to attach additional data without compromising document validity.\n\nIn the DOM, Element objects have a dataset property that refers to an object that has properties that correspond to the data- attributes with their prefix removed. Thus, dataset.x would hold the value of the data-x attribute. Hyphenated attributes map to camelCase property names: the attribute data-section-number becomes the property dataset.sectionNumber.\n\nSuppose an HTML document contains this text:\n```html\n<h2 id=\"title\" data-section-number=\"16.1\">Attributes</h2>\n```\nThen you could write JavaScript like this to access that section number:\n```js\nlet number = document.querySelector(\"#title\").dataset.sectionNumber;\n```\n### 15.3.4 Element Content\nLook again at the document tree pictured in Figure 15-1, and ask yourself what the “content” of the `<p>` element is. There are two ways we might answer this question:\n\n- The content is the HTML string “This is a `<i>`simple`</i>` document”.\n- The content is the plain-text string “This is a simple document”.\n\nBoth of these are valid answers, and each answer is useful in its own way. The sections that follow explain how to work with the HTML representation and the plain-text representation of an element’s content.\n\nELEMENT CONTENT AS HTML\nReading the innerHTML property of an Element returns the content of that element as a string of markup. Setting this property on an element invokes the web browser’s parser and replaces the element’s current content with a parsed representation of the new string. You can test this out by opening the developer console and typing:\n```js\ndocument.body.innerHTML = \"<h1>Oops</h1>\";\n```\nYou will see that the entire web page disappears and is replaced with the single heading, “Oops”. Web browsers are very good at parsing HTML, and setting innerHTML is usually fairly efficient. Note, however, that appending text to the innerHTML property with the += operator is not efficient because it requires both a serialization step to convert element content to a string and then a parsing step to convert the new string back into element content.\n\nWARNING\nWhen using these HTML APIs, it is very important that you never insert user input into the document. If you do this, you allow malicious users to inject their own scripts into your application. See “Cross-site scripting” for details.\n\nThe outerHTML property of an Element is like innerHTML except that its value includes the element itself. When you query outerHTML, the value includes the opening and closing tags of the element. And when you set outerHTML on an element, the new content replaces the element itself.\n\nA related Element method is insertAdjacentHTML(), which allows you to insert a string of arbitrary HTML markup “adjacent” to the specified element. The markup is passed as the second argument to this method, and the precise meaning of “adjacent” depends on the value of the first argument. This first argument should be a string with one of the values “beforebegin,” “afterbegin,” “beforeend,” or “afterend.” These values correspond to insertion points that are illustrated in Figure 15-2.\n\n<Figures figure=\"15-2\">Insertion points for insertAdjacentHTML()</Figures>\n\n#### ELEMENT CONTENT AS PLAIN TEXT\nSometimes you want to query the content of an element as plain text or to insert plain text into a document (without having to escape the angle brackets and ampersands used in HTML markup). The standard way to do this is with the textContent property:\n```js\nlet para = document.querySelector(\"p\"); // First <p> in the document\nlet text = para.textContent;            // Get the text of the paragraph\npara.textContent = \"Hello World!\";      // Alter the text of the paragraph\n```\nThe textContent property is defined by the Node class, so it works for Text nodes as well as Element nodes. For Element nodes, it finds and returns all text in all descendants of the element.\n\nThe Element class defines an innerText property that is similar to textContent. innerText has some unusual and complex behaviors, such as attempting to preserve table formatting. It is not well specified nor implemented compatibly between browsers, however, and should no longer be used.\n\nTEXT IN `<SCRIPT>` ELEMENTS\nInline `<script>` elements (i.e., those that do not have a src attribute) have a text property that you can use to retrieve their text. The content of a `<script>` element is never displayed by the browser, and the HTML parser ignores angle brackets and ampersands within a script. This makes a `<script>` element an ideal place to embed arbitrary textual data for use by your application. Simply set the type attribute of the element to some value (such as “text/x-custom-data”) that makes it clear that the script is not executable JavaScript code. If you do this, the JavaScript interpreter will ignore the script, but the element will exist in the document tree, and its text property will return the data to you.\n\n### 15.3.5 Creating, Inserting, and Deleting Nodes\nWe’ve seen how to query and alter document content using strings of HTML and of plain text. And we’ve also seen that we can traverse a Document to examine the individual Element and Text nodes that it is made of. It is also possible to alter a document at the level of individual nodes. The Document class defines methods for creating Element objects, and Element and Text objects have methods for inserting, deleting, and replacing nodes in the tree.\n\nCreate a new element with the createElement() method of the Document class and append strings of text or other elements to it with its append() and prepend() methods:\n```js\nlet paragraph = document.createElement(\"p\"); // Create an empty <p> element\nlet emphasis = document.createElement(\"em\"); // Create an empty <em> element\nemphasis.append(\"World\");                    // Add text to the <em> element\nparagraph.append(\"Hello \", emphasis, \"!\");   // Add text and <em> to <p>\nparagraph.prepend(\"¡\");                      // Add more text at start of <p>\nparagraph.innerHTML                          // => \"¡Hello <em>World</em>!\"\n```\nappend() and prepend() take any number of arguments, which can be Node objects or strings. String arguments are automatically converted to Text nodes. (You can create Text nodes explicitly with document.createTextNode(), but there is rarely any reason to do so.) append() adds the arguments to the element at the end of the child list. prepend() adds the arguments at the start of the child list.\n\nIf you want to insert an Element or Text node into the middle of the containing element’s child list, then neither append() or prepend() will work for you. In this case, you should obtain a reference to a sibling node and call before() to insert the new content before that sibling or after() to insert it after that sibling. For example:\n```js\n// Find the heading element with class=\"greetings\"\nlet greetings = document.querySelector(\"h2.greetings\");\n\n// Now insert the new paragraph and a horizontal rule after that heading\ngreetings.after(paragraph, document.createElement(\"hr\"));\n```\nLike append() and prepend(), after() and before() take any number of string and element arguments and insert them all into the document after converting strings to Text nodes. append() and prepend() are only defined on Element objects, but after() and before() work on both Element and Text nodes: you can use them to insert content relative to a Text node.\n\nNote that elements can only be inserted at one spot in the document. If an element is already in the document and you insert it somewhere else, it will be moved to the new location, not copied:\n```js\n// We inserted the paragraph after this element, but now we\n// move it so it appears before the element instead\ngreetings.before(paragraph);\n```\nIf you do want to make a copy of an element, use the cloneNode() method, passing true to copy all of its content:\n```js\n// Make a copy of the paragraph and insert it after the greetings element\ngreetings.after(paragraph.cloneNode(true));\n```\nYou can remove an Element or Text node from the document by calling its remove() method, or you can replace it by calling replaceWith() instead. remove() takes no arguments, and replaceWith() takes any number of strings and elements just like before() and after() do:\n```js\n// Remove the greetings element from the document and replace it with\n// the paragraph element (moving the paragraph from its current location\n// if it is already inserted into the document).\ngreetings.replaceWith(paragraph);\n\n// And now remove the paragraph.\nparagraph.remove();\n```\nThe DOM API also defines an older generation of methods for inserting and removing content. appendChild(), insertBefore(), replaceChild(), and removeChild() are harder to use than the methods shown here and should never be needed.\n\n### 15.3.6 Example: Generating a Table of Contents\nExample 15-1 shows how to dynamically create a table of contents for a document. It demonstrates many of the document scripting techniques described in the previous sections. The example is well commented, and you should have no trouble following the code.\n\nExample 15-1. Generating a table of contents with the DOM API\n```js\n/**\n * TOC.js: create a table of contents for a document.\n *\n * This script runs when the DOMContentLoaded event is fired and\n * automatically generates a table of contents for the document.\n * It does not define any global symbols so it should not conflict\n * with other scripts.\n *\n * When this script runs, it first looks for a document element with\n * an id of \"TOC\". If there is no such element it creates one at the\n * start of the document. Next, the function finds all <h2> through\n * <h6> tags, treats them as section titles, and creates a table of\n * contents within the TOC element. The function adds section numbers\n * to each section heading and wraps the headings in named anchors so\n * that the TOC can link to them. The generated anchors have names\n * that begin with \"TOC\", so you should avoid this prefix in your own\n * HTML.\n *\n * The entries in the generated TOC can be styled with CSS. All\n * entries have a class \"TOCEntry\". Entries also have a class that\n * corresponds to the level of the section heading. <h1> tags generate\n * entries of class \"TOCLevel1\", <h2> tags generate entries of class\n * \"TOCLevel2\", and so on. Section numbers inserted into headings have\n * class \"TOCSectNum\".\n *\n * You might use this script with a stylesheet like this:\n *\n *   #TOC { border: solid black 1px; margin: 10px; padding: 10px; }\n *   .TOCEntry { margin: 5px 0px; }\n *   .TOCEntry a { text-decoration: none; }\n *   .TOCLevel1 { font-size: 16pt; font-weight: bold; }\n *   .TOCLevel2 { font-size: 14pt; margin-left: .25in; }\n *   .TOCLevel3 { font-size: 12pt; margin-left: .5in; }\n *   .TOCSectNum:after { content: \": \"; }\n *\n * To hide the section numbers, use this:\n *\n *   .TOCSectNum { display: none }\n **/\ndocument.addEventListener(\"DOMContentLoaded\", () => {\n    // Find the TOC container element.\n    // If there isn't one, create one at the start of the document.\n    let toc = document.querySelector(\"#TOC\");\n    if (!toc) {\n        toc = document.createElement(\"div\");\n        toc.id = \"TOC\";\n        document.body.prepend(toc);\n    }\n\n    // Find all section heading elements. We're assuming here that the\n    // document title uses <h1> and that sections within the document are\n    // marked with <h2> through <h6>.\n    let headings = document.querySelectorAll(\"h2,h3,h4,h5,h6\");\n\n    // Initialize an array that keeps track of section numbers.\n    let sectionNumbers = [0,0,0,0,0];\n\n    // Now loop through the section header elements we found.\n    for(let heading of headings) {\n        // Skip the heading if it is inside the TOC container.\n        if (heading.parentNode === toc) {\n            continue;\n        }\n\n        // Figure out what level heading it is.\n        // Subtract 1 because <h2> is a level-1 heading.\n        let level = parseInt(heading.tagName.charAt(1)) - 1;\n\n        // Increment the section number for this heading level\n        // and reset all lower heading level numbers to zero.\n        sectionNumbers[level-1]++;\n        for(let i = level; i < sectionNumbers.length; i++) {\n            sectionNumbers[i] = 0;\n        }\n\n        // Now combine section numbers for all heading levels\n        // to produce a section number like 2.3.1.\n        let sectionNumber = sectionNumbers.slice(0, level).join(\".\");\n\n        // Add the section number to the section header title.\n        // We place the number in a <span> to make it styleable.\n        let span = document.createElement(\"span\");\n        span.className = \"TOCSectNum\";\n        span.textContent = sectionNumber;\n        heading.prepend(span);\n\n        // Wrap the heading in a named anchor so we can link to it.\n        let anchor = document.createElement(\"a\");\n        let fragmentName = `TOC${sectionNumber}`;\n        anchor.name = fragmentName;\n        heading.before(anchor);    // Insert anchor before heading\n        anchor.append(heading);    // and move heading inside anchor\n\n        // Now create a link to this section.\n        let link = document.createElement(\"a\");\n        link.href = `#${fragmentName}`;     // Link destination\n\n        // Copy the heading text into the link. This is a safe use of\n        // innerHTML because we are not inserting any untrusted strings.\n        link.innerHTML = heading.innerHTML;\n\n        // Place the link in a div that is styleable based on the level.\n        let entry = document.createElement(\"div\");\n        entry.classList.add(\"TOCEntry\", `TOCLevel${level}`);\n        entry.append(link);\n\n        // And add the div to the TOC container.\n        toc.append(entry);\n    }\n});\n```\n## 15.4 Scripting CSS\nWe’ve seen that JavaScript can control the logical structure and content of HTML documents. It can also control the visual appearance and layout of those documents by scripting CSS. The following subsections explain a few different techniques that JavaScript code can use to work with CSS.\n\nThis is a book about JavaScript, not about CSS, and this section assumes that you already have a working knowledge of how CSS is used to style HTML content. But it’s worth mentioning some of the CSS styles that are commonly scripted from JavaScript:\n\n- Setting the display style to “none” hides an element. You can later show the element by setting display to some other value.\n- You can dynamically position elements by setting the position style to “absolute,” “relative,” or “fixed” and then setting the top and left styles to the desired coordinates. This is important when using JavaScript to display dynamic content like modal dialogues and tooltips.\n- You can shift, scale, and rotate elements with the transform style.\n- You can animate changes to other CSS styles with the transition style. These animations are handled automatically by the web browser and do not require JavaScript, but you can use JavaScript to initiate the animations.\n\n### 15.4.1 CSS Classes\nThe simplest way to use JavaScript to affect the styling of document content is to add and remove CSS class names from the class attribute of HTML tags. This is easy to do with the classList property of Element objects, as explained in “The class attribute”.\n\nSuppose, for example, that your document’s stylesheet includes a definition for a “hidden” class:\n```css\n.hidden {\n  display:none;\n}\n```\nWith this style defined, you can hide (and then show) an element with code like this:\n```js\n// Assume that this \"tooltip\" element has class=\"hidden\" in the HTML file.\n// We can make it visible like this:\ndocument.querySelector(\"#tooltip\").classList.remove(\"hidden\");\n\n// And we can hide it again like this:\ndocument.querySelector(\"#tooltip\").classList.add(\"hidden\");\n```\n### 15.4.2 Inline Styles\nTo continue with the preceding tooltip example, suppose that the document is structured with only a single tooltip element, and we want to dynamically position it before displaying it. In general, we can’t create a different stylesheet class for each possible position of the tooltip, so the classList property won’t help us with positioning.\n\nIn this case, we need to script the style attribute of the tooltip element to set inline styles that are specific to that one element. The DOM defines a style property on all Element objects that correspond to the style attribute. Unlike most such properties, however, the style property is not a string. Instead, it is a CSSStyleDeclaration object: a parsed representation of the CSS styles that appear in textual form in the style attribute. To display and set the position of our hypothetical tooltip with JavaScript, we might use code like this:\n```js\nfunction displayAt(tooltip, x, y) {\n    tooltip.style.display = \"block\";\n    tooltip.style.position = \"absolute\";\n    tooltip.style.left = `${x}px`;\n    tooltip.style.top = `${y}px`;\n}\n```\nNAMING CONVENTIONS: CSS PROPERTIES IN JAVASCRIPT\nMany CSS style properties, such as font-size, contain hyphens in their names. In JavaScript, a hyphen is interpreted as a minus sign and is not allowed in property names or other identifiers. Therefore, the names of the properties of the CSSStyleDeclaration object are slightly different from the names of actual CSS properties. If a CSS property name contains one or more hyphens, the CSSStyleDeclaration property name is formed by removing the hyphens and capitalizing the letter immediately following each hyphen. The CSS property border-left-width is accessed through the JavaScript borderLeftWidth property, for example, and the CSS font-family property is written as fontFamily in JavaScript.\n\nWhen working with the style properties of the CSSStyleDeclaration object, remember that all values must be specified as strings. In a stylesheet or style attribute, you can write:\n```css\ndisplay: block; font-family: sans-serif; background-color: #ffffff;\n```\nTo accomplish the same thing for an element e with JavaScript, you have to quote all of the values:\n```js\ne.style.display = \"block\";\ne.style.fontFamily = \"sans-serif\";\ne.style.backgroundColor = \"#ffffff\";\n```\nNote that the semicolons go outside the strings. These are just normal JavaScript semicolons; the semicolons you use in CSS stylesheets are not required as part of the string values you set with JavaScript.\n\nFurthermore, remember that many CSS properties require units such as “px” for pixels or “pt” for points. Thus, it is not correct to set the marginLeft property like this:\n```js\ne.style.marginLeft = 300;    // Incorrect: this is a number, not a string\ne.style.marginLeft = \"300\";  // Incorrect: the units are missing\n```\nUnits are required when setting style properties in JavaScript, just as they are when setting style properties in stylesheets. The correct way to set the value of the marginLeft property of an element e to 300 pixels is:\n```js\ne.style.marginLeft = \"300px\";\n```\nIf you want to set a CSS property to a computed value, be sure to append the units at the end of the computation:\n```js\ne.style.left = `${x0 + left_border + left_padding}px`;\n```\nRecall that some CSS properties, such as margin, are shortcuts for other properties, such as margin-top, margin-right, margin-bottom, and margin-left. The CSSStyleDeclaration object has properties that correspond to these shortcut properties. For example, you might set the margin property like this:\n```js\ne.style.margin = `${top}px ${right}px ${bottom}px ${left}px`;\n```\nSometimes, you may find it easier to set or query the inline style of an element as a single string value rather than as a CSSStyleDeclaration object. To do that, you can use the Element getAttribute() and setAttribute() methods, or you can use the cssText property of the CSSStyleDeclaration object:\n```js\n// Copy the inline styles of element e to element f:\nf.setAttribute(\"style\", e.getAttribute(\"style\"));\n\n// Or do it like this:\nf.style.cssText = e.style.cssText;\n```\nWhen querying the style property of an element, keep in mind that it represents only the inline styles of an element and that most styles for most elements are specified in stylesheets rather than inline. Furthermore, the values you obtain when querying the style property will use whatever units and whatever shortcut property format is actually used on the HTML attribute, and your code may have to do some sophisticated parsing to interpret them. In general, if you want to query the styles of an element, you probably want the computed style, which is discussed next.\n\n### 15.4.3 Computed Styles\nThe computed style for an element is the set of property values that the browser derives (or computes) from the element’s inline style plus all applicable style rules in all stylesheets: it is the set of properties actually used to display the element. Like inline styles, computed styles are represented with a CSSStyleDeclaration object. Unlike inline styles, however, computed styles are read-only. You can’t set these styles, but the computed CSSStyleDeclaration object for an element lets you determine what style property values the browser used when rendering that element.\n\nObtain the computed style for an element with the getComputedStyle() method of the Window object. The first argument to this method is the element whose computed style is desired. The optional second argument is used to specify a CSS pseudoelement, such as “::before” or “::after”:\n```js\nlet title = document.querySelector(\"#section1title\");\nlet styles = window.getComputedStyle(title);\nlet beforeStyles = window.getComputedStyle(title, \"::before\");\n```\nThe return value of getComputedStyle() is a CSSStyleDeclaration object that represents all the styles that apply to the specified element (or pseudoelement). There are a number of important differences between a CSSStyleDeclaration object that represents inline styles and one that represents computed styles:\n\nComputed style properties are read-only.\n\nComputed style properties are absolute: relative units like percentages and points are converted to absolute values. Any property that specifies a size (such as a margin size or a font size) will have a value measured in pixels. This value will be a string with a “px” suffix, so you’ll still need to parse it, but you won’t have to worry about parsing or converting other units. Properties whose values are colors will be returned in “rgb()” or “rgba()” format.\n\nShortcut properties are not computed—only the fundamental properties that they are based on are. Don’t query the margin property, for example, but use marginLeft, marginTop, and so on. Similarly, don’t query border or even borderWidth. Instead, use borderLeftWidth, borderTopWidth, and so on.\n\nThe cssText property of the computed style is undefined.\n\nA CSSStyleDeclaration object returned by getComputedStyle() generally contains much more information about an element than the CSSStyleDeclaration obtained from the inline style property of that element. But computed styles can be tricky, and querying them does not always provide the information you might expect. Consider the font-family attribute: it accepts a comma-separated list of desired font families for cross-platform portability. When you query the fontFamily property of a computed style, you’re simply getting the value of the most specific font-family style that applies to the element. This may return a value such as “arial,helvetica,sans-serif,” which does not tell you which typeface is actually in use. Similarly, if an element is not absolutely positioned, attempting to query its position and size through the top and left properties of its computed style often returns the value auto. This is a perfectly legal CSS value, but it is probably not what you were looking for.\n\nAlthough CSS can be used to precisely specify the position and size of document elements, querying the computed style of an element is not the preferred way to determine the element’s size and position. See §15.5.2 for a simpler, portable alternative.\n\n### 15.4.4 Scripting Stylesheets\nIn addition to scripting class attributes and inline styles, JavaScript can also manipulate stylesheets themselves. Stylesheets are associated with an HTML document with a `<style>` tag or with a `<link rel=\"stylesheet\">` tag. Both of these are regular HTML tags, so you can give them both id attributes and then look them up with document.querySelector().\n\nThe Element objects for both `<style>` and `<link>` tags have a disabled property that you can use to disable the entire stylesheet. You might use it with code like this:\n```js\n// This function switches between the \"light\" and \"dark\" themes\nfunction toggleTheme() {\n    let lightTheme = document.querySelector(\"#light-theme\");\n    let darkTheme = document.querySelector(\"#dark-theme\");\n    if (darkTheme.disabled) {          // Currently light, switch to dark\n        lightTheme.disabled = true;\n        darkTheme.disabled = false;\n    } else {                           // Currently dark, switch to light\n        lightTheme.disabled = false;\n        darkTheme.disabled = true;\n    }\n}\n```\nAnother simple way to script stylesheets is to insert new ones into the document using DOM manipulation techniques we’ve already seen. For example:\n```js\nfunction setTheme(name) {\n    // Create a new <link rel=\"stylesheet\"> element to load the named stylesheet\n    let link = document.createElement(\"link\");\n    link.id = \"theme\";\n    link.rel = \"stylesheet\";\n    link.href = `themes/${name}.css`;\n\n    // Look for an existing link with id \"theme\"\n    let currentTheme = document.querySelector(\"#theme\");\n    if (currentTheme) {\n        // If there is an existing theme, replace it with the new one.\n        currentTheme.replaceWith(link);\n    } else {\n        // Otherwise, just insert the link to the theme stylesheet.\n        document.head.append(link);\n    }\n}\n```\nLess subtly, you can also just insert a string of HTML containing a `<style>` tag into your document. This is a fun trick, for example:\n```js\ndocument.head.insertAdjacentHTML(\n    \"beforeend\",\n    \"<style>body{transform:rotate(180deg)}</style>\"\n);\n```\nBrowsers define an API that allows JavaScript to look inside stylesheets to query, modify, insert, and delete style rules in that stylesheet. This API is so specialized that it is not documented here. You can read about it on MDN by searching for “CSSStyleSheet” and “CSS Object Model.”\n\n### 15.4.5 CSS Animations and Events\nSuppose you have the following two CSS classes defined in a stylesheet:\n```css\n.transparent { opacity: 0; }\n.fadeable { transition: opacity .5s ease-in }\n```\nIf you apply the first style to an element, it will be fully transparent and therefore invisible. But if you apply the second style that tells the browser that when the opacity of the element changes, that change should be animated over a period of 0.5 seconds, “ease-in” specifies that the opacity change animation should start off slow and then accelerate.\n\nNow suppose that your HTML document contains an element with the “fadeable” class:\n```js\n<div id=\"subscribe\" class=\"fadeable notification\">...</div>\n```\nIn JavaScript, you can add the “transparent” class:\n```js\ndocument.querySelector(\"#subscribe\").classList.add(\"transparent\");\n```\nThis element is configured to animate opacity changes. Adding the “transparent” class changes the opacity and triggers an animate: the browser “fades out” the element so that it becomes fully transparent over the period of half a second.\n\nThis works in reverse as well: if you remove the “transparent” class of a “fadeable” element, that is also an opacity change, and the element fades back in and becomes visible again.\n\nJavaScript does not have to do any work to make these animations happen: they are a pure CSS effect. But JavaScript can be used to trigger them.\n\nJavaScript can also be used to monitor the progress of a CSS transition because the web browser fires events at the start and end of a transition. The “transitionrun” event is dispatched when the transition is first triggered. This may happen before any visual changes begin, when the transition-delay style has been specified. Once the visual changes begin a “transitionstart” event is dispatched, and when the animation is complete, a “transitionend” event is dispatched. The target of all these events is the element being animated, of course. The event object passed to handlers for these events is a TransitionEvent object. It has a propertyName property that specifies the CSS property being animated and an elapsedTime property that for “transitionend” events specifies how many seconds have passed since the “transitionstart” event.\n\nIn addition to transitions, CSS also supports a more complex form of animation known simply as “CSS Animations.” These use CSS properties such as animation-name and animation-duration and a special @keyframes rule to define animation details. Details of how CSS animations work are beyond the scope of this book, but once again, if you define all of the animation properties on a CSS class, then you can use JavaScript to trigger the animation simply by adding the class to the element that is to be animated.\n\nAnd like CSS transitions, CSS animations also trigger events that your JavaScript code can listen form. “animationstart” is dispatched when the animation starts, and “animationend” is dispatched when it is complete. If the animation repeats more than once, then an “animationiteration” event is dispatched after each repetition except the last. The event target is the animated element, and the event object passed to handler functions is an AnimationEvent object. These events include an animationName property that specifies the animation-name property that defines the animation and an elapsedTime property that specifies how many seconds have passed since the animation started.\n\n## 15.5 Document Geometry and Scrolling\nIn this chapter so far, we have thought about documents as abstract trees of elements and text nodes. But when a browser renders a document within a window, it creates a visual representation of the document in which each element has a position and a size. Often, web applications can treat documents as trees of elements and never have to think about how those elements are rendered on screen. Sometimes, however, it is necessary to determine the precise geometry of an element. If, for example, you want to use CSS to dynamically position an element (such as a tooltip) next to some ordinary browser-positioned element, you need to be able to determine the location of that element.\n\nThe following subsections explain how you can go back and forth between the abstract, tree-based model of a document and the geometrical, coordinate-based view of the document as it is laid out in a browser window.\n\n### 15.5.1 Document Coordinates and Viewport Coordinates\nThe position of a document element is measured in CSS pixels, with the x coordinate increasing to the right and the y coordinate increasing as we go down. There are two different points we can use as the coordinate system origin, however: the x and y coordinates of an element can be relative to the top-left corner of the document or relative to the top-left corner of the viewport in which the document is displayed. In top-level windows and tabs, the “viewport” is the portion of the browser that actually displays document content: it excludes browser “chrome” such as menus, toolbars, and tabs. For documents displayed in `<iframe>` tags, it is the iframe element in the DOM that defines the viewport for the nested document. In either case, when we talk about the position of an element, we must be clear whether we are using document coordinates or viewport coordinates. (Note that viewport coordinates are sometimes called “window coordinates.”)\n\nIf the document is smaller than the viewport, or if it has not been scrolled, the upper-left corner of the document is in the upper-left corner of the viewport and the document and viewport coordinate systems are the same. In general, however, to convert between the two coordinate systems, we must add or subtract the scroll offsets. If an element has a y coordinate of 200 pixels in document coordinates, for example, and if the user has scrolled down by 75 pixels, then that element has a y coordinate of 125 pixels in viewport coordinates. Similarly, if an element has an x coordinate of 400 in viewport coordinates after the user has scrolled the viewport 200 pixels horizontally, then the element’s x coordinate in document coordinates is 600.\n\nIf we use the mental model of printed paper documents, it is logical to assume that every element in a document must have a unique position in document coordinates, regardless of how much the user has scrolled the document. That is an appealing property of paper documents, and it applies for simple web documents, but in general, document coordinates don’t really work on the web. The problem is that the CSS overflow property allows elements within a document to contain more content than it can display. Elements can have their own scrollbars and serve as viewports for the content they contain. The fact that the web allows scrolling elements within a scrolling document means that it is simply not possible to describe the position of an element within the document using a single (x,y) point.\n\nBecause document coordinates don’t really work, client-side JavaScript tends to use viewport coordinates. The getBoundingClientRect() and elementFromPoint() methods described next use viewport coordinates, for example, and the clientX and clientY properties of mouse and pointer event objects also use this coordinate system.\n\nWhen you explicitly position an element using CSS position:fixed, the top and left properties are interpreted in viewport coordinates. If you use position:relative, the element is positioned relative to where it would have been if it didn’t have the position property set. If you use position:absolute, then top and left are relative to the document or to the nearest containing positioned element. This means, for example, that an absolutely positioned element inside a relatively positioned element is positioned relative to the container element, not relative to the overall document. It is sometimes very useful to create a relatively positioned container with top and left set to 0 (so the container is laid out normally) in order to establish a new coordinate system origin for the absolutely positioned elements it contains. We might refer to this new coordinate system as “container coordinates” to distinguish it from document coordinates and viewport coordinates.\n\nCSS PIXELS\nIf, like me, you are old enough to remember computer monitors with resolutions of 1024 × 768 and touch-screen phones with resolutions of 320 × 480, then you may still think that the word “pixel” refers to a single “picture element” in hardware. Today’s 4K monitors and “retina” displays have such high resolution that software pixels have been decoupled from hardware pixels. A CSS pixel—and therefore a client-side JavaScript pixel—may in fact consist of multiple device pixels. The devicePixelRatio property of the Window object specifies how many device pixels are used for each software pixel. A “dpr” of 2, for example, means that each software pixel is actually a 2 × 2 grid of hardware pixels. The devicePixelRatio value depends on the physical resolution of your hardware, on settings in your operating system, and on the zoom level in your browser.\n\ndevicePixelRatio does not have to be an integer. If you are using a CSS font size of “12px” and the device pixel ratio is 2.5, then the actual font size, in device pixels, is 30. Because the pixel values we use in CSS no longer correspond directly to individual pixels on the screen, pixel coordinates no longer need to be integers. If the devicePixelRatio is 3, then a coordinate of 3.33 makes perfect sense. And if the ratio is actually 2, then a coordinate of 3.33 will just be rounded up to 3.5.\n\n### 15.5.2 Querying the Geometry of an Element\nYou can determine the size (including CSS border and padding, but not the margin) and position (in viewport coordinates) of an element by calling its getBoundingClientRect() method. It takes no arguments and returns an object with properties left, right, top, bottom, width, and height. The left and top properties give the x and y coordinates of the upper-left corner of the element, and the right and bottom properties give the coordinates of the lower-right corner. The differences between these values are the width and height properties.\n\nBlock elements, such as images, paragraphs, and `<div>` elements are always rectangular when laid out by the browser. Inline elements, such as `<span>`, `<code>`, and `<b>` elements, however, may span multiple lines and may therefore consist of multiple rectangles. Imagine, for example, some text within `<em>` and `</em>` tags that happens to be displayed so that it wraps across two lines. Its rectangles consist of the end of the first line and beginning of the second line. If you call getBoundingClientRect() on this element, the bounding rectangle would include the entire width of both lines. If you want to query the individual rectangles of inline elements, call the getClientRects() method to obtain a read-only, array-like object whose elements are rectangle objects like those returned by getBoundingClientRect().\n\n### 15.5.3 Determining the Element at a Point\nThe getBoundingClientRect() method allows us to determine the current position of an element in a viewport. Sometimes we want to go in the other direction and determine which element is at a given location in the viewport. You can determine this with the elementFromPoint() method of the Document object. Call this method with the x and y coordinates of a point (using viewport coordinates, not document coordinates: the clientX and clientY coordinates of a mouse event work, for example). elementFromPoint() returns an Element object that is at the specified position. The hit detection algorithm for selecting the element is not precisely specified, but the intent of this method is that it returns the innermost (most deeply nested) and uppermost (highest CSS z-index attribute) element at that point.\n\n### 15.5.4 Scrolling\nThe scrollTo() method of the Window object takes the x and y coordinates of a point (in document coordinates) and sets these as the scrollbar offsets. That is, it scrolls the window so that the specified point is in the upper-left corner of the viewport. If you specify a point that is too close to the bottom or too close to the right edge of the document, the browser will move it as close as possible to the upper-left corner but won’t be able to get it all the way there. The following code scrolls the browser so that the bottom-most page of the document is visible:\n```js\n// Get the heights of the document and viewport.\nlet documentHeight = document.documentElement.offsetHeight;\nlet viewportHeight = window.innerHeight;\n// And scroll so the last \"page\" shows in the viewport\nwindow.scrollTo(0, documentHeight - viewportHeight);\n```\nThe scrollBy() method of the Window is similar to scrollTo(), but its arguments are relative and are added to the current scroll position:\n```js\n// Scroll 50 pixels down every 500 ms. Note there is no way to turn this off!\nsetInterval(() => { scrollBy(0,50)}, 500);\n```\nIf you want to scroll smoothly with scrollTo() or scrollBy(), pass a single object argument instead of two numbers, like this:\n```js\nwindow.scrollTo({\n  left: 0,\n  top: documentHeight - viewportHeight,\n  behavior: \"smooth\"\n});\n```\nOften, instead of scrolling to a numeric location in a document, we just want to scroll so that a certain element in the document is visible. You can do this with the scrollIntoView() method on the desired HTML element. This method ensures that the element on which it is invoked is visible in the viewport. By default, it tries to put the top edge of the element at or near the top of the viewport. If false is passed as the only argument, it tries to put the bottom edge of the element at the bottom of the viewport. The browser will also scroll the viewport horizontally as needed to make the element visible.\n\nYou can also pass an object to scrollIntoView(), setting the behavior:\"smooth\" property for smooth scrolling. You can set the block property to specify where the element should be positioned vertically and the inline property to specify how it should be positioned horizontally if horizontal scrolling is needed. Legal values for both of these properties are start, end, nearest, and center.\n\n### 15.5.5 Viewport Size, Content Size, and Scroll Position\nAs we’ve discussed, browser windows and other HTML elements can display scrolling content. When this is the case, we sometimes need to know the size of the viewport, the size of the content, and the scroll offsets of the content within the viewport. This section covers these details.\n\nFor browser windows, the viewport size is given by the window.innerWidth and window.innerHeight properties. (Web pages optimized for mobile devices often use a `<meta name=\"viewport\">` tag in their `<head>` to set the desired viewport width for the page.) The total size of the document is the same as the size of the `<html>` element, document.documentElement. You can call getBoundingClientRect() on document.documentElement to get the width and height of the document, or you can use the offsetWidth and offsetHeight properties of document.documentElement. The scroll offsets of the document within its viewport are available as window.scrollX and window.scrollY. These are read-only properties, so you can’t set them to scroll the document: use window.scrollTo() instead.\n\nThings are a little more complicated for elements. Every Element object defines the following three groups of properties:\n```\noffsetWidth     clientWidth      scrollWidth\noffsetHeight    clientHeight     scrollHeight\noffsetLeft      clientLeft       scrollLeft\noffsetTop       clientTop        scrollTop\noffsetParent\n```\nThe offsetWidth and offsetHeight properties of an element return its on-screen size in CSS pixels. The returned sizes include the element border and padding but not margins. The offsetLeft and offsetTop properties return the x and y coordinates of the element. For many elements, these values are document coordinates. But for descendants of positioned elements and for some other elements, such as table cells, these properties return coordinates that are relative to an ancestor element rather than the document itself. The offsetParent property specifies which element the properties are relative to. These offset properties are all read-only.\n\nclientWidth and clientHeight are like offsetWidth and offsetHeight except that they do not include the border size—only the content area and its padding. The clientLeft and clientTop properties are not very useful: they return the horizontal and vertical distance between the outside of an element’s padding and the outside of its border. Usually, these values are just the width of the left and top borders. These client properties are all read-only. For inline elements like `<i>`, `<code>`, and `<span>`, they all return 0.\n\nscrollWidth and scrollHeight return the size of an element’s content area plus its padding plus any overflowing content. When the content fits within the content area without overflow, these properties are the same as clientWidth and clientHeight. But when there is overflow, they include the overflowing content and return values larger than clientWidth and clientHeight. scrollLeft and scrollTop give the scroll offset of the element content within the element’s viewport. Unlike all the other properties described here, scrollLeft and scrollTop are writable properties, and you can set them to scroll the content within an element. (In most browsers, Element objects also have scrollTo() and scrollBy() methods like the Window object does, but these are not yet universally supported.)\n\n## 15.6 Web Components\nHTML is a language for document markup and defines a rich set of tags for that purpose. Over the last three decades, it has become a language that is used to describe the user interfaces of web applications, but basic HTML tags such as `<input>` and `<button>` are inadequate for modern UI designs. Web developers are able to make it work, but only by using CSS and JavaScript to augment the appearance and behavior of basic HTML tags. Consider a typical user interface component, such as the search box shown in Figure 15-3.\n\n<Figures figure=\"15-3\">A search box user interface component</Figures>\n\nThe HTML `<input>` element can be used to accept a single line of input from the user, but it doesn’t have any way to display icons like the magnifying glass on the left and the cancel X on the right. In order to implement a modern user interface element like this for the web, we need to use at least four HTML elements: an `<input>` element to accept and display the user’s input, two `<img>` elements (or in this case, two `<span>` elements displaying Unicode glyphs), and a container `<div>` element to hold those three children. Furthermore, we have to use CSS to hide the default border of the `<input>` element and define a border for the container. And we need to use JavaScript to make all the HTML elements work together. When the user clicks on the X icon, we need an event handler to clear the input from the `<input>` element, for example.\n\nThat is a lot of work to do every time you want to display a search box in a web application, and most web applications today are not written using “raw” HTML. Instead, many web developers use frameworks like React and Angular that support the creation of reusable user interface components like the search box shown here. Web components is a browser-native alternative to those frameworks based on three relatively recent additions to web standards that allow JavaScript to extend HTML with new tags that work as self-contained, reusable UI components.\n\nThe subsections that follow explain how to use web components defined by other developers in your own web pages, then explain each of the three technologies that web components are based on, and finally tie all three together in an example that implements the search box element pictured in Figure 15-3.\n\n### 15.6.1 Using Web Components\nWeb components are defined in JavaScript, so in order to use a web component in your HTML file, you need to include the JavaScript file that defines the component. Because web components are a relatively new technology, they are often written as JavaScript modules, so you might include one in your HTML like this:\n```js\n<script type=\"module\" src=\"components/search-box.js\">\n```\nWeb components define their own HTML tag names, with the important restriction that those tag names must include a hyphen. (This means that future versions of HTML can introduce new tags without hyphens, and there is no chance that the tags will conflict with anyone’s web component.) To use a web component, just use its tag in your HTML file:\n```js\n<search-box placeholder=\"Search...\"></search-box>\n```\nWeb components can have attributes just like regular HTML tags can; the documentation for the component you are using should tell you which attributes are supported. Web components cannot be defined with self-closing tags. You cannot write `<search-box/>`, for example. Your HTML file must include both the opening tag and the closing tag.\n\nLike regular HTML elements, some web components are written to expect children and others are written in such a way that they do not expect (and will not display) children. Some web components are written so that they can optionally accept specially labeled children that will appear in named “slots.” The `<search-box>` component pictured in Figure 15-3 and implemented in Example 15-3 uses “slots” for the two icons it displays. If you want to to use a `<search-box>` with different icons, you can use HTML like this:\n```js\n<search-box>\n  <img src=\"images/search-icon.png\" slot=\"left\"/>\n  <img src=\"images/cancel-icon.png\" slot=\"right\"/>\n</search-box>\n```\nThe slot attribute is an extension to HTML that it is used to specify which children should go where. The slot names—“left” and “right” in this example—are defined by the web component. If the component you are using supports slots, that fact should be included in its documentation.\n\nI previously noted that web components are often implemented as JavaScript modules and can be loaded into HTML files with a `<script type=\"module\">` tag. You may remember from the beginning of this chapter that modules are loaded after document content is parsed, as if they had a deferred tag. So this means that a web browser will typically parse and render tags like `<search-box>` before it has run the code that will tell it what a `<search-box>` is. This is normal when using web components. HTML parsers in web browsers are flexible and very forgiving about input that they do not understand. When they encounter a web component tag before that component has been defined, they add a generic HTMLElement to the DOM tree even though they do not know what to do with it. Later, when the custom element is defined, the generic element is “upgraded” so that it looks and behaves as desired.\n\nIf a web component has children, then those children will probably be displayed incorrectly before the component is defined. You can use this CSS to keep web components hidden until they are defined:\n```js\n/*\n * Make the <search-box> component invisible before it is defined.\n * And try to duplicate its eventual layout and size so that nearby\n * content does not move when it becomes defined.\n */\nsearch-box:not(:defined) {\n    opacity:0;\n    display: inline-block;\n    width: 300px;\n    height: 50px;\n}\n```\nLike regular HTML elements, web components can be used in JavaScript. If you include a `<search-box>` tag in your web page, then you can obtain a reference to it with querySelector() and an appropriate CSS selector, just as you would for any other HTML tag. Generally, it only makes sense to do this after the module that defines the component has run, so be careful when querying web components that you do not do so too early. Web component implementations typically (but this is not a requirement) define a JavaScript property for each HTML attribute they support. And, like HTML elements, they may also define useful methods. Once again, the documentation for the web component you are using should specify what properties and methods are available to your JavaScript code.\n\nNow that you know how to use web components, the next three sections cover the three web browser features that allow us to implement them.\n\nDOCUMENTFRAGMENT NODES\nBefore we can cover web component APIs, we need to return briefly to the DOM API to explain what a DocumentFragment is. The DOM API organizes a document into a tree of Node objects, where a Node can be a Document, an Element, a Text node, or even a Comment. None of these node types allows you to represent a fragment of a document that consists of a set of sibling nodes without their parent. This is where DocumentFragment comes in: it is another type of Node that serves as a temporary parent when you want to manipulate a group of sibling nodes as a single unit. You can create a DocumentFragment node with document.createDocumentFragment(). Once you have a DocumentFragment, you can use it like an Element and append() content to it. A DocumentFragment is different from an Element because it does not have a parent. But more importantly, when you insert a DocumentFragment node into the document, the DocumentFragment itself is not inserted. Instead, all of its children are inserted.\n\n### 15.6.2 HTML Templates\nThe HTML `<template>` tag is only loosely related to web components, but it does enable a useful optimization for components that appear frequently in web pages. `<template>` tags and their children are never rendered by a web browser and are only useful on web pages that use JavaScript. The idea behind this tag is that when a web page contains multiple repetitions of the same basic HTML structure (such as rows in a table or the internal implementation of a web component), then we can use a `<template>` to define that element structure once, then use JavaScript to duplicate the structure as many times as needed.\n\nIn JavaScript, a `<template>` tag is represented by an HTMLTemplateElement object. This object defines a single content property, and the value of this property is a DocumentFragment of all the child nodes of the `<template>`. You can clone this DocumentFragment and then insert the cloned copy into your document as needed. The fragment itself will not be inserted, but its children will be. Suppose you’re working with a document that includes a `<table>` and `<template id=\"row\">` tag and that the template defines the structure of rows for that table. You might use the template like this:\n```js\nlet tableBody = document.querySelector(\"tbody\");\nlet template = document.querySelector(\"#row\");\nlet clone = template.content.cloneNode(true);  // deep clone\n// ...Use the DOM to insert content into the <td> elements of the clone...\n// Now add the cloned and initialized row into the table\ntableBody.append(clone);\n```\nTemplate elements do not have to appear literally in an HTML document in order to be useful. You can create a template in your JavaScript code, create its children with innerHTML, and then make as many clones as needed without the parsing overhead of innerHTML. This is how HTML templates are typically used in web components, and Example 15-3 demonstrates this technique.\n\n### 15.6.3 Custom Elements\nThe second web browser feature that enables web components is “custom elements”: the ability to associate a JavaScript class with an HTML tag name so that any such tags in the document are automatically turned into instances of the class in the DOM tree. The customElements.define() method takes a web component tag name as its first argument (remember that the tag name must include a hyphen) and a subclass of HTMLElement as its second argument. Any existing elements in the document with that tag name are “upgraded” to newly created instances of the class. And if the browser parses any HTML in the future, it will automatically create an instance of the class for each of the tags it encounters.\n\nThe class passed to customElements.define() should extend HTMLElement and not a more specific type like HTMLButtonElement.4 Recall from Chapter 9 that when a JavaScript class extends another class, the constructor function must call super() before it uses the this keyword, so if the custom element class has a constructor, it should call super() (with no arguments) before doing anything else.\n\nThe browser will automatically invoke certain “lifecycle methods” of a custom element class. The connectedCallback() method is invoked when an instance of the custom element is inserted into the document, and many elements use this method to perform initialization. There is also a disconnectedCallback() method invoked when (and if) the element is removed from the document, though this is less often used.\n\nIf a custom element class defines a static observedAttributes property whose value is an array of attribute names, and if any of the named attributes are set (or changed) on an instance of the custom element, the browser will invoke the attributeChangedCallback() method, passing the attribute name, its old value, and its new value. This callback can take whatever steps are necessary to update the component based on its attribute values.\n\nCustom element classes can also define whatever other properties and methods they want to. Commonly, they will define getter and setter methods that make the element’s attributes available as JavaScript properties.\n\nAs an example of a custom element, suppose we want to be able to display circles within paragraphs of regular text. We’d like to be able to write HTML like this in order to render mathematical story problems like the one shown in Figure 15-4:\n```js\n<p>\n  The document has one marble: <inline-circle></inline-circle>.\n  The HTML parser instantiates two more marbles:\n  <inline-circle diameter=\"1.2em\" color=\"blue\"></inline-circle>\n  <inline-circle diameter=\".6em\" color=\"gold\"></inline-circle>.\n  How many marbles does the document contain now?\n</p>\n```\n\n<Figures figure=\"15-4\">An inline circle custom element</Figures>\n\nWe can implement this `<inline-circle>` custom element with the code shown in Example 15-2:\n\nExample 15-2. The `<inline-circle>` custom element\n```js\ncustomElements.define(\"inline-circle\", class InlineCircle extends HTMLElement {\n    // The browser calls this method when an <inline-circle> element\n    // is inserted into the document. There is also a disconnectedCallback()\n    // that we don't need in this example.\n    connectedCallback() {\n        // Set the styles needed to create circles\n        this.style.display = \"inline-block\";\n        this.style.borderRadius = \"50%\";\n        this.style.border = \"solid black 1px\";\n        this.style.transform = \"translateY(10%)\";\n\n        // If there is not already a size defined, set a default size\n        // that is based on the current font size.\n        if (!this.style.width) {\n            this.style.width = \"0.8em\";\n            this.style.height = \"0.8em\";\n        }\n    }\n\n    // The static observedAttributes property specifies which attributes\n    // we want to be notified about changes to. (We use a getter here since\n    // we can only use \"static\" with methods.)\n    static get observedAttributes() { return [\"diameter\", \"color\"]; }\n\n    // This callback is invoked when one of the attributes listed above\n    // changes, either when the custom element is first parsed, or later.\n    attributeChangedCallback(name, oldValue, newValue) {\n        switch(name) {\n        case \"diameter\":\n            // If the diameter attribute changes, update the size styles\n            this.style.width = newValue;\n            this.style.height = newValue;\n            break;\n        case \"color\":\n            // If the color attribute changes, update the color styles\n            this.style.backgroundColor = newValue;\n            break;\n        }\n    }\n\n    // Define JavaScript properties that correspond to the element's\n    // attributes. These getters and setters just get and set the underlying\n    // attributes. If a JavaScript property is set, that sets the attribute\n    // which triggers a call to attributeChangedCallback() which updates\n    // the element styles.\n    get diameter() { return this.getAttribute(\"diameter\"); }\n    set diameter(diameter) { this.setAttribute(\"diameter\", diameter); }\n    get color() { return this.getAttribute(\"color\"); }\n    set color(color) { this.setAttribute(\"color\", color); }\n});\n```\n### 15.6.4 Shadow DOM\nThe custom element demonstrated in Example 15-2 is not well encapsulated. When you set its diameter or color attributes, it responds by altering its own style attribute, which is not behavior we would ever expect from a real HTML element. To turn a custom element into a true web component, it should use the powerful encapsulation mechanism known as shadow DOM.\n\nShadow DOM allows a “shadow root” to be attached to a custom element (and also to a `<div>`, `<span>`, `<body>`, `<article>`, `<main>`, `<nav>`, `<header>`, `<footer>`, `<section>`, `<p>`, `<blockquote>`, `<aside>`, or `<h1>` through `<h6>` element) known as a “shadow host.” Shadow host elements, like all HTML elements, are already the root of a normal DOM tree of descendant elements and text nodes. A shadow root is the root of another, more private, tree of descendant elements that sprouts from the shadow host and can be thought of as a distinct minidocument.\n\nThe word “shadow” in “shadow DOM” refers to the fact that elements that descend from a shadow root are “hiding in the shadows”: they are not part of the normal DOM tree, do not appear in the children array of their host element, and are not visited by normal DOM traversal methods such as querySelector(). For contrast, the normal, regular DOM children of a shadow host are sometimes referred to as the “light DOM.”\n\nTo understand the purpose of the shadow DOM, picture the HTML `<audio>` and `<video>` elements: they display a nontrivial user interface for controlling media playback, but the play and pause buttons and other UI elements are not part of the DOM tree and cannot be manipulated by JavaScript. Given that web browsers are designed to display HTML, it is only natural that browser vendors would want to display internal UIs like these using HTML. In fact, most browsers have been doing something like that for a long time, and the shadow DOM makes it a standard part of the web platform.\n\nSHADOW DOM ENCAPSULATION\nThe key feature of shadow DOM is the encapsulation it provides. The descendants of a shadow root are hidden from—and independent from—the regular DOM tree, almost as if they were in an independent document. There are three very important kinds of encapsulation provided by the shadow DOM:\n\n- As already mentioned, elements in the shadow DOM are hidden from regular DOM methods like querySelectorAll(). When a shadow root is created and attached to its shadow host, it can be created in “open” or “closed” mode. A closed shadow root is completely sealed away and inaccessible. More commonly, though, shadow roots are created in “open” mode, which means that the shadow host has a shadowRoot property that JavaScript can use to gain access to the elements of the shadow root, if it has some reason to do so.\n- Styles defined beneath a shadow root are private to that tree and will never affect the light DOM elements on the outside. (A shadow root can define default styles for its host element, but these will be overridden by light DOM styles.) Similarly, the light DOM styles that apply to the shadow host element have no effect on the descendants of the shadow root. Elements in the shadow DOM will inherit things like font size and background color from the light DOM, and styles in the shadow DOM can choose to use CSS variables defined in the light DOM. For the most part, however, the styles of the light DOM and the styles of the shadow DOM are completely independent: the author of a web component and the user of a web component do not have to worry about collisions or conflicts between their stylesheets. Being able to “scope” CSS in this way is perhaps the most important feature of the shadow DOM.\n- Some events (like “load”) that occur within the shadow DOM are confined to the shadow DOM. Others, including focus, mouse, and keyboard events bubble up and out. When an event that originates in the shadow DOM crosses the boundary and begins to propagate in the light DOM, its target property is changed to the shadow host element, so it appears to have originated directly on that element.\n\nSHADOW DOM SLOTS AND LIGHT DOM CHILDREN\nAn HTML element that is a shadow host has two trees of descendants. One is the children[] array—the regular light DOM descendants of the host element—and the other is the shadow root and all of its descendants, and you may be wondering how two distinct content trees can be displayed within the same host element. Here’s how it works:\n\n- The descendants of the shadow root are always displayed within the shadow host.\n- If those descendants include a `<slot>` element, then the regular light DOM children of the host element are displayed as if they were children of that `<slot>`, replacing any shadow DOM content in the slot. If the shadow DOM does not include a `<slot>`, then any light DOM content of the host is never displayed. If the shadow DOM has a `<slot>`, but the shadow host has no light DOM children, then the shadow DOM content of the slot is displayed as a default.\n- When light DOM content is displayed within a shadow DOM slot, we say that those elements have been “distributed,” but it is important to understand that the elements do not actually become part of the shadow DOM. They can still be queried with querySelector(), and they still appear in the light DOM as children or descendants of the host element.\n- If the shadow DOM defines more than one `<slot>` and names those slots with a name attribute, then children of the shadow host can specify which slot they would like to appear in by specifying a slot=\"slotname\" attribute. We saw an example of this usage in §15.6.1 when we demonstrated how to customize the icons displayed by the `<search-box>` component.\n\nSHADOW DOM API\nFor all of its power, the Shadow DOM doesn’t have much of a JavaScript API. To turn a light DOM element into a shadow host, just call its attachShadow() method, passing {mode:\"open\"} as the only argument. This method returns a shadow root object and also sets that object as the value of the host’s shadowRoot property. The shadow root object is a DocumentFragment, and you can use DOM methods to add content to it or just set its innerHTML property to a string of HTML.\n\nIf your web component needs to know when the light DOM content of a shadow DOM `<slot>` has changed, it can register a listener for “slotchanged” events directly on the `<slot>` element.\n\n### 15.6.5 Example: a `<search-box>` Web Component\nFigure 15-3 illustrated a `<search-box>` web component. Example 15-3 demonstrates the three enabling technologies that define web components: it implements the `<search-box>` component as a custom element that uses a `<template>` tag for efficiency and a shadow root for encapsulation.\n\nThis example shows how to use the low-level web component APIs directly. In practice, many web components developed today create them using higher-level libraries such as “lit-element.” One of the reasons to use a library is that creating reusable and customizable components is actually quite hard to do well, and there are many details to get right. Example 15-3 demonstrates web components and does some basic keyboard focus handling, but otherwise ignores accessibility and makes no attempt to use proper ARIA attributes to make the component work with screen readers and other assistive technology.\n\nExample 15-3. Implementing a web component\n```js\n/**\n * This class defines a custom HTML <search-box> element that displays an\n * <input> text input field plus two icons or emoji. By default, it displays a\n * magnifying glass emoji (indicating search) to the left of the text field\n * and an X emoji (indicating cancel) to the right of the text field. It\n * hides the border on the input field and displays a border around itself,\n * creating the appearance that the two emoji are inside the input\n * field. Similarly, when the internal input field is focused, the focus ring\n * is displayed around the <search-box>.\n *\n * You can override the default icons by including <span> or <img> children\n * of <search-box> with slot=\"left\" and slot=\"right\" attributes.\n *\n * <search-box> supports the normal HTML disabled and hidden attributes and\n * also size and placeholder attributes, which have the same meaning for this\n * element as they do for the <input> element.\n *\n * Input events from the internal <input> element bubble up and appear with\n * their target field set to the <search-box> element.\n *\n * The element fires a \"search\" event with the detail property set to the\n * current input string when the user clicks on the left emoji (the magnifying\n * glass). The \"search\" event is also dispatched when the internal text field\n * generates a \"change\" event (when the text has changed and the user types\n * Return or Tab).\n *\n * The element fires a \"clear\" event when the user clicks on the right emoji\n * (the X). If no handler calls preventDefault() on the event then the element\n * clears the user's input once event dispatch is complete.\n *\n * Note that there are no onsearch and onclear properties or attributes:\n * handlers for the \"search\" and \"clear\" events can only be registered with\n * addEventListener().\n */\nclass SearchBox extends HTMLElement {\n    constructor() {\n        super(); // Invoke the superclass constructor; must be first.\n\n        // Create a shadow DOM tree and attach it to this element, setting\n        // the value of this.shadowRoot.\n        this.attachShadow({mode: \"open\"});\n\n        // Clone the template that defines the descendants and stylesheet for\n        // this custom component, and append that content to the shadow root.\n        this.shadowRoot.append(SearchBox.template.content.cloneNode(true));\n\n        // Get references to the important elements in the shadow DOM\n        this.input = this.shadowRoot.querySelector(\"#input\");\n        let leftSlot = this.shadowRoot.querySelector('slot[name=\"left\"]');\n        let rightSlot = this.shadowRoot.querySelector('slot[name=\"right\"]');\n\n        // When the internal input field gets or loses focus, set or remove\n        // the \"focused\" attribute which will cause our internal stylesheet\n        // to display or hide a fake focus ring on the entire component. Note\n        // that the \"blur\" and \"focus\" events bubble and appear to originate\n        // from the <search-box>.\n        this.input.onfocus = () => { this.setAttribute(\"focused\", \"\"); };\n        this.input.onblur = () => { this.removeAttribute(\"focused\");};\n\n        // If the user clicks on the magnifying glass, trigger a \"search\"\n        // event.  Also trigger it if the input field fires a \"change\"\n        // event. (The \"change\" event does not bubble out of the Shadow DOM.)\n        leftSlot.onclick = this.input.onchange = (event) => {\n            event.stopPropagation();    // Prevent click events from bubbling\n            if (this.disabled) return;  // Do nothing when disabled\n            this.dispatchEvent(new CustomEvent(\"search\", {\n                detail: this.input.value\n            }));\n        };\n\n        // If the user clicks on the X, trigger a \"clear\" event.\n        // If preventDefault() is not called on the event, clear the input.\n        rightSlot.onclick = (event) => {\n            event.stopPropagation();    // Don't let the click bubble up\n            if (this.disabled) return;  // Don't do anything if disabled\n            let e = new CustomEvent(\"clear\", { cancelable: true });\n            this.dispatchEvent(e);\n            if (!e.defaultPrevented) {  // If the event was not \"cancelled\"\n                this.input.value = \"\";  // then clear the input field\n            }\n        };\n    }\n\n    // When some of our attributes are set or changed, we need to set the\n    // corresponding value on the internal <input> element. This life cycle\n    // method, together with the static observedAttributes property below,\n    // takes care of that.\n    attributeChangedCallback(name, oldValue, newValue) {\n        if (name === \"disabled\") {\n            this.input.disabled = newValue !== null;\n        } else if (name === \"placeholder\") {\n            this.input.placeholder = newValue;\n        } else if (name === \"size\") {\n            this.input.size = newValue;\n        } else if (name === \"value\") {\n            this.input.value = newValue;\n        }\n    }\n\n    // Finally, we define property getters and setters for properties that\n    // correspond to the HTML attributes we support. The getters simply return\n    // the value (or the presence) of the attribute. And the setters just set\n    // the value (or the presence) of the attribute. When a setter method\n    // changes an attribute, the browser will automatically invoke the\n    // attributeChangedCallback above.\n\n    get placeholder() { return this.getAttribute(\"placeholder\"); }\n    get size() { return this.getAttribute(\"size\"); }\n    get value() { return this.getAttribute(\"value\"); }\n    get disabled() { return this.hasAttribute(\"disabled\"); }\n    get hidden() { return this.hasAttribute(\"hidden\"); }\n\n    set placeholder(value) { this.setAttribute(\"placeholder\", value); }\n    set size(value) { this.setAttribute(\"size\", value); }\n    set value(text) { this.setAttribute(\"value\", text); }\n    set disabled(value) {\n        if (value) this.setAttribute(\"disabled\", \"\");\n        else this.removeAttribute(\"disabled\");\n    }\n    set hidden(value) {\n        if (value) this.setAttribute(\"hidden\", \"\");\n        else this.removeAttribute(\"hidden\");\n    }\n}\n\n// This static field is required for the attributeChangedCallback method.\n// Only attributes named in this array will trigger calls to that method.\nSearchBox.observedAttributes = [\"disabled\", \"placeholder\", \"size\", \"value\"];\n\n// Create a <template> element to hold the stylesheet and the tree of\n// elements that we'll use for each instance of the SearchBox element.\nSearchBox.template = document.createElement(\"template\");\n\n// We initialize the template by parsing this string of HTML. Note, however,\n// that when we instantiate a SearchBox, we are able to just clone the nodes\n// in the template and do have to parse the HTML again.\nSearchBox.template.innerHTML = `\n<style>\n/*\n * The :host selector refers to the <search-box> element in the light\n * DOM. These styles are defaults and can be overridden by the user of the\n * <search-box> with styles in the light DOM.\n */\n:host {\n  display: inline-block;   /* The default is inline display */\n  border: solid black 1px; /* A rounded border around the <input> and <slots> */\n  border-radius: 5px;\n  padding: 4px 6px;        /* And some space inside the border */\n}\n:host([hidden]) {          /* Note the parentheses: when host has hidden... */\n  display:none;            /* ...attribute set don't display it */\n}\n:host([disabled]) {        /* When host has the disabled attribute... */\n  opacity: 0.5;            /* ...gray it out */\n}\n:host([focused]) {         /* When host has the focused attribute... */\n  box-shadow: 0 0 2px 2px #6AE;  /* display this fake focus ring. */\n}\n\n/* The rest of the stylesheet only applies to elements in the Shadow DOM. */\ninput {\n  border-width: 0;         /* Hide the border of the internal input field. */\n  outline: none;           /* Hide the focus ring, too. */\n  font: inherit;           /* <input> elements don't inherit font by default */\n  background: inherit;     /* Same for background color. */\n}\nslot {\n  cursor: default;         /* An arrow pointer cursor over the buttons */\n  user-select: none;       /* Don't let the user select the emoji text */\n}\n</style>\n<div>\n  <slot name=\"left\">\\u{1f50d}</slot>  <!-- U+1F50D is a magnifying glass -->\n  <input type=\"text\" id=\"input\" />    <!-- The actual input element -->\n  <slot name=\"right\">\\u{2573}</slot>  <!-- U+2573 is an X -->\n</div>\n`;\n\n// Finally, we call customElement.define() to register the SearchBox element\n// as the implementation of the <search-box> tag. Custom elements are required\n// to have a tag name that contains a hyphen.\ncustomElements.define(\"search-box\", SearchBox);\n```\n## 15.7 SVG: Scalable Vector Graphics\nSVG (scalable vector graphics) is an image format. The word “vector” in its name indicates that it is fundamentally different from raster image formats, such as GIF, JPEG, and PNG, that specify a matrix of pixel values. Instead, an SVG “image” is a precise, resolution-independent (hence “scalable”) description of the steps necessary to draw the desired graphic. SVG images are described by text files using the XML markup language, which is quite similar to HTML.\n\nThere are three ways you can use SVG in web browsers:\n\nYou can use .svg image files with regular HTML `<img>` tags, just as you would use a .png or .jpeg image.\n\nBecause the XML-based SVG format is so similar to HTML, you can actually embed SVG tags directly into your HTML documents. If you do this, the browser’s HTML parser allows you to omit XML namespaces and treat SVG tags as if they were HTML tags.\n\nYou can use the DOM API to dynamically create SVG elements to generate images on demand.\n\nThe subsections that follow demonstrate the second and third uses of SVG. Note, however, that SVG has a large and moderately complex grammar. In addition to simple shape-drawing primitives, it includes support for arbitrary curves, text, and animation. SVG graphics can even incorporate JavaScript scripts and CSS stylesheets to add behavior and presentation information. A full description of SVG is well beyond the scope of this book. The goal of this section is just to show you how you can use SVG in your HTML documents and script it with JavaScript.\n\n### 15.7.1 SVG in HTML\nSVG images can, of course, be displayed using HTML `<img>` tags. But you can also embed SVG directly in HTML. And if you do this, you can even use CSS stylesheets to specify things like fonts, colors, and line widths. Here, for example, is an HTML file that uses SVG to display an analog clock face:\n```html\n<html>\n<head>\n<title>Analog Clock</title>\n<style>\n/* These CSS styles all apply to the SVG elements defined below */\n#clock {                             /* Styles for everything in the clock:*/\n   stroke: black;                    /* black lines */\n   stroke-linecap: round;            /* with rounded ends */\n   fill: #ffe;                       /* on an off-white background */\n}\n#clock .face { stroke-width: 3; }    /* Clock face outline */\n#clock .ticks { stroke-width: 2; }   /* Lines that mark each hour */\n#clock .hands { stroke-width: 3; }   /* How to draw the clock hands */\n#clock .numbers {                    /* How to draw the numbers */\n    font-family: sans-serif; font-size: 10; font-weight: bold;\n    text-anchor: middle; stroke: none; fill: black;\n}\n</style>\n</head>\n<body>\n  <svg id=\"clock\" viewBox=\"0 0 100 100\" width=\"250\" height=\"250\">\n    <!-- The width and height attributes are the screen size of the graphic -->\n    <!-- The viewBox attribute gives the internal coordinate system -->\n    <circle class=\"face\" cx=\"50\" cy=\"50\" r=\"45\"/>  <!-- the clock face -->\n    <g class=\"ticks\">   <!-- tick marks for each of the 12 hours -->\n      <line x1='50' y1='5.000' x2='50.00' y2='10.00'/>\n      <line x1='72.50' y1='11.03' x2='70.00' y2='15.36'/>\n      <line x1='88.97' y1='27.50' x2='84.64' y2='30.00'/>\n      <line x1='95.00' y1='50.00' x2='90.00' y2='50.00'/>\n      <line x1='88.97' y1='72.50' x2='84.64' y2='70.00'/>\n      <line x1='72.50' y1='88.97' x2='70.00' y2='84.64'/>\n      <line x1='50.00' y1='95.00' x2='50.00' y2='90.00'/>\n      <line x1='27.50' y1='88.97' x2='30.00' y2='84.64'/>\n      <line x1='11.03' y1='72.50' x2='15.36' y2='70.00'/>\n      <line x1='5.000' y1='50.00' x2='10.00' y2='50.00'/>\n      <line x1='11.03' y1='27.50' x2='15.36' y2='30.00'/>\n      <line x1='27.50' y1='11.03' x2='30.00' y2='15.36'/>\n    </g>\n    <g class=\"numbers\"> <!-- Number the cardinal directions-->\n      <text x=\"50\" y=\"18\">12</text><text x=\"85\" y=\"53\">3</text>\n      <text x=\"50\" y=\"88\">6</text><text x=\"15\" y=\"53\">9</text>\n    </g>\n    <g class=\"hands\">   <!-- Draw hands pointing straight up. -->\n      <line class=\"hourhand\" x1=\"50\" y1=\"50\" x2=\"50\" y2=\"25\"/>\n      <line class=\"minutehand\" x1=\"50\" y1=\"50\" x2=\"50\" y2=\"20\"/>\n    </g>\n  </svg>\n  <script src=\"clock.js\"></script>\n</body>\n</html>\n```\nYou’ll notice that the descendants of the `<svg>` tag are not normal HTML tags. `<circle>`, `<line>`, and `<text>` tags have obvious purposes, though, and it should be clear how this SVG graphic works. There are many other SVG tags, however, and you’ll need to consult an SVG reference to learn more. You may also notice that the stylesheet is odd. Styles like fill, stroke-width, and text-anchor are not normal CSS style properties. In this case, CSS is essentially being used to set attributes of SVG tags that appear in the document. Note also that the CSS font shorthand property does not work for SVG tags, and you must explicitly set font-family, font-size, and font-weight as separate style properties.\n\n### 15.7.2 Scripting SVG\nOne reason to embed SVG directly into your HTML files (instead of just using static `<img> `tags) is that if you do this, then you can use the DOM API to manipulate the SVG image. Suppose you use SVG to display icons in your web application. You could embed SVG within a `<template>` tag (§15.6.2) and then clone the template content whenever you need to insert a copy of that icon into your UI. And if you want the icon to respond to user activity—by changing color when the user hovers the pointer over it, for example—you can often achieve this with CSS.\n\nIt is also possible to dynamically manipulate SVG graphics that are directly embedded in HTML. The clock face example in the previous section displays a static clock with hour and minute hands facing straight up displaying the time noon or midnight. But you may have noticed that the HTML file includes a `<script>` tag. That script runs a function periodically to check the time and transform the hour and minute hands by rotating them the appropriate number of degrees so that the clock actually displays the current time, as shown in Figure 15-5.\n\n<Figures figure=\"15-5\">A scripted SVG analog clock</Figures>\n\nThe code to manipulate the clock is straightforward. It determines the proper angle of the hour and minute hands based on the current time, then uses querySelector() to look up the SVG elements that display those hands, then sets a transform attribute on them to rotate them around the center of the clock face. The function uses setTimeout() to ensure that it runs once a minute:\n```js\n(function updateClock() { // Update the SVG clock graphic to show current time\n    let now = new Date();                       // Current time\n    let sec = now.getSeconds();                 // Seconds\n    let min = now.getMinutes() + sec/60;        // Fractional minutes\n    let hour = (now.getHours() % 12) + min/60;  // Fractional hours\n    let minangle = min * 6;                     // 6 degrees per minute\n    let hourangle = hour * 30;                  // 30 degrees per hour\n\n    // Get SVG elements for the hands of the clock\n    let minhand = document.querySelector(\"#clock .minutehand\");\n    let hourhand = document.querySelector(\"#clock .hourhand\");\n\n    // Set an SVG attribute on them to move them around the clock face\n    minhand.setAttribute(\"transform\", `rotate(${minangle},50,50)`);\n    hourhand.setAttribute(\"transform\", `rotate(${hourangle},50,50)`);\n\n    // Run this function again in 10 seconds\n    setTimeout(updateClock, 10000);\n}()); // Note immediate invocation of the function here.\n```\n### 15.7.3 Creating SVG Images with JavaScript\nIn addition to simply scripting SVG images embedded in your HTML documents, you can also build SVG images from scratch, which can be useful to create visualizations of dynamically loaded data, for example. Example 15-4 demonstrates how you can use JavaScript to create SVG pie charts, like the one shown in Figure 15-6.\n\nEven though SVG tags can be included within HTML documents, they are technically XML tags, not HTML tags, and if you want to create SVG elements with the JavaScript DOM API, you can’t use the normal createElement() function that was introduced in §15.3.5. Instead you must use createElementNS(), which takes an XML namespace string as its first argument. For SVG, that namespace is the literal string “http://www.w3.org/2000/svg.”\n\n<Figures figure=\"15-6\">An SVG pie chart built with JavaScript (data from Stack Overflow’s 2018 Developer Survey of Most Popular Technologies)</Figures>\n\nOther than the use of createElementNS(), the pie chart–drawing code in Example 15-4 is relatively straightforward. There is a little math to convert the data being charted into pie-slice angles. The bulk of the example, however, is DOM code that creates SVG elements and sets attributes on those elements.\n\nThe most opaque part of this example is the code that draws the actual pie slices. The element used to display each slice is `<path>`. This SVG element describes arbitrary shapes comprised of lines and curves. The shape description is specified by the d attribute of the `<path>` element. The value of this attribute uses a compact grammar of letter codes and numbers that specify coordinates, angles, and other values. The letter M, for example, means “move to” and is followed by x and y coordinates. The letter L means “line to” and draws a line from the current point to the coordinates that follow it. This example also uses the letter A to draw an arc. This letter is followed by seven numbers describing the arc, and you can look up the syntax online if you want to know more.\n\nExample 15-4. Drawing a pie chart with JavaScript and SVG\n```js\n/**\n * Create an <svg> element and draw a pie chart into it.\n *\n * This function expects an object argument with the following properties:\n *\n *   width, height: the size of the SVG graphic, in pixels\n *   cx, cy, r: the center and radius of the pie\n *   lx, ly: the upper-left corner of the chart legend\n *   data: an object whose property names are data labels and whose\n *         property values are the values associated with each label\n *\n * The function returns an <svg> element. The caller must insert it into\n * the document in order to make it visible.\n */\nfunction pieChart(options) {\n    let {width, height, cx, cy, r, lx, ly, data} = options;\n\n    // This is the XML namespace for svg elements\n    let svg = \"http://www.w3.org/2000/svg\";\n\n    // Create the <svg> element, and specify pixel size and user coordinates\n    let chart = document.createElementNS(svg, \"svg\");\n    chart.setAttribute(\"width\", width);\n    chart.setAttribute(\"height\", height);\n    chart.setAttribute(\"viewBox\", `0 0 ${width} ${height}`);\n\n    // Define the text styles we'll use for the chart. If we leave these\n    // values unset here, they can be set with CSS instead.\n    chart.setAttribute(\"font-family\", \"sans-serif\");\n    chart.setAttribute(\"font-size\", \"18\");\n\n    // Get labels and values as arrays and add up the values so we know how\n    // big the pie is.\n    let labels = Object.keys(data);\n    let values = Object.values(data);\n    let total = values.reduce((x,y) => x+y);\n\n    // Figure out the angles for all the slices. Slice i starts at angles[i]\n    // and ends at angles[i+1]. The angles are measured in radians.\n    let angles = [0];\n    values.forEach((x, i) => angles.push(angles[i] + x/total * 2 * Math.PI));\n\n    // Now loop through the slices of the pie\n    values.forEach((value, i) => {\n        // Compute the two points where our slice intersects the circle\n        // These formulas are chosen so that an angle of 0 is at 12 o'clock\n        // and positive angles increase clockwise.\n        let x1 = cx + r * Math.sin(angles[i]);\n        let y1 = cy - r * Math.cos(angles[i]);\n        let x2 = cx + r * Math.sin(angles[i+1]);\n        let y2 = cy - r * Math.cos(angles[i+1]);\n\n        // This is a flag for angles larger than a half circle\n        // It is required by the SVG arc drawing component\n        let big = (angles[i+1] - angles[i] > Math.PI) ? 1 : 0;\n\n        // This string describes how to draw a slice of the pie chart:\n        let path = `M${cx},${cy}` +     // Move to circle center.\n            `L${x1},${y1}` +            // Draw line to (x1,y1).\n            `A${r},${r} 0 ${big} 1` +   // Draw an arc of radius r...\n            `${x2},${y2}` +             // ...ending at to (x2,y2).\n            \"Z\";                        // Close path back to (cx,cy).\n\n        // Compute the CSS color for this slice. This formula works for only\n        // about 15 colors. So don't include more than 15 slices in a chart.\n        let color = `hsl(${(i*40)%360},${90-3*i}%,${50+2*i}%)`;\n\n        // We describe a slice with a <path> element. Note createElementNS().\n        let slice = document.createElementNS(svg, \"path\");\n\n        // Now set attributes on the <path> element\n        slice.setAttribute(\"d\", path);           // Set the path for this slice\n        slice.setAttribute(\"fill\", color);       // Set slice color\n        slice.setAttribute(\"stroke\", \"black\");   // Outline slice in black\n        slice.setAttribute(\"stroke-width\", \"1\"); // 1 CSS pixel thick\n        chart.append(slice);                     // Add slice to chart\n\n        // Now draw a little matching square for the key\n        let icon = document.createElementNS(svg, \"rect\");\n        icon.setAttribute(\"x\", lx);              // Position the square\n        icon.setAttribute(\"y\", ly + 30*i);\n        icon.setAttribute(\"width\", 20);          // Size the square\n        icon.setAttribute(\"height\", 20);\n        icon.setAttribute(\"fill\", color);        // Same fill color as slice\n        icon.setAttribute(\"stroke\", \"black\");    // Same outline, too.\n        icon.setAttribute(\"stroke-width\", \"1\");\n        chart.append(icon);                      // Add to the chart\n\n        // And add a label to the right of the rectangle\n        let label = document.createElementNS(svg, \"text\");\n        label.setAttribute(\"x\", lx + 30);        // Position the text\n        label.setAttribute(\"y\", ly + 30*i + 16);\n        label.append(`${labels[i]} ${value}`);   // Add text to label\n        chart.append(label);                     // Add label to the chart\n    });\n\n    return chart;\n}\n```\nThe pie chart in Figure 15-6 was created using the pieChart() function from Example 15-4, like this:\n```js\ndocument.querySelector(\"#chart\").append(pieChart({\n    width: 640, height:400,    // Total size of the chart\n    cx: 200, cy: 200, r: 180,  // Center and radius of the pie\n    lx: 400, ly: 10,           // Position of the legend\n    data: {                    // The data to chart\n        \"JavaScript\": 71.5,\n        \"Java\": 45.4,\n        \"Bash/Shell\": 40.4,\n        \"Python\": 37.9,\n        \"C#\": 35.3,\n        \"PHP\": 31.4,\n        \"C++\": 24.6,\n        \"C\": 22.1,\n        \"TypeScript\": 18.3,\n        \"Ruby\": 10.3,\n        \"Swift\": 8.3,\n        \"Objective-C\": 7.3,\n        \"Go\": 7.2,\n    }\n}));\n```\n## 15.8 Graphics in a `<canvas>`\nThe `<canvas>` element has no appearance of its own but creates a drawing surface within the document and exposes a powerful drawing API to client-side JavaScript. The main difference between the `<canvas>` API and SVG is that with the canvas you create drawings by calling methods, and with SVG you create drawings by building a tree of XML elements. These two approaches are equivalently powerful: either one can be simulated with the other. On the surface, they are quite different, however, and each has its strengths and weaknesses. An SVG drawing, for example, is easily edited by removing elements from its description. To remove an element from the same graphic in a `<canvas>`, it is often necessary to erase the drawing and redraw it from scratch. Since the Canvas drawing API is JavaScript-based and relatively compact (unlike the SVG grammar), it is documented in more detail in this book.\n\n3D GRAPHICS IN A CANVAS\nYou can also call getContext() with the string “webgl” to obtain a context object that allows you to draw 3D graphics using the WebGL API. WebGL is a large, complicated, and low-level API that allows JavaScript programmers to access the GPU, write custom shaders, and perform other very powerful graphics operations. WebGL is not documented in this book, however: web developers are more likely to use utility libraries built on top of WebGL than to use the WebGL API directly.\n\nMost of the Canvas drawing API is defined not on the `<canvas>` element itself, but instead on a “drawing context” object obtained with the getContext() method of the canvas. Call getContext() with the argument “2d” to obtain a CanvasRenderingContext2D object that you can use to draw two-dimensional graphics into the canvas.\n\nAs a simple example of the Canvas API, the following HTML document uses `<canvas>` elements and some JavaScript to display two simple shapes:\n```html\n<p>This is a red square: <canvas id=\"square\" width=10 height=10></canvas>.\n<p>This is a blue circle: <canvas id=\"circle\" width=10 height=10></canvas>.\n<script>\nlet canvas = document.querySelector(\"#square\");  // Get first canvas element\nlet context = canvas.getContext(\"2d\");           // Get 2D drawing context\ncontext.fillStyle = \"#f00\";                      // Set fill color to red\ncontext.fillRect(0,0,10,10);                     // Fill a square\n\ncanvas = document.querySelector(\"#circle\");      // Second canvas element\ncontext = canvas.getContext(\"2d\");               // Get its context\ncontext.beginPath();                             // Begin a new \"path\"\ncontext.arc(5, 5, 5, 0, 2*Math.PI, true);        // Add a circle to the path\ncontext.fillStyle = \"#00f\";                      // Set blue fill color\ncontext.fill();                                  // Fill the path\n</script>\n```\nWe’ve seen that SVG describes complex shapes as a “path” of lines and curves that can be drawn or filled. The Canvas API also uses the notion of a path. Instead of describing a path as a string of letters and numbers, a path is defined by a series of method calls, such as the beginPath() and arc() invocations in the preceding code. Once a path is defined, other methods, such as fill(), operate on that path. Various properties of the context object, such as fillStyle, specify how these operations are performed.\n\nThe subsections that follow demonstrate the methods and properties of the 2D Canvas API. Much of the example code that follows operates on a variable c. This variable holds the CanvasRenderingContext2D object of the canvas, but the code to initialize that variable is sometimes not shown. In order to make these examples run, you would need to add HTML markup to define a canvas with appropriate width and height attributes, and then add code like this to initialize the variable c:\n```js\nlet canvas = document.querySelector(\"#my_canvas_id\");\nlet c = canvas.getContext('2d');\n```\n### 15.8.1 Paths and Polygons\nTo draw lines on a canvas and to fill the areas enclosed by those lines, you begin by defining a path. A path is a sequence of one or more subpaths. A subpath is a sequence of two or more points connected by line segments (or, as we’ll see later, by curve segments). Begin a new path with the beginPath() method. Begin a new subpath with the moveTo() method. Once you have established the starting point of a subpath with moveTo(), you can connect that point to a new point with a straight line by calling lineTo(). The following code defines a path that includes two line segments:\n```js\nc.beginPath();        // Start a new path\nc.moveTo(100, 100);   // Begin a subpath at (100,100)\nc.lineTo(200, 200);   // Add a line from (100,100) to (200,200)\nc.lineTo(100, 200);   // Add a line from (200,200) to (100,200)\n```\nThis code simply defines a path; it does not draw anything on the canvas. To draw (or “stroke”) the two line segments in the path, call the stroke() method, and to fill the area defined by those line segments, call fill():\n```js\nc.fill();             // Fill a triangular area\nc.stroke();           // Stroke two sides of the triangle\n```\nThis code (along with some additional code to set line widths and fill colors) produced the drawing shown in Figure 15-7.\n\n<Figures figure=\"15-7\">A simple path, filled and stroked</Figures>\n\nNotice that the subpath defined in Figure 15-7 is “open.” It consists of just two line segments, and the end point is not connected back to the starting point. This means that it does not enclose a region. The fill() method fills open subpaths by acting as if a straight line connected the last point in the subpath to the first point in the subpath. That is why this code fills a triangle, but strokes only two sides of the triangle.\n\nIf you wanted to stroke all three sides of the triangle just shown, you would call the closePath() method to connect the end point of the subpath to the start point. (You could also call lineTo(100,100), but then you end up with three line segments that share a start and end point but are not truly closed. When drawing with wide lines, the visual results are better if you use closePath().)\n\nThere are two other important points to notice about stroke() and fill(). First, both methods operate on all subpaths in the current path. Suppose we had added another subpath in the preceding code:\n```js\nc.moveTo(300,100);    // Begin a new subpath at (300,100);\nc.lineTo(300,200);    // Draw a vertical line down to (300,200);\n```\nIf we then called stroke(), we would draw two connected edges of a triangle and a disconnected vertical line.\n\nThe second point to note about stroke() and fill() is that neither one alters the current path: you can call fill() and the path will still be there when you call stroke(). When you are done with a path and want to begin another, you must remember to call beginPath(). If you don’t, you’ll end up adding new subpaths to the existing path, and you may end up drawing those old subpaths over and over again.\n\nExample 15-5 defines a function for drawing regular polygons and demonstrates the use of moveTo(), lineTo(), and closePath() for defining subpaths and of fill() and stroke() for drawing those paths. It produces the drawing shown in Figure 15-8.\n\n<Figures figure=\"15-8\">Regular polygons</Figures>\n\nExample 15-5. Regular polygons with moveTo(), lineTo(), and closePath()\n```js\n// Define a regular polygon with n sides, centered at (x,y) with radius r.\n// The vertices are equally spaced along the circumference of a circle.\n// Put the first vertex straight up or at the specified angle.\n// Rotate clockwise, unless the last argument is true.\nfunction polygon(c, n, x, y, r, angle=0, counterclockwise=false) {\n    c.moveTo(x + r*Math.sin(angle),  // Begin a new subpath at the first vertex\n             y - r*Math.cos(angle)); // Use trigonometry to compute position\n    let delta = 2*Math.PI/n;         // Angular distance between vertices\n    for(let i = 1; i < n; i++) {     // For each of the remaining vertices\n        angle += counterclockwise?-delta:delta; // Adjust angle\n        c.lineTo(x + r*Math.sin(angle),         // Add line to next vertex\n                 y - r*Math.cos(angle));\n    }\n    c.closePath();                   // Connect last vertex back to the first\n}\n\n// Assume there is just one canvas, and get its context object to draw with.\nlet c = document.querySelector(\"canvas\").getContext(\"2d\");\n\n// Start a new path and add polygon subpaths\nc.beginPath();\npolygon(c, 3, 50, 70, 50);                   // Triangle\npolygon(c, 4, 150, 60, 50, Math.PI/4);       // Square\npolygon(c, 5, 255, 55, 50);                  // Pentagon\npolygon(c, 6, 365, 53, 50, Math.PI/6);       // Hexagon\npolygon(c, 4, 365, 53, 20, Math.PI/4, true); // Small square inside the hexagon\n\n// Set some properties that control how the graphics will look\nc.fillStyle = \"#ccc\";    // Light gray interiors\nc.strokeStyle = \"#008\";  // outlined with dark blue lines\nc.lineWidth = 5;         // five pixels wide.\n\n// Now draw all the polygons (each in its own subpath) with these calls\nc.fill();                // Fill the shapes\nc.stroke();              // And stroke their outlines\n```\nNotice that this example draws a hexagon with a square inside it. The square and the hexagon are separate subpaths, but they overlap. When this happens (or when a single subpath intersects itself), the canvas needs to be able to determine which regions are inside the path and which are outside. The canvas uses a test known as the “nonzero winding rule” to achieve this. In this case, the interior of the square is not filled because the square and the hexagon were drawn in the opposite directions: the vertices of the hexagon were connected with line segments moving clockwise around the circle. The vertices of the square were connected counterclockwise. Had the square been drawn clockwise as well, the call to fill() would have filled the interior of the square as well.\n\n### 15.8.2 Canvas Dimensions and Coordinates\nThe width and height attributes of the `<canvas>` element and the corresponding width and height properties of the Canvas object specify the dimensions of the canvas. The default canvas coordinate system places the origin (0,0) at the upper-left corner of the canvas. The x coordinates increase to the right and the y coordinates increase as you go down the screen. Points on the canvas can be specified using floating-point values.\n\nThe dimensions of a canvas cannot be altered without completely resetting the canvas. Setting either the width or height properties of a Canvas (even setting them to their current value) clears the canvas, erases the current path, and resets all graphics attributes (including current transformation and clipping region) to their original state.\n\nThe width and height attributes of a canvas specify the actual number of pixels that the canvas can draw into. Four bytes of memory are allocated for each pixel, so if width and height are both set to 100, the canvas allocates 40,000 bytes to represent 10,000 pixels.\n\nThe width and height attributes also specify the default size (in CSS pixels) at which the canvas will be displayed on the screen. If window.devicePixelRatio is 2, then 100 × 100 CSS pixels is actually 40,000 hardware pixels. When the contents of the canvas are drawn onto the screen, the 10,000 pixels in memory will need to be enlarged to cover 40,000 physical pixels on the screen, and this means that your graphics will not be as crisp as they could be.\n\nFor optimum image quality, you should not use the width and height attributes to set the on-screen size of the canvas. Instead, set the desired on-screen size CSS pixel size of the canvas with CSS width and height style attributes. Then, before you begin drawing in your JavaScript code, set the width and height properties of the canvas object to the number of CSS pixels times window.devicePixelRatio. Continuing with the preceding example, this technique would result in the canvas being displayed at 100 × 100 CSS pixels but allocating memory for 200 × 200 pixels. (Even with this technique, the user can zoom in on the canvas and may see fuzzy or pixelated graphics if they do. This is in contrast to SVG graphics, which remain crisp no matter the on-screen size or zoom level.)\n\n### 15.8.3 Graphics Attributes\nExample 15-5 set the properties fillStyle, strokeStyle, and lineWidth on the context object of the canvas. These properties are graphics attributes that specify the color to be used by fill() and by stroke(), and the width of the lines to be drawn by stroke(). Notice that these parameters are not passed to the fill() and stroke() methods, but are instead part of the general graphics state of the canvas. If you define a method that draws a shape and do not set these properties yourself, the caller of your method can define the color of the shape by setting the strokeStyle and fillStyle properties before calling your method. This separation of graphics state from drawing commands is fundamental to the Canvas API and is akin to the separation of presentation from content achieved by applying CSS stylesheets to HTML documents.\n\nThere are a number of properties (and also some methods) on the context object that affect the graphics state of the canvas. They are detailed below.\n\nLINE STYLES\nThe lineWidth property specifies how wide (in CSS pixels) the lines drawn by stroke() will be. The default value is 1. It is important to understand that line width is determined by the lineWidth property at the time stroke() is called, not at the time that lineTo() and other path-building methods are called. To fully understand the lineWidth property, it is important to visualize paths as infinitely thin one-dimensional lines. The lines and curves drawn by the stroke() method are centered over the path, with half of the lineWidth on either side. If you’re stroking a closed path and only want the line to appear outside the path, stroke the path first, then fill with an opaque color to hide the portion of the stroke that appears inside the path. Or if you only want the line to appear inside a closed path, call the save() and clip() methods first, then call stroke() and restore(). (The save(), restore(), and clip() methods are described later.)\n\nWhen drawing lines that are more than about two pixels wide, the lineCap and lineJoin properties can have a significant impact on the visual appearance of the ends of a path and the vertices at which two path segments meet. Figure 15-9 illustrates the values and resulting graphical appearance of lineCap and lineJoin.\n\n<Figures figure=\"15-9\">The lineCap and lineJoin attributes</Figures>\n\nThe default value for lineCap is “butt.” The default value for lineJoin is “miter.” Note, however, that if two lines meet at a very narrow angle, then the resulting miter can become quite long and visually distracting. If the miter at a given vertex would be longer than half of the line width times the miterLimit property, that vertex will be drawn with a beveled join instead of a mitered join. The default value for miterLimit is 10.\n\nThe stroke() method can draw dashed and dotted lines as well as solid lines, and a canvas’s graphics state includes an array of numbers that serves as a “dash pattern” by specifying how many pixels to draw, then how many to omit. Unlike other line-drawing properties, the dash pattern is set and queried with the methods setLineDash() and getLineDash() instead of with a property. To specify a dotted dash pattern, you might use setLineDash() like this:\n```js\nc.setLineDash([18, 3, 3, 3]); // 18px dash, 3px space, 3px dot, 3px space\n```\nFinally, the lineDashOffset property specifies how far into the dash pattern drawing should begin. The default is 0. Paths stroked with the dash pattern shown here begin with an 18-pixel dash, but if lineDashOffset is set to 21, then that same path would begin with a dot followed by a space and a dash.\n\nCOLORS, PATTERNS, AND GRADIENTS\nThe fillStyle and strokeStyle properties specify how paths are filled and stroked. The word “style” often means color, but these properties can also be used to specify a color gradient or an image to be used for filling and stroking. (Note that drawing a line is basically the same as filling a narrow region on both sides of the line, and filling and stroking are fundamentally the same operation.)\n\nIf you want to fill or stroke with a solid color (or a translucent color), simply set these properties to a valid CSS color string. Nothing else is required.\n\nTo fill (or stroke) with a color gradient, set fillStyle (or strokeStyle) to a CanvasGradient object returned by the createLinearGradient() or createRadialGradient() methods of the context. The arguments to createLinearGradient() are the coordinates of two points that define a line (it does not need to be horizontal or vertical) along which the colors will vary. The arguments to createRadialGradient() specify the centers and radii of two circles. (They need not be concentric, but the first circle typically lies entirely inside the second.) Areas inside the smaller circle or outside the larger will be filled with solid colors; areas between the two will be filled with a color gradient.\n\nAfter creating the CanvasGradient object that defines the regions of the canvas that will be filled, you must define the gradient colors by calling the addColorStop() method of the CanvasGradient. The first argument to this method is a number between 0.0 and 1.0. The second argument is a CSS color specification. You must call this method at least twice to define a simple color gradient, but you may call it more than that. The color at 0.0 will appear at the start of the gradient, and the color at 1.0 will appear at the end. If you specify additional colors, they will appear at the specified fractional position within the gradient. Between the points you specify, colors will be smoothly interpolated. Here are some examples:\n```js\n// A linear gradient, diagonally across the canvas (assuming no transforms)\nlet bgfade = c.createLinearGradient(0,0,canvas.width,canvas.height);\nbgfade.addColorStop(0.0, \"#88f\");  // Start with light blue in upper left\nbgfade.addColorStop(1.0, \"#fff\");  // Fade to white in lower right\n\n// A gradient between two concentric circles. Transparent in the middle\n// fading to translucent gray and then back to transparent.\nlet donut = c.createRadialGradient(300,300,100, 300,300,300);\ndonut.addColorStop(0.0, \"transparent\");           // Transparent\ndonut.addColorStop(0.7, \"rgba(100,100,100,.9)\");  // Translucent gray\ndonut.addColorStop(1.0, \"rgba(0,0,0,0)\");         // Transparent again\n```\nAn important point to understand about gradients is that they are not position-independent. When you create a gradient, you specify bounds for the gradient. If you then attempt to fill an area outside of those bounds, you’ll get the solid color defined at one end or the other of the gradient.\n\nIn addition to colors and color gradients, you can also fill and stroke using images. To do this, set fillStyle or strokeStyle to a CanvasPattern returned by the createPattern() method of the context object. The first argument to this method should be an `<img>` or `<canvas>` element that contains the image you want to fill or stroke with. (Note that the source image or canvas does not need to be inserted into the document in order to be used in this way.) The second argument to createPattern() is the string “repeat,” “repeat-x,” “repeat-y,” or “no-repeat,” which specifies whether (and in which dimensions) the background images repeat.\n\nTEXT STYLES\nThe font property specifies the font to be used by the text-drawing methods fillText() and strokeText() (see “Text”). The value of the font property should be a string in the same syntax as the CSS font attribute.\n\nThe textAlign property specifies how the text should be horizontally aligned with respect to the X coordinate passed to fillText() or strokeText(). Legal values are “start,” “left,” “center,” “right,” and “end.” The default is “start,” which, for left-to-right text, has the same meaning as “left.”\n\nThe textBaseline property specifies how the text should be vertically aligned with respect to the y coordinate. The default value is “alphabetic,” and it is appropriate for Latin and similar scripts. The value “ideographic” is intended for use with scripts such as Chinese and Japanese. The value “hanging” is intended for use with Devanagari and similar scripts (which are used for many of the languages of India). The “top,” “middle,” and “bottom” baselines are purely geometric baselines, based on the “em square” of the font.\n\nSHADOWS\nFour properties of the context object control the drawing of drop shadows. If you set these properties appropriately, any line, area, text, or image you draw will be given a shadow, which will make it appear as if it is floating above the canvas surface.\n\nThe shadowColor property specifies the color of the shadow. The default is fully transparent black, and shadows will never appear unless you set this property to a translucent or opaque color. This property can only be set to a color string: patterns and gradients are not allowed for shadows. Using a translucent shadow color produces the most realistic shadow effects because it allows the background to show through.\n\nThe shadowOffsetX and shadowOffsetY properties specify the X and Y offsets of the shadow. The default for both properties is 0, which places the shadow directly beneath your drawing, where it is not visible. If you set both properties to a positive value, shadows will appear below and to the right of what you draw, as if there were a light source above and to the left, shining onto the canvas from outside the computer screen. Larger offsets produce larger shadows and make drawn objects appear as if they are floating “higher” above the canvas. These values are not affected by coordinate transformations (§15.8.5): shadow direction and “height” remain consistent even when shapes are rotated and scaled.\n\nThe shadowBlur property specifies how blurred the edges of the shadow are. The default value is 0, which produces crisp, unblurred shadows. Larger values produce more blur, up to an implementation-defined upper bound.\n\nTRANSLUCENCY AND COMPOSITING\nIf you want to stroke or fill a path using a translucent color, you can set strokeStyle or fillStyle using a CSS color syntax like “rgba(…)” that supports alpha transparency. The “a” in “RGBA” stands for “alpha” and is a value between 0 (fully transparent) and 1 (fully opaque). But the Canvas API provides another way to work with translucent colors. If you do not want to explicitly specify an alpha channel for each color, or if you want to add translucency to opaque images or patterns, you can set the globalAlpha property. Every pixel you draw will have its alpha value multiplied by globalAlpha. The default is 1, which adds no transparency. If you set globalAlpha to 0, everything you draw will be fully transparent, and nothing will appear in the canvas. But if you set this property to 0.5, then pixels that would otherwise have been opaque will be 50% opaque, and pixels that would have been 50% opaque will be 25% opaque instead.\n\nWhen you stroke lines, fill regions, draw text, or copy images, you generally expect the new pixels to be drawn on top of the pixels that are already in the canvas. If you are drawing opaque pixels, they simply replace the pixels that are already there. If you are drawing with translucent pixels, the new (“source”) pixel is combined with the old (“destination”) pixel so that the old pixel shows through the new pixel based on how transparent that pixel is.\n\nThis process of combining new (possibly translucent) source pixels with existing (possibly translucent) destination pixels is called compositing, and the compositing process described previously is the default way that the Canvas API combines pixels. But you can set the globalCompositeOperation property to specify other ways of combining pixels. The default value is “source-over,” which means that source pixels are drawn “over” the destination pixels and are combined with them if the source is translucent. But if you set globalCompositeOperation to “destination-over”, then the canvas will combine pixels as if the new source pixels were drawn beneath the existing destination pixels. If the destination is translucent or transparent, some or all of the source pixel color is visible in the resulting color. As another example, the compositing mode “source-atop” combines the source pixels with the transparency of the destination pixels so that nothing is drawn on portions of the canvas that are already fully transparent. There are a number of legal values for globalCompositeOperation, but most have only specialized uses and are not covered here.\n\nSAVING AND RESTORING GRAPHICS STATE\nSince the Canvas API defines graphics attributes on the context object, you might be tempted to call getContext() multiple times to obtain multiple context objects. If you could do this, you could define different attributes on each context: each context would then be like a different brush and would paint with a different color or draw lines of different widths. Unfortunately, you cannot use the canvas in this way. Each `<canvas>` element has only a single context object, and every call to getContext() returns the same CanvasRenderingContext2D object.\n\nAlthough the Canvas API only allows you to define a single set of graphics attributes at a time, it does allow you to save the current graphics state so that you can alter it and then easily restore it later. The save() method pushes the current graphics state onto a stack of saved states. The restore() method pops the stack and restores the most recently saved state. All of the properties that have been described in this section are part of the saved state, as are the current transformation and clipping region (both of which are explained later). Importantly, the currently defined path and the current point are not part of the graphics state and cannot be saved and restored.\n\n### 15.8.4 Canvas Drawing Operations\nWe’ve already seen some basic canvas methods—beginPath(), moveTo(), lineTo(), closePath(), fill(), and stroke()—for defining, filling, and drawing lines and polygons. But the Canvas API includes other drawing methods as well.\n\nRECTANGLES\nCanvasRenderingContext2D defines four methods for drawing rectangles. All four of these rectangle methods expect two arguments that specify one corner of the rectangle followed by the rectangle width and height. Normally, you specify the upper-left corner and then pass a positive width and positive height, but you may also specify other corners and pass negative dimensions.\n\nfillRect() fills the specified rectangle with the current fillStyle. strokeRect() strokes the outline of the specified rectangle using the current strokeStyle and other line attributes. clearRect() is like fillRect(), but it ignores the current fill style and fills the rectangle with transparent black pixels (the default color of all blank canvases). The important thing about these three methods is that they do not affect the current path or the current point within that path.\n\nThe final rectangle method is named rect(), and it does affect the current path: it adds the specified rectangle, in a subpath of its own, to the path. Like other path-definition methods, it does not fill or stroke anything itself.\n\nCURVES\nA path is a sequence of subpaths, and a subpath is a sequence of connected points. In the paths we defined in §15.8.1, those points were connected with straight line segments, but that need not always be the case. The CanvasRenderingContext2D object defines a number of methods that add a new point to the subpath and connect the current point to that new point with a curve:\n\narc()\nThis method adds a circle, or a portion of a circle (an arc), to the path. The arc to be drawn is specified with six parameters: the x and y coordinates of the center of a circle, the radius of the circle, the start and end angles of the arc, and the direction (clockwise or counterclockwise) of the arc between those two angles. If there is a current point in the path, then this method connects the current point to the beginning of the arc with a straight line (which is useful when drawing wedges or pie slices), then connects the beginning of the arc to the end of the arc with a portion of a circle, leaving the end of the arc as the new current point. If there is no current point when this method is called, then it only adds the circular arc to the path.\n\nellipse()\nThis method is much like arc() except that it adds an ellipse or a portion of an ellipse to the path. Instead of one radius, it has two: an x-axis radius and a y-axis radius. Also, because ellipses are not radially symmetrical, this method takes another argument that specifies the number of radians by which the ellipse is rotated clockwise about its center.\n\narcTo()\nThis method draws a straight line and a circular arc just like the arc() method does, but it specifies the arc to be drawn using different parameters. The arguments to arcTo() specify points P1 and P2 and a radius. The arc that is added to the path has the specified radius. It begins at the tangent point with the (imaginary) line from the current point to P1 and ends at the tangent point with the (imaginary) line between P1 and P2. This unusual-seeming method of specifying arcs is actually quite useful for drawing shapes with rounded corners. If you specify a radius of 0, this method just draws a straight line from the current point to P1. With a nonzero radius, however, it draws a straight line from the current point in the direction of P1, then curves that line around in a circle until it is heading in the direction of P2.\n\nbezierCurveTo()\nThis method adds a new point P to the subpath and connects it to the current point with a cubic Bezier curve. The shape of the curve is specified by two “control points,” C1 and C2. At the start of the curve (at the current point), the curve heads in the direction of C1. At the end of the curve (at point P), the curve arrives from the direction of C2. In between these points, the direction of the curve varies smoothly. The point P becomes the new current point for the subpath.\n\nquadraticCurveTo()\nThis method is like bezierCurveTo(), but it uses a quadratic Bezier curve instead of a cubic Bezier curve and has only a single control point.\n\nYou can use these methods to draw paths like those in Figure 15-10.\n\n<Figures figure=\"15-10\">Curved paths in a canvas</Figures>\n\nExample 15-6 shows the code used to create Figure 15-10. The methods demonstrated in this code are some of the most complicated in the Canvas API; consult an online reference for complete details on the methods and their arguments.\n\nExample 15-6. Adding curves to a path\n```js\n// A utility function to convert angles from degrees to radians\nfunction rads(x) { return Math.PI*x/180; }\n\n// Get the context object of the document's canvas element\nlet c = document.querySelector(\"canvas\").getContext(\"2d\");\n\n// Define some graphics attributes and draw the curves\nc.fillStyle = \"#aaa\";     // Gray fills\nc.lineWidth = 2;          // 2-pixel black (by default) lines\n\n// Draw a circle.\n// There is no current point, so draw just the circle with no straight\n// line from the current point to the start of the circle.\nc.beginPath();\nc.arc(75,100,50,          // Center at (75,100), radius 50\n      0,rads(360),false); // Go clockwise from 0 to 360 degrees\nc.fill();                 // Fill the circle\nc.stroke();               // Stroke its outline.\n\n// Now draw an ellipse in the same way\nc.beginPath();            // Start new path not connected to the circle\nc.ellipse(200, 100, 50, 35, rads(15),  // Center, radii, and rotation\n          0, rads(360), false);        // Start angle, end angle, direction\n\n// Draw a wedge. Angles are measured clockwise from the positive x axis.\n// Note that arc() adds a line from the current point to the arc start.\nc.moveTo(325, 100);       // Start at the center of the circle.\nc.arc(325, 100, 50,       // Circle center and radius\n      rads(-60), rads(0), // Start at angle -60 and go to angle 0\n      true);              // counterclockwise\nc.closePath();            // Add radius back to the center of the circle\n\n// Similar wedge, offset a bit, and in the opposite direction\nc.moveTo(340, 92);\nc.arc(340, 92, 42, rads(-60), rads(0), false);\nc.closePath();\n\n// Use arcTo() for rounded corners. Here we draw a square with\n// upper left corner at (400,50) and corners of varying radii.\nc.moveTo(450, 50);           // Begin in the middle of the top edge.\nc.arcTo(500,50,500,150,30);  // Add part of top edge and upper right corner.\nc.arcTo(500,150,400,150,20); // Add right edge and lower right corner.\nc.arcTo(400,150,400,50,10);  // Add bottom edge and lower left corner.\nc.arcTo(400,50,500,50,0);    // Add left edge and upper left corner.\nc.closePath();               // Close path to add the rest of the top edge.\n\n// Quadratic Bezier curve: one control point\nc.moveTo(525, 125);                      // Begin here\nc.quadraticCurveTo(550, 75, 625, 125);   // Draw a curve to (625, 125)\nc.fillRect(550-3, 75-3, 6, 6);           // Mark the control point (550,75)\n\n// Cubic Bezier curve\nc.moveTo(625, 100);                      // Start at (625, 100)\nc.bezierCurveTo(645,70,705,130,725,100); // Curve to (725, 100)\nc.fillRect(645-3, 70-3, 6, 6);           // Mark control points\nc.fillRect(705-3, 130-3, 6, 6);\n\n// Finally, fill the curves and stroke their outlines.\nc.fill();\nc.stroke();\n```\nTEXT\nTo draw text in a canvas, you normally use the fillText() method, which draws text using the color (or gradient or pattern) specified by the fillStyle property. For special effects at large text sizes, you can use strokeText() to draw the outline of the individual font glyphs. Both methods take the text to be drawn as their first argument and take the x and y coordinates of the text as the second and third arguments. Neither method affects the current path or the current point.\n\nfillText() and strokeText() take an optional fourth argument. If given, this argument specifies the maximum width of the text to be displayed. If the text would be wider than the specified value when drawn using the font property, the canvas will make it fit by scaling it or by using a narrower or smaller font.\n\nIf you need to measure text yourself before drawing it, pass it to the measureText() method. This method returns a TextMetrics object that specifies the measurements of the text when drawn with the current font. At the time of this writing, the only “metric” contained in the TextMetrics object is the width. Query the on-screen width of a string like this:\n\nlet width = c.measureText(text).width;\nThis is useful if you want to center a string of text within a canvas, for example.\n\nIMAGES\nIn addition to vector graphics (paths, lines, etc.), the Canvas API also supports bitmap images. The drawImage() method copies the pixels of a source image (or of a rectangle within the source image) onto the canvas, scaling and rotating the pixels of the image as necessary.\n\ndrawImage() can be invoked with three, five, or nine arguments. In all cases, the first argument is the source image from which pixels are to be copied. This image argument is often an `<img>` element, but it can also be another `<canvas>` element or even a `<video>` element (from which a single frame will be copied). If you specify an `<img>` or `<video>` element that is still loading its data, the drawImage() call will do nothing.\n\nIn the three-argument version of drawImage(), the second and third arguments specify the x and y coordinates at which the upper-left corner of the image is to be drawn. In this version of the method, the entire source image is copied to the canvas. The x and y coordinates are interpreted in the current coordinate system, and the image is scaled and rotated if necessary, depending on the canvas transform currently in effect.\n\nThe five-argument version of drawImage() adds width and height arguments to the x and y arguments described earlier. These four arguments define a destination rectangle within the canvas. The upper-left corner of the source image goes at (x,y), and the lower-right corner goes at (x+width, y+height). Again, the entire source image is copied. With this version of the method, the source image will be scaled to fit the destination rectangle.\n\nThe nine-argument version of drawImage() specifies both a source rectangle and a destination rectangle and copies only the pixels within the source rectangle. Arguments two through five specify the source rectangle. They are measured in CSS pixels. If the source image is another canvas, the source rectangle uses the default coordinate system for that canvas and ignores any transformations that have been specified. Arguments six through nine specify the destination rectangle into which the image is drawn and are in the current coordinate system of the canvas, not in the default coordinate system.\n\nIn addition to drawing images into a canvas, we can also extract the content of a canvas as an image using the toDataURL() method. Unlike all the other methods described here, toDataURL() is a method of the Canvas element itself, not of the context object. You normally invoke toDataURL() with no arguments, and it returns the content of the canvas as a PNG image, encoded as a string using a data: URL. The returned URL is suitable for use with an `<img>` element, and you can make a static snapshot of a canvas with code like this:\n```js\nlet img = document.createElement(\"img\");  // Create an <img> element\nimg.src = canvas.toDataURL();             // Set its src attribute\ndocument.body.appendChild(img);           // Append it to the document\n```\n### 15.8.5 Coordinate System Transforms\nAs we’ve noted, the default coordinate system of a canvas places the origin in the upper-left corner, has x coordinates increasing to the right, and has y coordinates increasing downward. In this default system, the coordinates of a point map directly to a CSS pixel (which then maps directly to one or more device pixels). Certain canvas operations and attributes (such as extracting raw pixel values and setting shadow offsets) always use this default coordinate system. In addition to the default coordinate system, however, every canvas has a “current transformation matrix” as part of its graphics state. This matrix defines the current coordinate system of the canvas. In most canvas operations, when you specify the coordinates of a point, it is taken to be a point in the current coordinate system, not in the default coordinate system. The current transformation matrix is used to convert the coordinates you specified to the equivalent coordinates in the default coordinate system.\n\nThe setTransform() method allows you to set a canvas’s transformation matrix directly, but coordinate system transformations are usually easier to specify as a sequence of translations, rotations, and scaling operations. Figure 15-11 illustrates these operations and their effect on the canvas coordinate system. The program that produced the figure drew the same set of axes seven times in a row. The only thing that changed each time was the current transform. Notice that the transforms affect the text as well as the lines that are drawn.\n\n<Figures figure=\"15-11\">Coordinate system transformations</Figures>\n\nThe translate() method simply moves the origin of the coordinate system left, right, up, or down. The rotate() method rotates the axes clockwise by the specified angle. (The Canvas API always specifies angles in radians. To convert degrees to radians, divide by 180 and multiply by Math.PI.) The scale() method stretches or contracts distances along the x or y axes.\n\nPassing a negative scale factor to the scale() method flips that axis across the origin, as if it were reflected in a mirror. This is what was done in the lower left of Figure 15-11: translate() was used to move the origin to the bottom-left corner of the canvas, then scale() was used to flip the y axis around so that y coordinates increase as we go up the page. A flipped coordinate system like this is familiar from algebra class and may be useful for plotting data points on charts. Note, however, that it makes text difficult to read!\n\nUNDERSTANDING TRANSFORMATIONS MATHEMATICALLY\nI find it easiest to understand transforms geometrically, thinking about translate(), rotate(), and scale() as transforming the axes of the coordinate system as illustrated in Figure 15-11. It is also possible to understand transforms algebraically as equations that map the coordinates of a point (x,y) in the transformed coordinate system back to the coordinates (x',y') of the same point in the previous coordinate system.\n\nThe method call c.translate(dx,dy) can be described with these equations:\n```js\nx' = x + dx;  // An X coordinate of 0 in the new system is dx in the old\ny' = y + dy;\n```\nScaling operations have similarly simple equations. A call c.scale(sx,sy) can be described like this:\n```js\nx' = sx * x;\ny' = sy * y;\n```\nRotations are more complicated. The call c.rotate(a) is described by these trigonometric equations:\n```js\nx' = x * cos(a) - y * sin(a);\ny' = y * cos(a) + x * sin(a);\n```\nNotice that the order of transformations matters. Suppose we start with the default coordinate system of a canvas, then translate it, and then scale it. In order to map the point (x,y) in the current coordinate system back to the point (x'',y'') in the default coordinate system, we must first apply the scaling equations to map the point to an intermediate point (x',y') in the translated but unscaled coordinate system, then use the translation equations to map from this intermediate point to (x'',y''). The result is this:\n```js\nx'' = sx*x + dx;\ny'' = sy*y + dy;\n```\nIf, on the other hand, we’d called scale() before calling translate(), the resulting equations would be different:\n```js\nx'' = sx*(x + dx);\ny'' = sy*(y + dy);\n```\nThe key thing to remember when thinking algebraically about sequences of transformations is that you must work backward from the last (most recent) transformation to the first. When thinking geometrically about transformed axes, however, you work forward from first transformation to last.\n\nThe transformations supported by the canvas are known as affine transforms. Affine transforms may modify the distances between points and the angles between lines, but parallel lines always remain parallel after an affine transformation—it is not possible, for example, to specify a fish-eye lens distortion with an affine transform. An arbitrary affine transform can be described by the six parameters a through f in these equations:\n```js\nx' = ax + cy + e\ny' = bx + dy + f\n```\nYou can apply an arbitrary transformation to the current coordinate system by passing those six parameters to the transform() method. Figure 15-11 illustrates two types of transformations—shears and rotations about a specified point—that you can implement with the transform() method like this:\n```js\n// Shear transform:\n//   x' = x + kx*y;\n//   y' = ky*x + y;\nfunction shear(c, kx, ky) { c.transform(1, ky, kx, 1, 0, 0); }\n\n// Rotate theta radians counterclockwise around the point (x,y)\n// This can also be accomplished with a translate, rotate, translate sequence\nfunction rotateAbout(c, theta, x, y) {\n    let ct = Math.cos(theta);\n    let st = Math.sin(theta);\n    c.transform(ct, -st, st, ct, -x*ct-y*st+x, x*st-y*ct+y);\n}\n```\nThe setTransform() method takes the same arguments as transform(), but instead of transforming the current coordinate system, it ignores the current system, transforms the default coordinate system, and makes the result the new current coordinate system. setTransform() is useful to temporarily reset the canvas to its default coordinate system:\n```js\nc.save();                      // Save current coordinate system\nc.setTransform(1,0,0,1,0,0);   // Revert to the default coordinate system\n// Perform operations using default CSS pixel coordinates\nc.restore();                   // Restore the saved coordinate system\n```\nTRANSFORMATION EXAMPLE\nExample 15-7 demonstrates the power of coordinate system transformations by using the translate(), rotate(), and scale() methods recursively to draw a Koch snowflake fractal. The output of this example appears in Figure 15-12, which shows Koch snowflakes with 0, 1, 2, 3, and 4 levels of recursion.\n\n<Figures figure=\"15-12\">Koch snowflakes</Figures>\n\nThe code that produces these figures is elegant, but its use of recursive coordinate system transformations makes it somewhat difficult to understand. Even if you don’t follow all the nuances, note that the code includes only a single invocation of the lineTo() method. Every single line segment in Figure 15-12 is drawn like this:\n```js\nc.lineTo(len, 0);\n```\nThe value of the variable len does not change during the execution of the program, so the position, orientation, and length of each of the line segments is determined by translations, rotations, and scaling operations.\n\nExample 15-7. A Koch snowflake with transformations\n```js\nlet deg = Math.PI/180;  // For converting degrees to radians\n\n// Draw a level-n Koch snowflake fractal on the canvas context c,\n// with lower-left corner at (x,y) and side length len.\nfunction snowflake(c, n, x, y, len) {\n    c.save();           // Save current transformation\n    c.translate(x,y);   // Translate origin to starting point\n    c.moveTo(0,0);      // Begin a new subpath at the new origin\n    leg(n);             // Draw the first leg of the snowflake\n    c.rotate(-120*deg); // Now rotate 120 degrees counterclockwise\n    leg(n);             // Draw the second leg\n    c.rotate(-120*deg); // Rotate again\n    leg(n);             // Draw the final leg\n    c.closePath();      // Close the subpath\n    c.restore();        // And restore original transformation\n\n    // Draw a single leg of a level-n Koch snowflake.\n    // This function leaves the current point at the end of the leg it has\n    // drawn and translates the coordinate system so the current point is (0,0).\n    // This means you can easily call rotate() after drawing a leg.\n    function leg(n) {\n        c.save();               // Save the current transformation\n        if (n === 0) {          // Nonrecursive case:\n            c.lineTo(len, 0);   //   Just draw a horizontal line\n        }                       //                                       _  _\n        else {                  // Recursive case: draw 4 sub-legs like:  \\/\n            c.scale(1/3,1/3);   // Sub-legs are 1/3 the size of this leg\n            leg(n-1);           // Recurse for the first sub-leg\n            c.rotate(60*deg);   // Turn 60 degrees clockwise\n            leg(n-1);           // Second sub-leg\n            c.rotate(-120*deg); // Rotate 120 degrees back\n            leg(n-1);           // Third sub-leg\n            c.rotate(60*deg);   // Rotate back to our original heading\n            leg(n-1);           // Final sub-leg\n        }\n        c.restore();            // Restore the transformation\n        c.translate(len, 0);    // But translate to make end of leg (0,0)\n    }\n}\n\nlet c = document.querySelector(\"canvas\").getContext(\"2d\");\nsnowflake(c, 0, 25, 125, 125);  // A level-0 snowflake is a triangle\nsnowflake(c, 1, 175, 125, 125); // A level-1 snowflake is a 6-sided star\nsnowflake(c, 2, 325, 125, 125); // etc.\nsnowflake(c, 3, 475, 125, 125);\nsnowflake(c, 4, 625, 125, 125); // A level-4 snowflake looks like a snowflake!\nc.stroke();                     // Stroke this very complicated path\n```\n### 15.8.6 Clipping\nAfter defining a path, you usually call stroke() or fill() (or both). You can also call the clip() method to define a clipping region. Once a clipping region is defined, nothing will be drawn outside of it. Figure 15-13 shows a complex drawing produced using clipping regions. The vertical stripe running down the middle and the text along the bottom of the figure were stroked with no clipping region and then filled after the triangular clipping region was defined.\n\n<Figures figure=\"15-13\">Unclipped strokes and clipped fills</Figures>\n\nFigure 15-13 was generated using the polygon() method of Example 15-5 and the following code:\n```js\n// Define some drawing attributes\nc.font = \"bold 60pt sans-serif\";    // Big font\nc.lineWidth = 2;                    // Narrow lines\nc.strokeStyle = \"#000\";             // Black lines\n\n// Outline a rectangle and some text\nc.strokeRect(175, 25, 50, 325);     // A vertical stripe down the middle\nc.strokeText(\"<canvas>\", 15, 330);  // Note strokeText() instead of fillText()\n\n// Define a complex path with an interior that is outside.\npolygon(c,3,200,225,200);           // Large triangle\npolygon(c,3,200,225,100,0,true);    // Smaller reverse triangle inside\n\n// Make that path the clipping region.\nc.clip();\n\n// Stroke the path with a 5 pixel line, entirely inside the clipping region.\nc.lineWidth = 10;       // Half of this 10 pixel line will be clipped away\nc.stroke();\n\n// Fill the parts of the rectangle and text that are inside the clipping region\nc.fillStyle = \"#aaa\";             // Light gray\nc.fillRect(175, 25, 50, 325);     // Fill the vertical stripe\nc.fillStyle = \"#888\";             // Darker gray\nc.fillText(\"<canvas>\", 15, 330);  // Fill the text\n```\nIt is important to note that when you call clip(), the current path is itself clipped to the current clipping region, then that clipped path becomes the new clipping region. This means that the clip() method can shrink the clipping region but can never enlarge it. There is no method to reset the clipping region, so before calling clip(), you should typically call save() so that you can later restore() the unclipped region.\n\n### 15.8.7 Pixel Manipulation\nThe getImageData() method returns an ImageData object that represents the raw pixels (as R, G, B, and A components) from a rectangular region of your canvas. You can create empty ImageData objects with createImageData(). The pixels in an ImageData object are writable, so you can set them any way you want, then copy those pixels back onto the canvas with putImageData().\n\nThese pixel manipulation methods provide very low-level access to the canvas. The rectangle you pass to getImageData() is in the default coordinate system: its dimensions are measured in CSS pixels, and it is not affected by the current transformation. When you call putImageData(), the position you specify is also measured in the default coordinate system. Furthermore, putImageData() ignores all graphics attributes. It does not perform any compositing, it does not multiply pixels by globalAlpha, and it does not draw shadows.\n\nPixel manipulation methods are useful for implementing image processing. Example 15-8 shows how to create a simple motion blur or “smear” effect like that shown in Figure 15-14.\n\n<Figures figure=\"15-14\">A motion blur effect created by image processing</Figures>\n\nThe following code demonstrates getImageData() and putImageData() and shows how to iterate through and modify the pixel values in an ImageData object.\n\nExample 15-8. Motion blur with ImageData\n```js\n// Smear the pixels of the rectangle to the right, producing a\n// sort of motion blur as if objects are moving from right to left.\n// n must be 2 or larger. Larger values produce bigger smears.\n// The rectangle is specified in the default coordinate system.\nfunction smear(c, n, x, y, w, h) {\n    // Get the ImageData object that represents the rectangle of pixels to smear\n    let pixels = c.getImageData(x, y, w, h);\n\n    // This smear is done in-place and requires only the source ImageData.\n    // Some image processing algorithms require an additional ImageData to\n    // store transformed pixel values. If we needed an output buffer, we could\n    // create a new ImageData with the same dimensions like this:\n    //   let output_pixels = c.createImageData(pixels);\n\n    // Get the dimensions of the grid of pixels in the ImageData object\n    let width = pixels.width, height = pixels.height;\n\n    // This is the byte array that holds the raw pixel data, left-to-right and\n    // top-to-bottom. Each pixel occupies 4 consecutive bytes in R,G,B,A order.\n    let data = pixels.data;\n\n    // Each pixel after the first in each row is smeared by replacing it with\n    // 1/nth of its own value plus m/nths of the previous pixel's value\n    let m = n-1;\n\n    for(let row = 0; row < height; row++) {  // For each row\n        let i = row*width*4 + 4;  // The offset of the second pixel of the row\n        for(let col = 1; col < width; col++, i += 4) { // For each column\n            data[i] =   (data[i] + data[i-4]*m)/n;     // Red pixel component\n            data[i+1] = (data[i+1] + data[i-3]*m)/n;   // Green\n            data[i+2] = (data[i+2] + data[i-2]*m)/n;   // Blue\n            data[i+3] = (data[i+3] + data[i-1]*m)/n;   // Alpha component\n        }\n    }\n\n    // Now copy the smeared image data back to the same position on the canvas\n    c.putImageData(pixels, x, y);\n}\n```\n## 15.9 Audio APIs\nThe HTML `<audio>` and `<video>` tags allow you to easily include sound and videos in your web pages. These are complex elements with significant APIs and nontrivial user interfaces. You can control media playback with the play() and pause() methods. You can set the volume and playbackRate properties to control the audio volume and speed of playback. And you can skip to a particular time within the media by setting the currentTime property.\n\nWe will not cover `<audio>` and `<video>` tags in any further detail here, however. The following subsections demonstrate two ways to add scripted sound effects to your web pages.\n\n### 15.9.1 The Audio() Constructor\nYou don’t have to include an `<audio>` tag in your HTML document in order to include sound effects in your web pages. You can dynamically create `<audio>` elements with the normal DOM document.createElement() method, or, as a shortcut, you can simply use the Audio() constructor. You do not have to add the created element to your document in order to play it. You can simply call its play() method:\n```js\n// Load the sound effect in advance so it is ready for use\nlet soundeffect = new Audio(\"soundeffect.mp3\");\n\n// Play the sound effect whenever the user clicks the mouse button\ndocument.addEventListener(\"click\", () => {\n    soundeffect.cloneNode().play(); // Load and play the sound\n});\n```\nNote the use of cloneNode() here. If the user clicks the mouse rapidly, we want to be able to have multiple overlapping copies of the sound effect playing at the same time. To do that, we need multiple Audio elements. Because the Audio elements are not added to the document, they will be garbage collected when they are done playing.\n\n### 15.9.2 The WebAudio API\nIn addition to playback of recorded sounds with Audio elements, web browsers also allow the generation and playback of synthesized sounds with the WebAudio API. Using the WebAudio API is like hooking up an old-style electronic synthesizer with patch cords. With WebAudio, you create a set of AudioNode objects, which represents sources, transformations, or destinations of waveforms, and then connect these nodes together into a network to produce sounds. The API is not particularly complex, but a full explanation requires an understanding of electronic music and signal processing concepts that are beyond the scope of this book.\n\nThe following code below uses the WebAudio API to synthesize a short chord that fades out over about a second. This example demonstrates the basics of the WebAudio API. If this is interesting to you, you can find much more about this API online:\n```js\n// Begin by creating an audioContext object. Safari still requires\n// us to use webkitAudioContext instead of AudioContext.\nlet audioContext = new (this.AudioContext||this.webkitAudioContext)();\n\n// Define the base sound as a combination of three pure sine waves\nlet notes = [ 293.7, 370.0, 440.0 ]; // D major chord: D, F# and A\n\n// Create oscillator nodes for each of the notes we want to play\nlet oscillators = notes.map(note => {\n    let o = audioContext.createOscillator();\n    o.frequency.value = note;\n    return o;\n});\n\n// Shape the sound by controlling its volume over time.\n// Starting at time 0 quickly ramp up to full volume.\n// Then starting at time 0.1 slowly ramp down to 0.\nlet volumeControl = audioContext.createGain();\nvolumeControl.gain.setTargetAtTime(1, 0.0, 0.02);\nvolumeControl.gain.setTargetAtTime(0, 0.1, 0.2);\n\n// We're going to send the sound to the default destination:\n// the user's speakers\nlet speakers = audioContext.destination;\n\n// Connect each of the source notes to the volume control\noscillators.forEach(o => o.connect(volumeControl));\n\n// And connect the output of the volume control to the speakers.\nvolumeControl.connect(speakers);\n\n// Now start playing the sounds and let them run for 1.25 seconds.\nlet startTime = audioContext.currentTime;\nlet stopTime = startTime + 1.25;\noscillators.forEach(o => {\n    o.start(startTime);\n    o.stop(stopTime);\n});\n\n// If we want to create a sequence of sounds we can use event handlers\noscillators[0].addEventListener(\"ended\", () => {\n    // This event handler is invoked when the note stops playing\n});\n```\n## 15.10 Location, Navigation, and History\nThe location property of both the Window and Document objects refers to the Location object, which represents the current URL of the document displayed in the window, and which also provides an API for loading new documents into the window.\n\nThe Location object is very much like a URL object (§11.9), and you can use properties like protocol, hostname, port, and path to access the various parts of the URL of the current document. The href property returns the entire URL as a string, as does the toString() method.\n\nThe hash and search properties of the Location object are interesting ones. The hash property returns the “fragment identifier” portion of the URL, if there is one: a hash mark (#) followed by an element ID. The search property is similar. It returns the portion of the URL that starts with a question mark: often some sort of query string. In general, this portion of a URL is used to parameterize the URL and provides a way to embed arguments in it. While these arguments are usually intended for scripts run on a server, there is no reason why they cannot also be used in JavaScript-enabled pages.\n\nURL objects have a searchParams property that is a parsed representation of the search property. The Location object does not have a searchParams property, but if you want to parse window.location.search, you can simply create a URL object from the Location object and then use the URL’s searchParams:\n```js\nlet url = new URL(window.location);\nlet query = url.searchParams.get(\"q\");\nlet numResults = parseInt(url.searchParams.get(\"n\") || \"10\");\n```\nIn addition to the Location object that you can refer to as window.location or document.location, and the URL() constructor that we used earlier, browsers also define a document.URL property. Surprisingly, the value of this property is not a URL object, but just a string. The string holds the URL of the current document.\n\n### 15.10.1 Loading New Documents\nIf you assign a string to window.location or to document.location, that string is interpreted as a URL and the browser loads it, replacing the current document with a new one:\n```js\nwindow.location = \"http://www.oreilly.com\"; // Go buy some books!\n```\nYou can also assign relative URLs to location. They are resolved relative to the current URL:\n```js\ndocument.location = \"page2.html\";           // Load the next page\n```\nA bare fragment identifier is a special kind of relative URL that does not cause the browser to load a new document but simply to scroll so that the document element with id or name that matches the fragment is visible at the top of the browser window. As a special case, the fragment identifier #top makes the browser jump to the start of the document (assuming no element has an id=\"top\" attribute):\n```js\nlocation = \"#top\";                          // Jump to the top of the document\n```\nThe individual properties of the Location object are writable, and setting them changes the location URL and also causes the browser to load a new document (or, in the case of the hash property, to navigate within the current document):\n```js\ndocument.location.path = \"pages/3.html\"; // Load a new page\ndocument.location.hash = \"TOC\";          // Scroll to the table of contents\nlocation.search = \"?page=\" + (page+1);   // Reload with new query string\n```\nYou can also load a new page by passing a new string to the assign() method of the Location object. This is the same as assigning the string to the location property, however, so it’s not particularly interesting.\n\nThe replace() method of the Location object, on the other hand, is quite useful. When you pass a string to replace(), it is interpreted as a URL and causes the browser to load a new page, just as assign() does. The difference is that replace() replaces the current document in the browser’s history. If a script in document A sets the location property or calls assign() to load document B and then the user clicks the Back button, the browser will go back to document A. If you use replace() instead, then document A is erased from the browser’s history, and when the user clicks the Back button, the browser returns to whatever document was displayed before document A.\n\nWhen a script unconditionally loads a new document, the replace() method is a better choice than assign(). Otherwise, the Back button would take the browser back to the original document, and the same script would again load the new document. Suppose you have a JavaScript-enhanced version of your page and a static version that does not use JavaScript. If you determine that the user’s browser does not support the web platform APIs that you want to use, you could use location.replace() to load the static version:\n```js\n// If the browser does not support the JavaScript APIs we need,\n// redirect to a static page that does not use JavaScript.\nif (!isBrowserSupported()) location.replace(\"staticpage.html\");\n```\nNotice that the URL passed to replace() is a relative one. Relative URLs are interpreted relative to the page in which they appear, just as they would be if they were used in a hyperlink.\n\nIn addition to the assign() and replace() methods, the Location object also defines reload(), which simply makes the browser reload the document.\n\n### 15.10.2 Browsing History\nThe history property of the Window object refers to the History object for the window. The History object models the browsing history of a window as a list of documents and document states. The length property of the History object specifies the number of elements in the browsing history list, but for security reasons, scripts are not allowed to access the stored URLs. (If they could, any scripts could snoop through your browsing history.)\n\nThe History object has back() and forward() methods that behave like the browser’s Back and Forward buttons do: they make the browser go backward or forward one step in its browsing history. A third method, go(), takes an integer argument and can skip any number of pages forward (for positive arguments) or backward (for negative arguments) in the history list:\n```js\nhistory.go(-2);   // Go back 2, like clicking the Back button twice\nhistory.go(0);    // Another way to reload the current page\n```\nIf a window contains child windows (such as `<iframe>` elements), the browsing histories of the child windows are chronologically interleaved with the history of the main window. This means that calling history.back() (for example) on the main window may cause one of the child windows to navigate back to a previously displayed document but leaves the main window in its current state.\n\nThe History object described here dates back to the early days of the web when documents were passive and all computation was performed on the server. Today, web applications often generate or load content dynamically and display new application states without actually loading new documents. Applications like these must perform their own history management if they want the user to be able to use the Back and Forward buttons (or the equivalent gestures) to navigate from one application state to another in an intuitive way. There are two ways to accomplish this, described in the next two sections.\n\n### 15.10.3 History Management with hashchange Events\nOne history management technique involves location.hash and the “hashchange” event. Here are the key facts you need to know to understand this technique:\n\nThe location.hash property sets the fragment identifier of the URL and is traditionally used to specify the ID of a document section to scroll to. But location.hash does not have to be an element ID: you can set it to any string. As long as no element happens to have that string as its ID, the browser won’t scroll when you set the hash property like this.\n\nSetting the location.hash property updates the URL displayed in the location bar and, very importantly, adds an entry to the browser’s history.\n\nWhenever the fragment identifier of the document changes, the browser fires a “hashchange” event on the Window object. If you set location.hash explictly, a “hashchange” event is fired. And, as we’ve mentioned, this change to the Location object creates a new entry in the browser’s browsing history. So if the user now clicks the Back button, the browser will return to its previous URL before you set location.hash. But this means that the fragment identifier has changed again, so another “hashchange” event is fired in this case. This means that as long as you can create a unique fragment identifier for each possible state of your application, “hashchange” events will notify you if the user moves backward and forward though their browsing history.\n\nTo use this history management mechanism, you’ll need to be able to encode the state information necessary to render a “page” of your application into a relatively short string of text that is suitable for use as a fragment identifier. And you’ll need to write a function to convert page state into a string and another function to parse the string and re-create the page state it represents.\n\nOnce you have written those functions, the rest is easy. Define a window.onhashchange function (or register a “hashchange” listener with addEventListener()) that reads location.hash, converts that string into a representation of your application state, and then takes whatever actions are necessary to display that new application state.\n\nWhen the user interacts with your application (such as by clicking a link) in a way that would cause the application to enter a new state, don’t render the new state directly. Instead, encode the desired new state as a string and set location.hash to that string. This will trigger a “hashchange” event, and your handler for that event will display the new state. Using this roundabout technique ensures that the new state is inserted into the browsing history so that the Back and Forward buttons continue to work.\n\n### 15.10.4 History Management with pushState()\nThe second technique for managing history is somewhat more complex but is less of a hack than the “hashchange” event. This more robust history-management technique is based on the history.pushState() method and the “popstate” event. When a web app enters a new state, it calls history.pushState() to add an object representing the state to the browser’s history. If the user then clicks the Back button, the browser fires a “popstate” event with a copy of that saved state object, and the app uses that object to re-create its previous state. In addition to the saved state object, applications can also save a URL with each state, which is important if you want users to be able to bookmark and share links to the internal states of the app.\n\nThe first argument to pushState() is an object that contains all the state information necessary to restore the current state of the document. This object is saved using HTML’s structured clone algorithm, which is more versatile than JSON.stringify() and can support Map, Set, and Date objects as well as typed arrays and ArrayBuffers.\n\nThe second argument was intended to be a title string for the state, but most browsers do not support it, and you should just pass an empty string. The third argument is an optional URL that will be displayed in the location bar immediately and also if the user returns to this state via Back and Forward buttons. Relative URLs are resolved against the current location of the document. Associating a URL with each state allows the user to bookmark internal states of your application. Remember, though, that if the user saves a bookmark and then visits it a day later, you won’t get a “popstate” event about that visit: you’ll have to restore your application state by parsing the URL.\n\nTHE STRUCTURED CLONE ALGORITHM\nThe history.pushState() method does not use JSON.stringify() (§11.6) to serialize state data. Instead, it (and other browser APIs we’ll learn about later) uses a more robust serialization technique known as the structured clone algorithm, defined by the HTML standard.\n\nThe structured clone algorithm can serialize anything that JSON.stringify() can, but in addition, it enables serialization of most other JavaScript types, including Map, Set, Date, RegExp, and typed arrays, and it can handle data structures that include circular references. The structured clone algorithm cannot serialize functions or classes, however. When cloning objects it does not copy the prototype object, getters and setters, or non-enumerable properties. While the structured clone algorithm can clone most built-in JavaScript types, it cannot copy types defined by the host environment, such as document Element objects.\n\nThis means that the state object you pass to history.pushState() need not be limited to the objects, arrays, and primitive values that JSON.stringify() supports. Note, however, that if you pass an instance of a class that you have defined, that instance will be serialized as an ordinary JavaScript object and will lose its prototype.\n\nIn addition to the pushState() method, the History object also defines replaceState(), which takes the same arguments but replaces the current history state instead of adding a new state to the browsing history. When an application that uses pushState() is first loaded, it is often a good idea to call replaceState() to define a state object for this initial state of the application.\n\nWhen the user navigates to saved history states using the Back or Forward buttons, the browser fires a “popstate” event on the Window object. The event object associated with the event has a property named state, which contains a copy (another structured clone) of the state object you passed to pushState().\n\nExample 15-9 is a simple web application—the number-guessing game pictured in Figure 15-15—that uses pushState() to save its history, allowing the user to “go back” to review or redo their guesses.\n\n<Figures figure=\"15-15\">A number-guessing game</Figures>\n\nExample 15-9. History management with pushState()\n```html\n<html><head><title>I'm thinking of a number...</title>\n<style>\nbody { height: 250px; display: flex; flex-direction: column;\n       align-items: center; justify-content: space-evenly; }\n#heading { font: bold 36px sans-serif; margin: 0; }\n#container { border: solid black 1px; height: 1em; width: 80%; }\n#range { background-color: green; margin-left: 0%; height: 1em; width: 100%; }\n#input { display: block; font-size: 24px; width: 60%; padding: 5px; }\n#playagain { font-size: 24px; padding: 10px; border-radius: 5px; }\n</style>\n</head>\n<body>\n<h1 id=\"heading\">I'm thinking of a number...</h1>\n<!-- A visual representation of the numbers that have not been ruled out -->\n<div id=\"container\"><div id=\"range\"></div></div>\n<!-- Where the user enters their guess -->\n<input id=\"input\" type=\"text\">\n<!-- A button that reloads with no search string. Hidden until game ends. -->\n<button id=\"playagain\" hidden onclick=\"location.search='';\">Play Again</button>\n<script>\n/**\n * An instance of this GameState class represents the internal state of\n * our number guessing game. The class defines static factory methods for\n * initializing the game state from different sources, a method for\n * updating the state based on a new guess, and a method for modifying the\n * document based on the current state.\n */\nclass GameState {\n    // This is a factory function to create a new game\n    static newGame() {\n        let s = new GameState();\n        s.secret = s.randomInt(0, 100);  // An integer: 0 < n < 100\n        s.low = 0;                       // Guesses must be greater than this\n        s.high = 100;                    // Guesses must be less than this\n        s.numGuesses = 0;                // How many guesses have been made\n        s.guess = null;                  // What the last guess was\n        return s;\n    }\n\n    // When we save the state of the game with history.pushState(), it is just\n    // a plain JavaScript object that gets saved, not an instance of GameState.\n    // So this factory function re-creates a GameState object based on the\n    // plain object that we get from a popstate event.\n    static fromStateObject(stateObject) {\n        let s = new GameState();\n        for(let key of Object.keys(stateObject)) {\n            s[key] = stateObject[key];\n        }\n        return s;\n    }\n\n    // In order to enable bookmarking, we need to be able to encode the\n    // state of any game as a URL. This is easy to do with URLSearchParams.\n    toURL() {\n        let url = new URL(window.location);\n        url.searchParams.set(\"l\", this.low);\n        url.searchParams.set(\"h\", this.high);\n        url.searchParams.set(\"n\", this.numGuesses);\n        url.searchParams.set(\"g\", this.guess);\n        // Note that we can't encode the secret number in the url or it\n        // will give away the secret. If the user bookmarks the page with\n        // these parameters and then returns to it, we will simply pick a\n        // new random number between low and high.\n        return url.href;\n    }\n\n    // This is a factory function that creates a new GameState object and\n    // initializes it from the specified URL. If the URL does not contain the\n    // expected parameters or if they are malformed it just returns null.\n    static fromURL(url) {\n        let s = new GameState();\n        let params = new URL(url).searchParams;\n        s.low = parseInt(params.get(\"l\"));\n        s.high = parseInt(params.get(\"h\"));\n        s.numGuesses = parseInt(params.get(\"n\"));\n        s.guess = parseInt(params.get(\"g\"));\n\n        // If the URL is missing any of the parameters we need or if\n        // they did not parse as integers, then return null;\n        if (isNaN(s.low) || isNaN(s.high) ||\n            isNaN(s.numGuesses) || isNaN(s.guess)) {\n            return null;\n        }\n\n        // Pick a new secret number in the right range each time we\n        // restore a game from a URL.\n        s.secret = s.randomInt(s.low, s.high);\n        return s;\n    }\n\n    // Return an integer n, min < n < max\n    randomInt(min, max) {\n        return min + Math.ceil(Math.random() * (max - min - 1));\n    }\n\n    // Modify the document to display the current state of the game.\n    render() {\n        let heading = document.querySelector(\"#heading\"); // The <h1> at the top\n        let range = document.querySelector(\"#range\");     // Display guess range\n        let input = document.querySelector(\"#input\");     // Guess input field\n        let playagain = document.querySelector(\"#playagain\");\n\n        // Update the document heading and title\n        heading.textContent = document.title =\n            `I'm thinking of a number between ${this.low} and ${this.high}.`;\n\n        // Update the visual range of numbers\n        range.style.marginLeft = `${this.low}%`;\n        range.style.width = `${(this.high-this.low)}%`;\n\n        // Make sure the input field is empty and focused.\n        input.value = \"\";\n        input.focus();\n\n        // Display feedback based on the user's last guess. The input\n        // placeholder will show because we made the input field empty.\n        if (this.guess === null) {\n            input.placeholder = \"Type your guess and hit Enter\";\n        } else if (this.guess < this.secret) {\n            input.placeholder = `${this.guess} is too low. Guess again`;\n        } else if (this.guess > this.secret) {\n            input.placeholder = `${this.guess} is too high. Guess again`;\n        } else {\n            input.placeholder = document.title = `${this.guess} is correct!`;\n            heading.textContent = `You win in ${this.numGuesses} guesses!`;\n            playagain.hidden = false;\n        }\n    }\n\n    // Update the state of the game based on what the user guessed.\n    // Returns true if the state was updated, and false otherwise.\n    updateForGuess(guess) {\n        // If it is a number and is in the right range\n        if ((guess > this.low) && (guess < this.high)) {\n            // Update state object based on this guess\n            if (guess < this.secret) this.low = guess;\n            else if (guess > this.secret) this.high = guess;\n            this.guess = guess;\n            this.numGuesses++;\n            return true;\n        }\n        else { // An invalid guess: notify user but don't update state\n            alert(`Please enter a number greater than ${\n                   this.low} and less than ${this.high}`);\n            return false;\n        }\n    }\n}\n\n// With the GameState class defined, making the game work is just a matter\n// of initializing, updating, saving and rendering the state object at\n// the appropriate times.\n\n// When we are first loaded, we try get the state of the game from the URL\n// and if that fails we instead begin a new game. So if the user bookmarks a\n// game that game can be restored from the URL. But if we load a page with\n// no query parameters we'll just get a new game.\nlet gamestate = GameState.fromURL(window.location) || GameState.newGame();\n\n// Save this initial state of the game into the browser history, but use\n// replaceState instead of pushState() for this initial page\nhistory.replaceState(gamestate, \"\", gamestate.toURL());\n\n// Display this initial state\ngamestate.render();\n\n// When the user guesses, update the state of the game based on their guess\n// then save the new state to browser history and render the new state\ndocument.querySelector(\"#input\").onchange = (event) => {\n    if (gamestate.updateForGuess(parseInt(event.target.value))) {\n        history.pushState(gamestate, \"\", gamestate.toURL());\n    }\n    gamestate.render();\n};\n\n// If the user goes back or forward in history, we'll get a popstate event\n// on the window object with a copy of the state object we saved with\n// pushState. When that happens, render the new state.\nwindow.onpopstate = (event) => {\n    gamestate = GameState.fromStateObject(event.state); // Restore the state\n    gamestate.render();                                 // and display it\n};\n</script>\n</body></html>\n```\n## 15.11 Networking\nEvery time you load a web page, the browser makes network requests—using the HTTP and HTTPS protocols—for an HTML file as well as the images, fonts, scripts, and stylesheets that the file depends on. But in addition to being able to make network requests in response to user actions, web browsers also expose JavaScript APIs for networking as well.\n\nThis section covers three network APIs:\n\nThe fetch() method defines a Promise-based API for making HTTP and HTTPS requests. The fetch() API makes basic GET requests simple but has a comprehensive feature set that also supports just about any possible HTTP use case.\n\nThe Server-Sent Events (or SSE) API is a convenient, event-based interface to HTTP “long polling” techniques where the web server holds the network connection open so that it can send data to the client whenever it wants.\n\nWebSockets is a networking protocol that is not HTTP but is designed to interoperate with HTTP. It defines an asynchronous message-passing API where clients and servers can send and receive messages from each other in a way that is similar to TCP network sockets.\n\n### 15.11.1 fetch()\nFor basic HTTP requests, using fetch() is a three-step process:\n\nCall fetch(), passing the URL whose content you want to retrieve.\n\nGet the response object that is asynchronously returned by step 1 when the HTTP response begins to arrive and call a method of this response object to ask for the body of the response.\n\nGet the body object that is asynchronously returned by step 2 and process it however you want.\n\nThe fetch() API is completely Promise-based, and there are two asynchronous steps here, so you typically expect two then() calls or two await expressions when using fetch(). (And if you’ve forgotten what those are, you may want to reread Chapter 13 before continuing with this section.)\n\nHere’s what a fetch() request looks like if you are using then() and expect the server’s response to your request to be JSON-formatted:\n```js\nfetch(\"/api/users/current\")            // Make an HTTP (or HTTPS) GET request\n    .then(response => response.json()) // Parse its body as a JSON object\n    .then(currentUser => {             // Then process that parsed object\n        displayUserInfo(currentUser);\n    });\n```\nHere’s a similar request made using the async and await keywords to an API that returns a plain string rather than a JSON object:\n```js\nasync function isServiceReady() {\n    let response = await fetch(\"/api/service/status\");\n    let body = await response.text();\n    return body === \"ready\";\n}\n```\nIf you understand these two code examples, then you know 80% of what you need to know to use the fetch() API. The subsections that follow will demonstrate how to make requests and receive responses that are somewhat more complicated than those shown here.\n\nGOODBYE XMLHTTPREQUEST\nThe fetch() API replaces the baroque and misleadingly named XMLHttpRequest API (which has nothing to do with XML). You may still see XHR (as it is often abbreviated) in existing code, but there is no reason today to use it in new code, and it is not documented in this chapter. There is one example of XMLHttpRequest in this book, however, and you can refer to §13.1.3 if you’d like to see an example of old-style JavaScript networking.\n\nHTTP STATUS CODES, RESPONSE HEADERS, AND NETWORK ERRORS\nThe three-step fetch() process shown in §15.11.1 elides all error-handling code. Here’s a more realistic version:\n```js\nfetch(\"/api/users/current\")   // Make an HTTP (or HTTPS) GET request.\n    .then(response => {       // When we get a response, first check it\n        if (response.ok &&    // for a success code and the expected type.\n            response.headers.get(\"Content-Type\") === \"application/json\") {\n            return response.json(); // Return a Promise for the body.\n        } else {\n            throw new Error(        // Or throw an error.\n                `Unexpected response status ${response.status} or content type`\n            );\n        }\n    })\n    .then(currentUser => {    // When the response.json() Promise resolves\n        displayUserInfo(currentUser); // do something with the parsed body.\n    })\n    .catch(error => {         // Or if anything went wrong, just log the error.\n        // If the user's browser is offline, fetch() itself will reject.\n        // If the server returns a bad response then we throw an error above.\n        console.log(\"Error while fetching current user:\", error);\n    });\n```\nThe Promise returned by fetch() resolves to a Response object. The status property of this object is the HTTP status code, such as 200 for successful requests or 404 for “Not Found” responses. (statusText gives the standard English text that goes along with the numeric status code.) Conveniently, the ok property of a Response is true if status is 200 or any code between 200 and 299 and is false for any other code.\n\nfetch() resolves its Promise when the server’s response starts to arrive, as soon as the HTTP status and response headers are available, but typically before the full response body has arrived. Even though the body is not available yet, you can examine the headers in this second step of the fetch process. The headers property of a Response object is a Headers object. Use its has() method to test for the presence of a header, or use its get() method to get the value of a header. HTTP header names are case-insensitive, so you can pass lowercase or mixed-case header names to these functions.\n\nThe Headers object is also iterable if you ever need to do that:\n```js\nfetch(url).then(response => {\n    for(let [name,value] of response.headers) {\n        console.log(`${name}: ${value}`);\n    }\n});\n```\nIf a web server responds to your fetch() request, then the Promise that was returned will be fulfilled with a Response object, even if the server’s response was a 404 Not Found error or a 500 Internal Server Error. fetch() only rejects the Promise it returns if it cannot contact the web server at all. This can happen if the user’s computer is offline, the server is unresponsive, or the URL specifies a hostname that does not exist. Because these things can happen on any network request, it is always a good idea to include a .catch() clause any time you make a fetch() call.\n\nSETTING REQUEST PARAMETERS\nSometimes you want to pass extra parameters along with the URL when you make a request. This can be done by adding name/value pairs at the end of a URL after a ?. The URL and URLSearchParams classes (which were covered in §11.9) make it easy to construct URLs in this form, and the fetch() function accepts URL objects as its first argument, so you can include request parameters in a fetch() request like this:\n```js\nasync function search(term) {\n    let url = new URL(\"/api/search\");\n    url.searchParams.set(\"q\", term);\n    let response = await fetch(url);\n    if (!response.ok) throw new Error(response.statusText);\n    let resultsArray = await response.json();\n    return resultsArray;\n}\n```\nSETTING REQUEST HEADERS\nSometimes you need to set headers in your fetch() requests. If you’re making web API requests that require credentials, for example, then you may need to include an Authorization header that contains those credentials. In order to do this, you can use the two-argument version of fetch(). As before, the first argument is a string or URL object that specifies the URL to fetch. The second argument is an object that can provide additional options, including request headers:\n```js\nlet authHeaders = new Headers();\n// Don't use Basic auth unless it is over an HTTPS connection.\nauthHeaders.set(\"Authorization\",\n                `Basic ${btoa(`${username}:${password}`)}`);\nfetch(\"/api/users/\", { headers: authHeaders })\n    .then(response => response.json())             // Error handling omitted...\n    .then(usersList => displayAllUsers(usersList));\n```\nThere are a number of other options that can be specified in the second argument to fetch(), and we’ll see it again later. An alternative to passing two arguments to fetch() is to instead pass the same two arguments to the Request() constructor and then pass the resulting Request object to fetch():\n```js\nlet request = new Request(url, { headers });\nfetch(request).then(response => ...);\n```\nPARSING RESPONSE BODIES\nIn the three-step fetch() process that we’ve demonstrated, the second step ends by calling the json() or text() methods of the Response object and returning the Promise object that those methods return. Then, the third step begins when that Promise resolves with the body of the response parsed as a JSON object or simply as a string of text.\n\nThese are probably the two most common scenarios, but they are not the only ways to obtain the body of a web server’s response. In addition to json() and text(), the Response object also has these methods:\n\narrayBuffer()\nThis method returns a Promise that resolves to an ArrayBuffer. This is useful when the response contains binary data. You can use the ArrayBuffer to create a typed array (§11.2) or a DataView object (§11.2.5) from which you can read the binary data.\n\nblob()\nThis method returns a Promise that resolves to a Blob object. Blobs are not covered in any detail in this book, but the name stands for “Binary Large Object,” and they are useful when you expect large amounts of binary data. If you ask for the body of the response as a Blob, the browser implementation may stream the response data to a temporary file and then return a Blob object that represents that temporary file. Blob objects, therefore, do not allow random access to the response body the way that an ArrayBuffer does. Once you have a Blob, you can create a URL that refers to it with URL.createObjectURL(), or you can use the event-based FileReader API to asynchronously obtain the content of the Blob as a string or an ArrayBuffer. At the time of this writing, some browsers also define Promise-based text() and arrayBuffer() methods that give a more direct route for obtaining the content of a Blob.\n\nformData()\nThis method returns a Promise that resolves to a FormData object. You should use this method if you expect the body of the Response to be encoded in “multipart/form-data” format. This format is common in POST requests made to a server, but uncommon in server responses, so this method is not frequently used.\n\nSTREAMING RESPONSE BODIES\nIn addition to the five response methods that asynchronously return some form of the complete response body to you, there is also an option to stream the response body, which is useful if there is some kind of processing you can do on the chunks of the response body as they arrive over the network. But streaming the response is also useful if you want to display a progress bar so that the user can see the progress of the download.\n\nThe body property of a Response object is a ReadableStream object. If you have already called a response method like text() or json() that reads, parses, and returns the body, then bodyUsed will be true to indicate that the body stream has already been read. If bodyUsed is false, however, then the stream has not yet been read. In this case, you can call getReader() on response.body to obtain a stream reader object, then use the read() method of this reader object to asynchronously read chunks of text from the stream. The read() method returns a Promise that resolves to an object with done and value properties. done will be true if the entire body has been read or if the stream was closed. And value will either be the next chunk, as a Uint8Array, or undefined if there are no more chunks.\n\nThis streaming API is relatively straightforward if you use async and await but is surprisingly complex if you attempt to use it with raw Promises. Example 15-10 demonstrates the API by defining a streamBody() function. Suppose you wanted to download a large JSON file and report download progress to the user. You can’t do that with the json() method of the Response object, but you could do it with the streamBody() function, like this (assuming that an updateProgress() function is defined to set the value attribute on an HTML `<progress>` element):\n```js\nfetch('big.json')\n    .then(response => streamBody(response, updateProgress))\n    .then(bodyText => JSON.parse(bodyText))\n    .then(handleBigJSONObject);\n```\nThe streamBody() function can be implemented as shown in Example 15-10.\n\nExample 15-10. Streaming the response body from a fetch() request\n```js\n/**\n * An asynchronous function for streaming the body of a Response object\n * obtained from a fetch() request. Pass the Response object as the first\n * argument followed by two optional callbacks.\n *\n * If you specify a function as the second argument, that reportProgress\n * callback will be called once for each chunk that is received. The first\n * argument passed is the total number of bytes received so far. The second\n * argument is a number between 0 and 1 specifying how complete the download\n * is. If the Response object has no \"Content-Length\" header, however, then\n * this second argument will always be NaN.\n *\n * If you want to process the data in chunks as they arrive, specify a\n * function as the third argument. The chunks will be passed, as Uint8Array\n * objects, to this processChunk callback.\n *\n * streamBody() returns a Promise that resolves to a string. If a processChunk\n * callback was supplied then this string is the concatenation of the values\n * returned by that callback. Otherwise the string is the concatenation of\n * the chunk values converted to UTF-8 strings.\n */\nasync function streamBody(response, reportProgress, processChunk) {\n    // How many bytes are we expecting, or NaN if no header\n    let expectedBytes = parseInt(response.headers.get(\"Content-Length\"));\n    let bytesRead = 0;                       // How many bytes received so far\n    let reader = response.body.getReader();  // Read bytes with this function\n    let decoder = new TextDecoder(\"utf-8\");  // For converting bytes to text\n    let body = \"\";                           // Text read so far\n\n    while(true) {                                 // Loop until we exit below\n        let {done, value} = await reader.read();  // Read a chunk\n\n        if (value) {                              // If we got a byte array:\n            if (processChunk) {                   // Process the bytes if\n                let processed = processChunk(value);  // a callback was passed.\n                if (processed) {\n                    body += processed;\n                }\n            } else {                              // Otherwise, convert bytes\n                body += decoder.decode(value, {stream: true}); // to text.\n            }\n\n            if (reportProgress) {                 // If a progress callback was\n                bytesRead += value.length;        // passed, then call it\n                reportProgress(bytesRead, bytesRead / expectedBytes);\n            }\n        }\n        if (done) {                               // If this is the last chunk,\n            break;                                // exit the loop\n        }\n    }\n\n    return body;   // Return the body text we accumulated\n}\n```\nThis streaming API is new at the time of this writing and is expected to evolve. In particular, there are plans to make ReadableStream objects asynchronously iterable so that they can be used with for/await loops (§13.4.1).\n\nSPECIFYING THE REQUEST METHOD AND REQUEST BODY\nIn each of the fetch() examples shown so far, we have made an HTTP (or HTTPS) GET request. If you want to use a different request method (such as POST, PUT, or DELETE), simply use the two-argument version of fetch(), passing an Options object with a method parameter:\n```js\nfetch(url, { method: \"POST\" }).then(r => r.json()).then(handleResponse);\n```\nPOST and PUT requests typically have a request body containing data to be sent to the server. As long as the method property is not set to \"GET\" or \"HEAD\" (which do not support request bodies), you can specify a request body by setting the body property of the Options object:\n```js\nfetch(url, {\n    method: \"POST\",\n    body: \"hello world\"\n})\n```\nWhen you specify a request body, the browser automatically adds an appropriate “Content-Length” header to the request. When the body is a string, as in the preceding example, the browser defaults the “Content-Type” header to “text/plain;charset=UTF-8.” You may need to override this default if you specify a string body of some more specific type such as “text/html” or “application/json”:\n```js\nfetch(url, {\n    method: \"POST\",\n    headers: new Headers({\"Content-Type\": \"application/json\"}),\n    body: JSON.stringify(requestBody)\n})\n```\nThe body property of the fetch() options object does not have to be a string. If you have binary data in a typed array or a DataView object or an ArrayBuffer, you can set the body property to that value and specify an appropriate “Content-Type” header. If you have binary data in Blob form, you can simply set body to the Blob. Blobs have a type property that specifies their content type, and the value of this property is used as the default value of the “Content-Type” header.\n\nWith POST requests, is it somewhat common to pass a set of name/value parameters in the request body (instead of encoding them into the query portion of the URL). There are two ways to do this:\n\nYou can specify your parameter names and values with URLSearchParams (which we saw earlier in this section, and which is documented in §11.9) and then pass the URLSearchParams object as the value of the body property. If you do this, the body will be set to a string that looks like the query portion of a URL, and the “Content-Type” header will be automatically set to “application/x-www-form-urlencoded;charset=UTF-8.”\n\nIf instead you specify your parameter names and values with a FormData object, the body will use a more verbose multipart encoding and “Content-Type” will be set to “multipart/form-data; boundary=…” with a unique boundary string that matches the body. Using a FormData object is particularly useful when the values you want to upload are long, or are File or Blob objects that may each have its own “Content-Type.” FormData objects can be created and initialized with values by passing a `<form>` element to the FormData() constructor. But you can also create “multipart/form-data” request bodies by invoking the FormData() constructor with no arguments and initializing the name/value pairs it represents with the set() and append() methods.\n\nFILE UPLOAD WITH FETCH()\nUploading files from a user’s computer to a web server is a common task and can be accomplished using a FormData object as the request body. A common way to obtain a File object is to display an `<input type=\"file\">` element on your web page and listen for “change” events on that element. When a “change” event occurs, the files array of the input element should contain at least one File object. File objects are also available through the HTML drag-and-drop API. That API is not covered in this book, but you can get files from the dataTransfer.files array of the event object passed to an event listener for “drop” events.\n\nRemember also that File objects are a kind of Blob, and sometimes it can be useful to upload Blobs. Suppose you’ve written a web application that allows the user to create drawings in a `<canvas>` element. You can upload the user’s drawings as PNG files with code like the following:\n```js\n// The canvas.toBlob() function is callback-based.\n// This is a Promise-based wrapper for it.\nasync function getCanvasBlob(canvas) {\n    return new Promise((resolve, reject) => {\n        canvas.toBlob(resolve);\n    });\n}\n\n// Here is how we upload a PNG file from a canvas\nasync function uploadCanvasImage(canvas) {\n    let pngblob = await getCanvasBlob(canvas);\n    let formdata = new FormData();\n    formdata.set(\"canvasimage\", pngblob);\n    let response = await fetch(\"/upload\", { method: \"POST\", body: formdata });\n    let body = await response.json();\n}\n```\nCROSS-ORIGIN REQUESTS\nMost often, fetch() is used by web applications to request data from their own web server. Requests like these are known as same-origin requests because the URL passed to fetch() has the same origin (protocol plus hostname plus port) as the document that contains the script that is making the request.\n\nFor security reasons, web browsers generally disallow (though there are exceptions for images and scripts) cross-origin network requests. However, Cross-Origin Resource Sharing, or CORS, enables safe cross-origin requests. When fetch() is used with a cross-origin URL, the browser adds an “Origin” header to the request (and does not allow it to be overridden via the headers property) to notify the web server that the request is coming from a document with a different origin. If the server responds to the request with an appropriate “Access-Control-Allow-Origin” header, then the request proceeds. Otherwise, if the server does not explicitly allow the request, then the Promise returned by fetch() is rejected.\n\nABORTING A REQUEST\nSometimes you may want to abort a fetch() request that you have already issued, perhaps because the user clicked a Cancel button or the request is taking too long. The fetch API allows requests to be aborted using the AbortController and AbortSignal classes. (These classes define a generic abort mechanism suitable for use by other APIs as well.)\n\nIf you want to have the option of aborting a fetch() request, then create an AbortController object before starting the request. The signal property of the controller object is an AbortSignal object. Pass this signal object as the value of the signal property of the options object that you pass to fetch(). Having done that, you can call the abort() method of the controller object to abort the request, which will cause any Promise objects related to the fetch request to reject with an exception.\n\nHere is an example of using the AbortController mechanism to enforce a timeout for fetch requests:\n```js\n// This function is like fetch(), but it adds support for a timeout\n// property in the options object and aborts the fetch if it is not complete\n// within the number of milliseconds specified by that property.\nfunction fetchWithTimeout(url, options={}) {\n    if (options.timeout) {  // If the timeout property exists and is nonzero\n        let controller = new AbortController();  // Create a controller\n        options.signal = controller.signal;      // Set the signal property\n        // Start a timer that will send the abort signal after the specified\n        // number of milliseconds have passed. Note that we never cancel\n        // this timer. Calling abort() after the fetch is complete has\n        // no effect.\n        setTimeout(() => { controller.abort(); }, options.timeout);\n    }\n    // Now just perform a normal fetch\n    return fetch(url, options);\n}\n```\nMISCELLANEOUS REQUEST OPTIONS\nWe’ve seen that an Options object can be passed as the second argument to fetch() (or as the second argument to the Request() constructor) to specify the request method, request headers, and request body. It supports a number of other options as well, including these:\n\ncache\nUse this property to override the browser’s default caching behavior. HTTP caching is a complex topic that is beyond the scope of this book, but if you know something about how it works, you can use the following legal values of cache:\n\n\"default\"\nThis value specifies the default caching behavior. Fresh responses in the cache are served directly from the cache, and stale responses are revalidated before being served.\n\n\"no-store\"\nThis value makes the browser ignore its cache. The cache is not checked for matches when the request is made and is not updated when the response arrives.\n\n\"reload\"\nThis value tells the browser to always make a normal network request, ignoring the cache. When the response arrives, however, it is stored in the cache.\n\n\"no-cache\"\nThis (misleadingly named) value tells the browser to not serve fresh values from the cache. Fresh or stale cached values are revalidated before being returned.\n\n\"force-cache\"\nThis value tells the browser to serve responses from the cache even if they are stale.\n\nredirect\nThis property controls how the browser handles redirect responses from the server. The three legal values are:\n\n\"follow\"\nThis is the default value, and it makes the browser follow redirects automatically. If you use this default, the Response objects you get with fetch() should never have a status in the 300 to 399 range.\n\n\"error\"\nThis value makes fetch() reject its returned Promise if the server returns a redirect response.\n\n\"manual\"\nThis value means that you want to manually handle redirect responses, and the Promise returned by fetch() may resolve to a Response object with a status in the 300 to 399 range. In this case, you will have to use the “Location” header of the Response to manually follow the redirection.\n\nreferrer\nYou can set this property to a string that contains a relative URL to specify the value of the HTTP “Referer” header (which is historically misspelled with three Rs instead of four). If you set this property to the empty string, then the “Referer” header will be omitted from the request.\n\n### 15.11.2 Server-Sent Events\nA fundamental feature of the HTTP protocol upon which the web is built is that clients initiate requests and servers respond to those requests. Some web apps find it useful, however, to have their server send them notifications when events occur. This does not come naturally to HTTP, but the technique that has been devised is for the client to make a request to the server, and then neither the client nor the server close the connection. When the server has something to tell the client about, it writes data to the connection but keeps it open. The effect is as if the client makes a network request and the server responds in a slow and bursty way with significant pauses between bursts of activity. Network connections like this don’t usually stay open forever, but if the client detects that the connection has closed, it can simply make another request to reopen the connection.\n\nThis technique for allowing servers to send messages to clients is surprisingly effective (though it can be expensive on the server side because the server must maintain an active connection to all of its clients). Because it is a useful programming pattern, client-side JavaScript supports it with the EventSource API. To create this kind of long-lived request connection to a web server, simply pass a URL to the EventSource() constructor. When the server writes (properly formatted) data to the connection, the EventSource object translates those into events that you can listen for:\n```js\nlet ticker = new EventSource(\"stockprices.php\");\nticker.addEventListener(\"bid\", (event) => {\n    displayNewBid(event.data);\n}\n```\nThe event object associated with a message event has a data property that holds whatever string the server sent as the payload for this event. The event object also has a type property, like all event objects do, that specifies the name of the event. The server determines the type of the events that are generated. If the server omits an event name in the data it writes, then the event type defaults to “message.”\n\nThe Server-Sent Event protocol is straightforward. The client initiates a connection to the server (when it creates the EventSource object), and the server keeps this connection open. When an event occurs, the server writes lines of text to the connection. An event going over the wire might look like this, if the comments were omitted:\n```js\nevent: bid  // sets the type of the event object\ndata: GOOG  // sets the data property\ndata: 999   // appends a newline and more data\n            // a blank line marks the end of the event\n```\nThere are some additional details to the protocol that allow events to be given IDs and allow a reconnecting client to tell the server what the ID of the last event it received was, so that a server can resend any events it missed. Those details are invisible on the client side, however, and are not discussed here.\n\nOne obvious application for Server-Sent Events is for multiuser collaborations like online chat. A chat client might use fetch() to post messages to the chat room and subscribe to the stream of chatter with an EventSource object. Example 15-11 demonstrates how easy it is to write a chat client like this with EventSource.\n\nExample 15-11. A simple chat client using EventSource\n```html\n<html>\n<head><title>SSE Chat</title></head>\n<body>\n<!-- The chat UI is just a single text input field -->\n<!-- New chat messages will be inserted before this input field -->\n<input id=\"input\" style=\"width:100%; padding:10px; border:solid black 2px\"/>\n<script>\n// Take care of some UI details\nlet nick = prompt(\"Enter your nickname\");     // Get user's nickname\nlet input = document.getElementById(\"input\"); // Find the input field\ninput.focus();                                // Set keyboard focus\n\n// Register for notification of new messages using EventSource\nlet chat = new EventSource(\"/chat\");\nchat.addEventListener(\"chat\", event => {  // When a chat message arrives\n    let div = document.createElement(\"div\");  // Create a <div>\n    div.append(event.data);                   // Add text from the message\n    input.before(div);                        // And add div before input\n    input.scrollIntoView();                   // Ensure input elt is visible\n});\n\n// Post the user's messages to the server using fetch\ninput.addEventListener(\"change\", ()=>{  // When the user strikes return\n    fetch(\"/chat\", {                    // Start an HTTP request to this url.\n        method: \"POST\",                 // Make it a POST request with body\n        body: nick + \": \" + input.value // set to the user's nick and input.\n    })\n    .catch(e => console.error);         // Ignore response, but log any errors.\n    input.value = \"\";                   // Clear the input\n});\n</script>\n</body>\n</html>\n```\nThe server-side code for this chat program is not much more complicated than the client-side code. Example 15-12 is a simple Node HTTP server. When a client requests the root URL “/”, it sends the chat client code shown in Example 15-11. When a client makes a GET request for the URL “/chat”, it saves the response object and keeps that connection open. And when a client makes a POST request to “/chat”, it uses the body of the request as a chat message and writes it, using the “text/event-stream” format to each of the saved response objects. The server code listens on port 8080, so after running it with Node, point your browser to http://localhost:8080 to connect and begin chatting with yourself.\n\nExample 15-12. A Server-Sent Events chat server\n```js\n// This is server-side JavaScript, intended to be run with NodeJS.\n// It implements a very simple, completely anonymous chat room.\n// POST new messages to /chat, or GET a text/event-stream of messages\n// from the same URL. Making a GET request to / returns a simple HTML file\n// that contains the client-side chat UI.\nconst http = require(\"http\");\nconst fs = require(\"fs\");\nconst url = require(\"url\");\n\n// The HTML file for the chat client. Used below.\nconst clientHTML = fs.readFileSync(\"chatClient.html\");\n\n// An array of ServerResponse objects that we're going to send events to\nlet clients = [];\n\n// Create a new server, and listen on port 8080.\n// Connect to http://localhost:8080/ to use it.\nlet server = new http.Server();\nserver.listen(8080);\n\n// When the server gets a new request, run this function\nserver.on(\"request\", (request, response) => {\n    // Parse the requested URL\n    let pathname = url.parse(request.url).pathname;\n\n    // If the request was for \"/\", send the client-side chat UI.\n    if (pathname === \"/\") {  // A request for the chat UI\n        response.writeHead(200, {\"Content-Type\": \"text/html\"}).end(clientHTML);\n    }\n    // Otherwise send a 404 error for any path other than \"/chat\" or for\n    // any method other than \"GET\" and \"POST\"\n    else if (pathname !== \"/chat\" ||\n             (request.method !== \"GET\" && request.method !== \"POST\")) {\n        response.writeHead(404).end();\n    }\n    // If the /chat request was a GET, then a client is connecting.\n    else if (request.method === \"GET\") {\n        acceptNewClient(request, response);\n    }\n    // Otherwise the /chat request is a POST of a new message\n    else {\n        broadcastNewMessage(request, response);\n    }\n});\n\n// This handles GET requests for the /chat endpoint which are generated when\n// the client creates a new EventSource object (or when the EventSource\n// reconnects automatically).\nfunction acceptNewClient(request, response) {\n    // Remember the response object so we can send future messages to it\n    clients.push(response);\n\n    // If the client closes the connection, remove the corresponding\n    // response object from the array of active clients\n    request.connection.on(\"end\", () => {\n        clients.splice(clients.indexOf(response), 1);\n        response.end();\n    });\n\n    // Set headers and send an initial chat event to just this one client\n    response.writeHead(200, {\n        \"Content-Type\": \"text/event-stream\",\n        \"Connection\": \"keep-alive\",\n        \"Cache-Control\": \"no-cache\"\n    });\n    response.write(\"event: chat\\ndata: Connected\\n\\n\");\n\n    // Note that we intentionally do not call response.end() here.\n    // Keeping the connection open is what makes Server-Sent Events work.\n}\n\n// This function is called in response to POST requests to the /chat endpoint\n// which clients send when users type a new message.\nasync function broadcastNewMessage(request, response) {\n    // First, read the body of the request to get the user's message\n    request.setEncoding(\"utf8\");\n    let body = \"\";\n    for await (let chunk of request) {\n        body += chunk;\n    }\n\n    // Once we've read the body send an empty response and close the connection\n    response.writeHead(200).end();\n\n    // Format the message in text/event-stream format, prefixing each\n    // line with \"data: \"\n    let message = \"data: \" + body.replace(\"\\n\", \"\\ndata: \");\n\n    // Give the message data a prefix that defines it as a \"chat\" event\n    // and give it a double newline suffix that marks the end of the event.\n    let event = `event: chat\\n${message}\\n\\n`;\n\n    // Now send this event to all listening clients\n    clients.forEach(client => client.write(event));\n}\n```\n### 15.11.3 WebSockets\nThe WebSocket API is a simple interface to a complex and powerful network protocol. WebSockets allow JavaScript code in the browser to easily exchange text and binary messages with a server. As with Server-Sent Events, the client must establish the connection, but once the connection is established, the server can asynchronously send messages to the client. Unlike SSE, binary messages are supported, and messages can be sent in both directions, not just from server to client.\n\nThe network protocol that enables WebSockets is a kind of extension to HTTP. Although the WebSocket API is reminiscent of low-level network sockets, connection endpoints are not identified by IP address and port. Instead, when you want to connect to a service using the WebSocket protocol, you specify the service with a URL, just as you would for a web service. WebSocket URLs begin with wss:// instead of https://, however. (Browsers typically restrict WebSockets to only work in pages loaded over secure https:// connections).\n\nTo establish a WebSocket connection, the browser first establishes an HTTP connection and sends the server an Upgrade: websocket header requesting that the connection be switched from the HTTP protocol to the WebSocket protocol. What this means is that in order to use WebSockets in your client-side JavaScript, you will need to be working with a web server that also speaks the WebSocket protocol, and you will need to have server-side code written to send and receive data using that protocol. If your server is set up that way, then this section will explain everything you need to know to handle the client-side end of the connection. If your server does not support the WebSocket protocol, consider using Server-Sent Events (§15.11.2) instead.\n\nCREATING, CONNECTING, AND DISCONNECTING WEBSOCKETS\nIf you want to communicate with a WebSocket-enabled server, create a WebSocket object, specifying the wss:// URL that identifies the server and service you want to use:\n```js\nlet socket = new WebSocket(\"wss://example.com/stockticker\");\n```\nWhen you create a WebSocket, the connection process begins automatically. But a newly created WebSocket will not be connected when it is first returned.\n\nThe readyState property of the socket specifies what state the connection is in. This property can have the following values:\n\nWebSocket.CONNECTING\nThis WebSocket is connecting.\n\nWebSocket.OPEN\nThis WebSocket is connected and ready for communication.\n\nWebSocket.CLOSING\nThis WebSocket connection is being closed.\n\nWebSocket.CLOSED\nThis WebSocket has been closed; no further communication is possible. This state can also occur when the initial connection attempt fails.\n\nWhen a WebSocket transitions from the CONNECTING to the OPEN state, it fires an “open” event, and you can listen for this event by setting the onopen property of the WebSocket or by calling addEventListener() on that object.\n\nIf a protocol or other error occurs for a WebSocket connection, the WebSocket object fires an “error” event. You can set onerror to define a handler, or, alternatively, use addEventListener().\n\nWhen you are done with a WebSocket, you can close the connection by calling the close() method of the WebSocket object. When a WebSocket changes to the CLOSED state, it fires a “close” event, and you can set the onclose property to listen for this event.\n\nSENDING MESSAGES OVER A WEBSOCKET\nTo send a message to the server on the other end of a WebSocket connection, simply invoke the send() method of the WebSocket object. send() expects a single message argument, which can be a string, Blob, ArrayBuffer, typed array, or DataView object.\n\nThe send() method buffers the specified message to be transmitted and returns before the message is actually sent. The bufferedAmount property of the WebSocket object specifies the number of bytes that are buffered but not yet sent. (Surprisingly, WebSockets do not fire any event when this value reaches 0.)\n\nRECEIVING MESSAGES FROM A WEBSOCKET\nTo receive messages from a server over a WebSocket, register an event handler for “message” events, either by setting the onmessage property of the WebSocket object, or by calling addEventListener(). The object associated with a “message” event is a MessageEvent instance with a data property that contains the server’s message. If the server sent UTF-8 encoded text, then event.data will be a string containing that text.\n\nIf the server sends a message that consists of binary data instead of text, then the data property will (by default) be a Blob object representing that data. If you prefer to receive binary messages as ArrayBuffers instead of Blobs, set the binaryType property of the WebSocket object to the string “arraybuffer.”\n\nThere are a number of Web APIs that use MessageEvent objects for exchanging messages. Some of these APIs use the structured clone algorithm (see “The Structured Clone Algorithm”) to allow complex data structures as the message payload. WebSockets is not one of those APIs: messages exchanged over a WebSocket are either a single string of Unicode characters or a single string of bytes (represented as a Blob or an ArrayBuffer).\n\nPROTOCOL NEGOTIATION\nThe WebSocket protocol enables the exchange of text and binary messages, but says nothing at all about the structure or meaning of those messages. Applications that use WebSockets must build their own communication protocol on top of this simple message-exchange mechanism. The use of wss:// URLs helps with this: each URL will typically have its own rules for how messages are to be exchanged. If you write code to connect to wss://example.com/stockticker, then you probably know that you will be receiving messages about stock prices.\n\nProtocols tend to evolve, however. If a hypothetical stock quotation protocol is updated, you can define a new URL and connect to the updated service as wss://example.com/stockticker/v2. URL-based versioning is not always sufficient, however. With complex protocols that have evolved over time, you may end up with deployed servers that support multiple versions of the protocol and deployed clients that support a different set of protocol versions.\n\nAnticipating this situation, the WebSocket protocol and API include an application-level protocol negotiation feature. When you call the WebSocket() constructor, the wss:// URL is the first argument, but you can also pass an array of strings as the second argument. If you do this, you are specifying a list of application protocols that you know how to handle and asking the server to pick one. During the connection process, the server will choose one of the protocols (or will fail with an error if it does not support any of the client’s options). Once the connection has been established, the protocol property of the WebSocket object specifies which protocol version the server chose.\n\n## 15.12 Storage\nWeb applications can use browser APIs to store data locally on the user’s computer. This client-side storage serves to give the web browser a memory. Web apps can store user preferences, for example, or even store their complete state, so that they can resume exactly where you left off at the end of your last visit. Client-side storage is segregated by origin, so pages from one site can’t read the data stored by pages from another site. But two pages from the same site can share storage and use it as a communication mechanism. Data input in a form on one page can be displayed in a table on another page, for example. Web applications can choose the lifetime of the data they store: data can be stored temporarily so that it is retained only until the window closes or the browser exits, or it can be saved on the user’s computer and stored permanently so that it is available months or years later.\n\nThere are a number of forms of client-side storage:\n\nWeb Storage\nThe Web Storage API consists of the localStorage and sessionStorage objects, which are essentially persistent objects that map string keys to string values. Web Storage is very easy to use and is suitable for storing large (but not huge) amounts of data.\n\nCookies\nCookies are an old client-side storage mechanism that was designed for use by server-side scripts. An awkward JavaScript API makes cookies scriptable on the client side, but they’re hard to use and suitable only for storing small amounts of textual data. Also, any data stored as cookies is always transmitted to the server with every HTTP request, even if the data is only of interest to the client.\n\nIndexedDB\nIndexedDB is an asynchronous API to an object database that supports indexing.\n\nSTORAGE, SECURITY, AND PRIVACY\nWeb browsers often offer to remember web passwords for you, and they store them safely in encrypted form on the device. But none of the forms of client-side data storage described in this chapter involve encryption: you should assume that anything your web applications save resides on the user’s device in unencrypted form. Stored data is therefore accessible to curious users who share access to the device and to malicious software (such as spyware) that exists on the device. For this reason, no form of client-side storage should ever be used for passwords, financial account numbers, or other similarly sensitive information.\n\n### 15.12.1 localStorage and sessionStorage\nThe localStorage and sessionStorage properties of the Window object refer to Storage objects. A Storage object behaves much like a regular JavaScript object, except that:\n\nThe property values of Storage objects must be strings.\n\nThe properties stored in a Storage object persist. If you set a property of the localStorage object and then the user reloads the page, the value you saved in that property is still available to your program.\n\nYou can use the localStorage object like this, for example:\n```js\nlet name = localStorage.username;         // Query a stored value.\nif (!name) {\n    name = prompt(\"What is your name?\");  // Ask the user a question.\n    localStorage.username = name;         // Store the user's response.\n}\n```\nYou can use the delete operator to remove properties from localStorage and sessionStorage, and you can use a for/in loop or Object.keys() to enumerate the properties of a Storage object. If you want to remove all properties of a storage object, call the clear() method:\n\nlocalStorage.clear();\nStorage objects also define getItem(), setItem(), and deleteItem() methods, which you can use instead of direct property access and the delete operator if you want to.\n\nKeep in mind that the properties of Storage objects can only store strings. If you want to store and retrieve other kinds of data, you’ll have to encode and decode it yourself.\n\nFor example:\n```js\n// If you store a number, it is automatically converted to a string.\n// Don't forget to parse it when retrieving it from storage.\nlocalStorage.x = 10;\nlet x = parseInt(localStorage.x);\n\n// Convert a Date to a string when setting, and parse it when getting\nlocalStorage.lastRead = (new Date()).toUTCString();\nlet lastRead = new Date(Date.parse(localStorage.lastRead));\n\n// JSON makes a convenient encoding for any primitive or data structure\nlocalStorage.data = JSON.stringify(data);  // Encode and store\nlet data = JSON.parse(localStorage.data);  // Retrieve and decode.\n```\nSTORAGE LIFETIME AND SCOPE\nThe difference between localStorage and sessionStorage involves the lifetime and scope of the storage. Data stored through localStorage is permanent: it does not expire and remains stored on the user’s device until a web app deletes it or the user asks the browser (through some browser-specific UI) to delete it.\n\nlocalStorage is scoped to the document origin. As explained in “The same-origin policy”, the origin of a document is defined by its protocol, hostname, and port. All documents with the same origin share the same localStorage data (regardless of the origin of the scripts that actually access localStorage). They can read each other’s data, and they can overwrite each other’s data. But documents with different origins can never read or overwrite each other’s data (even if they’re both running a script from the same third-party server).\n\nNote that localStorage is also scoped by browser implementation. If you visit a site using Firefox and then visit again using Chrome (for example), any data stored during the first visit will not be accessible during the second visit.\n\nData stored through sessionStorage has a different lifetime than data stored through localStorage: it has the same lifetime as the top-level window or browser tab in which the script that stored it is running. When the window or tab is permanently closed, any data stored through sessionStorage is deleted. (Note, however, that modern browsers have the ability to reopen recently closed tabs and restore the last browsing session, so the lifetime of these tabs and their associated sessionStorage may be longer than it seems.)\n\nLike localStorage, sessionStorage is scoped to the document origin so that documents with different origins will never share sessionStorage. But sessionStorage is also scoped on a per-window basis. If a user has two browser tabs displaying documents from the same origin, those two tabs have separate sessionStorage data: the scripts running in one tab cannot read or overwrite the data written by scripts in the other tab, even if both tabs are visiting exactly the same page and are running exactly the same scripts.\n\nSTORAGE EVENTS\nWhenever the data stored in localStorage changes, the browser triggers a “storage” event on any other Window objects to which that data is visible (but not on the window that made the change). If a browser has two tabs open to pages with the same origin, and one of those pages stores a value in localStorage, the other tab will receive a “storage” event.\n\nRegister a handler for “storage” events either by setting window.onstorage or by calling window.addEventListener() with event type “storage”.\n\nThe event object associated with a “storage” event has some important properties:\n\nkey\nThe name or key of the item that was set or removed. If the clear() method was called, this property will be null.\n\nnewValue\nHolds the new value of the item, if there is one. If removeItem() was called, this property will not be present.\n\noldValue\nHolds the old value of an existing item that changed or was deleted. If a new property (with no old value) is added, then this property will not be present in the event object.\n\nstorageArea\nThe Storage object that changed. This is usually the localStorage object.\n\nurl\nThe URL (as a string) of the document whose script made this storage change.\n\nNote that localStorage and the “storage” event can serve as a broadcast mechanism by which a browser sends a message to all windows that are currently visiting the same website. If a user requests that a website stop performing animations, for example, the site might store that preference in localStorage so that it can honor it in future visits. And by storing the preference, it generates an event that allows other windows displaying the same site to honor the request as well.\n\nAs another example, imagine a web-based image-editing application that allows the user to display tool palettes in separate windows. When the user selects a tool, the application uses localStorage to save the current state and to generate a notification to other windows that a new tool has been selected.\n\n### 15.12.2 Cookies\nA cookie is a small amount of named data stored by the web browser and associated with a particular web page or website. Cookies were designed for server-side programming, and at the lowest level, they are implemented as an extension to the HTTP protocol. Cookie data is automatically transmitted between the web browser and web server, so server-side scripts can read and write cookie values that are stored on the client. This section demonstrates how client-side scripts can also manipulate cookies using the cookie property of the Document object.\n\nWHY “COOKIE”?\nThe name “cookie” does not have a lot of significance, but it is not used without precedent. In the annals of computing history, the term “cookie” or “magic cookie” has been used to refer to a small chunk of data, particularly a chunk of privileged or secret data, akin to a password, that proves identity or permits access. In JavaScript, cookies are used to save state and can establish a kind of identity for a web browser. Cookies in JavaScript do not use any kind of cryptography, however, and are not secure in any way (although transmitting them across an https: connection helps).\n\nThe API for manipulating cookies is an old and cryptic one. There are no methods involved: cookies are queried, set, and deleted by reading and writing the cookie property of the Document object using specially formatted strings. The lifetime and scope of each cookie can be individually specified with cookie attributes. These attributes are also specified with specially formatted strings set on the same cookie property.\n\nThe subsections that follow explain how to query and set cookie values and attributes.\n\nREADING COOKIES\nWhen you read the document.cookie property, it returns a string that contains all the cookies that apply to the current document. The string is a list of name/value pairs separated from each other by a semicolon and a space. The cookie value is just the value itself and does not include any of the attributes that may be associated with that cookie. (We’ll talk about attributes next.) In order to make use of the document.cookie property, you must typically call the split() method to break it into individual name/value pairs.\n\nOnce you have extracted the value of a cookie from the cookie property, you must interpret that value based on whatever format or encoding was used by the cookie’s creator. You might, for example, pass the cookie value to decodeURIComponent() and then to JSON.parse().\n\nThe code that follows defines a getCookie() function that parses the document.cookie property and returns an object whose properties specify the names and values of the document’s cookies:\n```js\n// Return the document's cookies as a Map object.\n// Assume that cookie values are encoded with encodeURIComponent().\nfunction getCookies() {\n    let cookies = new Map();    // The object we will return\n    let all = document.cookie;  // Get all cookies in one big string\n    let list = all.split(\"; \"); // Split into individual name/value pairs\n    for(let cookie of list) {   // For each cookie in that list\n        if (!cookie.includes(\"=\")) continue; // Skip if there is no = sign\n        let p = cookie.indexOf(\"=\");         // Find the first = sign\n        let name = cookie.substring(0, p);   // Get cookie name\n        let value = cookie.substring(p+1);   // Get cookie value\n        value = decodeURIComponent(value);   // Decode the value\n        cookies.set(name, value);            // Remember cookie name and value\n    }\n    return cookies;\n}\n```\nCOOKIE ATTRIBUTES: LIFETIME AND SCOPE\nIn addition to a name and a value, each cookie has optional attributes that control its lifetime and scope. Before we can describe how to set cookies with JavaScript, we need to explain cookie attributes.\n\nCookies are transient by default; the values they store last for the duration of the web browser session but are lost when the user exits the browser. If you want a cookie to last beyond a single browsing session, you must tell the browser how long (in seconds) you would like it to retain the cookie by specifying a max-age attribute. If you specify a lifetime, the browser will store cookies in a file and delete them only once they expire.\n\nCookie visibility is scoped by document origin as localStorage and sessionStorage are, but also by document path. This scope is configurable through cookie attributes path and domain. By default, a cookie is associated with, and accessible to, the web page that created it and any other web pages in the same directory or any subdirectories of that directory. If the web page example.com/catalog/index.html creates a cookie, for example, that cookie is also visible to example.com/catalog/order.html and example.com/catalog/widgets/index.html, but it is not visible to example.com/about.html.\n\nThis default visibility behavior is often exactly what you want. Sometimes, though, you’ll want to use cookie values throughout a website, regardless of which page creates the cookie. For instance, if the user enters their mailing address in a form on one page, you may want to save that address to use as the default the next time they return to the page and also as the default in an entirely unrelated form on another page where they are asked to enter a billing address. To allow this usage, you specify a path for the cookie. Then, any web page from the same web server whose URL begins with the path prefix you specified can share the cookie. For example, if a cookie set by example.com/catalog/widgets/index.html has its path set to “/catalog”, that cookie is also visible to example.com/catalog/order.html. Or, if the path is set to “/”, the cookie is visible to any page in the example.com domain, giving the cookie a scope like that of localStorage.\n\nBy default, cookies are scoped by document origin. Large websites may want cookies to be shared across subdomains, however. For example, the server at order.example.com may need to read cookie values set from catalog.example.com. This is where the domain attribute comes in. If a cookie created by a page on catalog.example.com sets its path attribute to “/” and its domain attribute to “.example.com,” that cookie is available to all web pages on catalog.example.com, orders.example.com, and any other server in the example.com domain. Note that you cannot set the domain of a cookie to a domain other than a parent domain of your server.\n\nThe final cookie attribute is a boolean attribute named secure that specifies how cookie values are transmitted over the network. By default, cookies are insecure, which means that they are transmitted over a normal, insecure HTTP connection. If a cookie is marked secure, however, it is transmitted only when the browser and server are connected via HTTPS or another secure protocol.\n\nCOOKIE LIMITATIONS\nCookies are intended for storage of small amounts of data by server-side scripts, and that data is transferred to the server each time a relevant URL is requested. The standard that defines cookies encourages browser manufacturers to allow unlimited numbers of cookies of unrestricted size but does not require browsers to retain more than 300 cookies total, 20 cookies per web server, or 4 KB of data per cookie (both name and value count toward this 4 KB limit). In practice, browsers allow many more than 300 cookies total, but the 4 KB size limit may still be enforced by some.\n\nSTORING COOKIES\nTo associate a transient cookie value with the current document, simply set the cookie property to a name=value string. For example:\n```js\ndocument.cookie = `version=${encodeURIComponent(document.lastModified)}`;\n```\nThe next time you read the cookie property, the name/value pair you stored is included in the list of cookies for the document. Cookie values cannot include semicolons, commas, or whitespace. For this reason, you may want to use the core JavaScript global function encodeURIComponent() to encode the value before storing it in the cookie. If you do this, you’ll have to use the corresponding decodeURIComponent() function when you read the cookie value.\n\nA cookie written with a simple name/value pair lasts for the current web-browsing session but is lost when the user exits the browser. To create a cookie that can last across browser sessions, specify its lifetime (in seconds) with a max-age attribute. You can do this by setting the cookie property to a string of the form: name=value; max-age=seconds. The following function sets a cookie with an optional max-age attribute:\n```js\n// Store the name/value pair as a cookie, encoding the value with\n// encodeURIComponent() in order to escape semicolons, commas, and spaces.\n// If daysToLive is a number, set the max-age attribute so that the cookie\n// expires after the specified number of days. Pass 0 to delete a cookie.\nfunction setCookie(name, value, daysToLive=null) {\n    let cookie = `${name}=${encodeURIComponent(value)}`;\n    if (daysToLive !== null) {\n        cookie += `; max-age=${daysToLive*60*60*24}`;\n    }\n    document.cookie = cookie;\n}\n```\nSimilarly, you can set the path and domain attributes of a cookie by appending strings of the form ;path=value or ;domain=value to the string that you set on the document.cookie property. To set the secure property, simply append ;secure.\n\nTo change the value of a cookie, set its value again using the same name, path, and domain along with the new value. You can change the lifetime of a cookie when you change its value by specifying a new max-age attribute.\n\nTo delete a cookie, set it again using the same name, path, and domain, specifying an arbitrary (or empty) value, and a max-age attribute of 0.\n\n### 15.12.3 IndexedDB\nWeb application architecture has traditionally featured HTML, CSS, and JavaScript on the client and a database on the server. You may find it surprising, therefore, to learn that the web platform includes a simple object database with a JavaScript API for persistently storing JavaScript objects on the user’s computer and retrieving them as needed.\n\nIndexedDB is an object database, not a relational database, and it is much simpler than databases that support SQL queries. It is more powerful, efficient, and robust than the key/value storage provided by the localStorage, however. Like the localStorage, IndexedDB databases are scoped to the origin of the containing document: two web pages with the same origin can access each other’s data, but web pages from different origins cannot.\n\nEach origin can have any number of IndexedDB databases. Each one has a name that must be unique within the origin. In the IndexedDB API, a database is simply a collection of named object stores. As the name implies, an object store stores objects. Objects are serialized into the object store using the structured clone algorithm (see “The Structured Clone Algorithm”), which means that the objects you store can have properties whose values are Maps, Sets, or typed arrays. Each object must have a key by which it can be sorted and retrieved from the store. Keys must be unique—two objects in the same store may not have the same key—and they must have a natural ordering so that they can be sorted. JavaScript strings, numbers, and Date objects are valid keys. An IndexedDB database can automatically generate a unique key for each object you insert into the database. Often, though, the objects you insert into an object store will already have a property that is suitable for use as a key. In this case, you specify a “key path” for that property when you create the object store. Conceptually, a key path is a value that tells the database how to extract an object’s key from the object.\n\nIn addition to retrieving objects from an object store by their primary key value, you may want to be able to search based on the value of other properties in the object. In order to be able to do this, you can define any number of indexes on the object store. (The ability to index an object store explains the name “IndexedDB.”) Each index defines a secondary key for the stored objects. These indexes are not generally unique, and multiple objects may match a single key value.\n\nIndexedDB provides atomicity guarantees: queries and updates to the database are grouped within a transaction so that they all succeed together or all fail together and never leave the database in an undefined, partially updated state. Transactions in IndexedDB are simpler than in many database APIs; we’ll mention them again later.\n\nConceptually, the IndexedDB API is quite simple. To query or update a database, you first open the database you want (specifying it by name). Next, you create a transaction object and use that object to look up the desired object store within the database, also by name. Finally, you look up an object by calling the get() method of the object store or store a new object by calling put() (or by calling add(), if you want to avoid overwriting existing objects).\n\nIf you want to look up the objects for a range of keys, you create an IDBRange object that specifies the upper and lower bounds of the range and pass it to the getAll() or openCursor() methods of the object store.\n\nIf you want to make a query using a secondary key, you look up the named index of the object store, then call the get(), getAll(), or openCursor() methods of the index object, passing either a single key or an IDBRange object.\n\nThis conceptual simplicity of the IndexedDB API is complicated, however, by the fact that the API is asynchronous (so that web apps can use it without blocking the browser’s main UI thread). IndexedDB was defined before Promises were widely supported, so the API is event-based rather than Promise-based, which means that it does not work with async and await.\n\nCreating transactions and looking up object stores and indexes are synchronous operations. But opening a database, updating an object store, and querying a store or index are all asynchronous operations. These asynchronous methods all immediately return a request object. The browser triggers a success or error event on the request object when the request succeeds or fails, and you can define handlers with the onsuccess and onerror properties. Inside an onsuccess handler, the result of the operation is available as the result property of the request object. Another useful event is the “complete” event dispatched on transaction objects when a transaction has completed successfully.\n\nOne convenient feature of this asynchronous API is that it simplifies transaction management. The IndexedDB API forces you to create a transaction object in order to get the object store on which you can perform queries and updates. In a synchronous API, you would expect to explicitly mark the end of the transaction by calling a commit() method. But with IndexedDB, transactions are automatically committed (if you do not explicitly abort them) when all the onsuccess event handlers have run and there are no more pending asynchronous requests that refer to that transaction.\n\nThere is one more event that is important to the IndexedDB API. When you open a database for the first time, or when you increment the version number of an existing database, IndexedDB fires an “upgradeneeded” event on the request object returned by the indexedDB.open() call. The job of the event handler for “upgradeneeded” events is to define or update the schema for the new database (or the new version of the existing database). For IndexedDB databases, this means creating object stores and defining indexes on those object stores. And in fact, the only time the IndexedDB API allows you to create an object store or an index is in response to an “upgradeneeded” event.\n\nWith this high-level overview of IndexedDB in mind, you should now be able to understand Example 15-13. That example uses IndexedDB to create and query a database that maps US postal codes (zip codes) to US cities. It demonstrates many, but not all, of the basic features of IndexedDB. Example 15-13 is long, but well commented.\n\nExample 15-13. A IndexedDB database of US postal codes\n```js\n// This utility function asynchronously obtains the database object (creating\n// and initializing the DB if necessary) and passes it to the callback.\nfunction withDB(callback) {\n    let request = indexedDB.open(\"zipcodes\", 1); // Request v1 of the database\n    request.onerror = console.error;   // Log any errors\n    request.onsuccess = () => {   // Or call this when done\n        let db = request.result;  // The result of the request is the database\n        callback(db);             // Invoke the callback with the database\n    };\n\n    // If version 1 of the database does not yet exist, then this event\n    // handler will be triggered. This is used to create and initialize\n    // object stores and indexes when the DB is first created or to modify\n    // them when we switch from one version of the DB schema to another.\n    request.onupgradeneeded = () => { initdb(request.result, callback); };\n}\n\n// withDB() calls this function if the database has not been initialized yet.\n// We set up the database and populate it with data, then pass the database to\n// the callback function.\n//\n// Our zip code database includes one object store that holds objects like this:\n//\n//   {\n//     zipcode: \"02134\",\n//     city: \"Allston\",\n//     state: \"MA\",\n//   }\n//\n// We use the \"zipcode\" property as the database key and create an index for\n// the city name.\nfunction initdb(db, callback) {\n    // Create the object store, specifying a name for the store and\n    // an options object that includes the \"key path\" specifying the\n    // property name of the key field for this store.\n    let store = db.createObjectStore(\"zipcodes\", // store name\n                                     { keyPath: \"zipcode\" });\n\n    // Now index the object store by city name as well as by zip code.\n    // With this method the key path string is passed directly as a\n    // required argument rather than as part of an options object.\n    store.createIndex(\"cities\", \"city\");\n\n    // Now get the data we are going to initialize the database with.\n    // The zipcodes.json data file was generated from CC-licensed data from\n    // www.geonames.org: https://download.geonames.org/export/zip/US.zip\n    fetch(\"zipcodes.json\")                  // Make an HTTP GET request\n        .then(response => response.json())  // Parse the body as JSON\n        .then(zipcodes => {                 // Get 40K zip code records\n            // In order to insert zip code data into the database we need a\n            // transaction object. To create our transaction object, we need\n            // to specify which object stores we'll be using (we only have\n            // one) and we need to tell it that we'll be doing writes to the\n            // database, not just reads:\n            let transaction = db.transaction([\"zipcodes\"], \"readwrite\");\n            transaction.onerror = console.error;\n\n            // Get our object store from the transaction\n            let store = transaction.objectStore(\"zipcodes\");\n\n            // The best part about the IndexedDB API is that object stores\n            // are *really* simple. Here's how we add (or update) our records:\n            for(let record of zipcodes) { store.put(record); }\n\n            // When the transaction completes successfully, the database\n            // is initialized and ready for use, so we can call the\n            // callback function that was originally passed to withDB()\n            transaction.oncomplete = () => { callback(db); };\n        });\n}\n\n// Given a zip code, use the IndexedDB API to asynchronously look up the city\n// with that zip code, and pass it to the specified callback, or pass null if\n// no city is found.\nfunction lookupCity(zip, callback) {\n    withDB(db => {\n        // Create a read-only transaction object for this query. The\n        // argument is an array of object stores we will need to use.\n        let transaction = db.transaction([\"zipcodes\"]);\n\n        // Get the object store from the transaction\n        let zipcodes = transaction.objectStore(\"zipcodes\");\n\n        // Now request the object that matches the specified zipcode key.\n        // The lines above were synchronous, but this one is async.\n        let request = zipcodes.get(zip);\n        request.onerror = console.error;  // Log errors\n        request.onsuccess = () => {       // Or call this function on success\n            let record = request.result;  // This is the query result\n            if (record) { // If we found a match, pass it to the callback\n                callback(`${record.city}, ${record.state}`);\n            } else {     // Otherwise, tell the callback that we failed\n                callback(null);\n            }\n        };\n    });\n}\n\n// Given the name of a city, use the IndexedDB API to asynchronously\n// look up all zip code records for all cities (in any state) that have\n// that (case-sensitive) name.\nfunction lookupZipcodes(city, callback) {\n    withDB(db => {\n        // As above, we create a transaction and get the object store\n        let transaction = db.transaction([\"zipcodes\"]);\n        let store = transaction.objectStore(\"zipcodes\");\n\n        // This time we also get the city index of the object store\n        let index = store.index(\"cities\");\n\n        // Ask for all matching records in the index with the specified\n        // city name, and when we get them we pass them to the callback.\n        // If we expected more results, we might use openCursor() instead.\n        let request = index.getAll(city);\n        request.onerror = console.error;\n        request.onsuccess = () => { callback(request.result); };\n    });\n}\n```\n## 15.13 Worker Threads and Messaging\nOne of the fundamental features of JavaScript is that it is single-threaded: a browser will never run two event handlers at the same time, and it will never trigger a timer while an event handler is running, for example. Concurrent updates to application state or to the document are simply not possible, and client-side programmers do not need to think about, or even understand, concurrent programming. A corollary is that client-side JavaScript functions must not run too long; otherwise, they will tie up the event loop and the web browser will become unresponsive to user input. This is the reason that fetch() is an asynchronous function, for example.\n\nWeb browsers very carefully relax the single-thread requirement with the Worker class: instances of this class represent threads that run concurrently with the main thread and the event loop. Workers live in a self-contained execution environment with a completely independent global object and no access to the Window or Document objects. Workers can communicate with the main thread only through asynchronous message passing. This means that concurrent modifications of the DOM remain impossible, but it also means that you can write long-running functions that do not stall the event loop and hang the browser. Creating a new worker is not a heavyweight operation like opening a new browser window, but workers are not flyweight “fibers” either, and it does not make sense to create new workers to perform trivial operations. Complex web applications may find it useful to create tens of workers, but it is unlikely that an application with hundreds or thousands of workers would be practical.\n\nWorkers are useful when your application needs to perform computationally intensive tasks, such as image processing. Using a worker moves tasks like this off the main thread so that the browser does not become unresponsive. And workers also offer the possibility of dividing the work among multiple threads. But workers are also useful when you have to perform frequent moderately intensive computations. Suppose, for example, that you’re implementing a simple in-browser code editor, and want to include syntax highlighting. To get the highlighting right, you need to parse the code on every keystroke. But if you do that on the main thread, it is likely that the parsing code will prevent the event handlers that respond to the user’s key strokes from running promptly and the user’s typing experience will be sluggish.\n\nAs with any threading API, there are two parts to the Worker API. The first is the Worker object: this is what a worker looks like from the outside, to the thread that creates it. The second is the WorkerGlobalScope: this is the global object for a new worker, and it is what a worker thread looks like, on the inside, to itself.\n\nThe following sections cover Worker and WorkerGlobalScope and also explain the message-passing API that allows workers to communicate with the main thread and each other. The same communication API is used to exchange messages between a document and `<iframe>` elements contained in the document, and this is covered in the following sections as well.\n\n### 15.13.1 Worker Objects\nTo create a new worker, call the Worker() constructor, passing a URL that specifies the JavaScript code that the worker is to run:\n\nlet dataCruncher = new Worker(\"utils/cruncher.js\");\nIf you specify a relative URL, it is resolved relative to the URL of the document that contains the script that called the Worker() constructor. If you specify an absolute URL, it must have the same origin (same protocol, host, and port) as that containing document.\n\nOnce you have a Worker object, you can send data to it with postMessage(). The value you pass to postMessage() will be copied using the structured clone algorithm (see “The Structured Clone Algorithm”), and the resulting copy will be delivered to the worker via a message event:\n\ndataCruncher.postMessage(\"/api/data/to/crunch\");\nHere we’re just passing a single string message, but you can also use objects, arrays, typed arrays, Maps, Sets, and so on. You can receive messages from a worker by listening for “message” events on the Worker object:\n```js\ndataCruncher.onmessage = function(e) {\n    let stats = e.data;  // The message is the data property of the event\n    console.log(`Average: ${stats.mean}`);\n}\n```\nLike all event targets, Worker objects define the standard addEventListener() and removeEventListener() methods, and you can use these in place of the onmessage.\n\nIn addition to postMessage(), Worker objects have just one other method, terminate(), which forces a worker thread to stop running.\n\n### 15.13.2 The Global Object in Workers\nWhen you create a new worker with the Worker() constructor, you specify the URL of a file of JavaScript code. That code is executed in a new, pristine JavaScript execution environment, isolated from the script that created the worker. The global object for that new execution environment is a WorkerGlobalScope object. A WorkerGlobalScope is something more than the core JavaScript global object, but less than a full-blown client-side Window object.\n\nThe WorkerGlobalScope object has a postMessage() method and an onmessage event handler property that are just like those of the Worker object but work in the opposite direction: calling postMessage() inside a worker generates a message event outside the worker, and messages sent from outside the worker are turned into events and delivered to the onmessage handler. Because the WorkerGlobalScope is the global object for a worker, postMessage() and onmessage look like a global function and global variable to worker code.\n\nIf you pass an object as the second argument to the Worker() constructor, and if that object has a name property, then the value of that property becomes the value of the name property in the worker’s global object. A worker might include this name in any messages it prints with console.warn() or console.error().\n\nThe close() function allows a worker to terminate itself, and it is similar in effect to the terminate() method of a Worker object.\n\nSince WorkerGlobalScope is the global object for workers, it has all of the properties of the core JavaScript global object, such as the JSON object, the isNaN() function, and the Date() constructor. In addition, however, WorkerGlobalScope also has the following properties of the client-side Window object:\n\nself is a reference to the global object itself. WorkerGlobalScope is not a Window object and does not define a window property.\n\nThe timer methods setTimeout(), clearTimeout(), setInterval(), and clearInterval().\n\nA location property that describes the URL that was passed to the Worker() constructor. This property refers to a Location object, just as the location property of a Window does. The Location object has properties href, protocol, host, hostname, port, pathname, search, and hash. In a worker, these properties are read-only, however.\n\nA navigator property that refers to an object with properties like those of the Navigator object of a window. A worker’s Navigator object has the properties appName, appVersion, platform, userAgent, and onLine.\n\nThe usual event target methods addEventListener() and removeEventListener().\n\nFinally, the WorkerGlobalScope object includes important client-side JavaScript APIs including the Console object, the fetch() function, and the IndexedDB API. WorkerGlobalScope also includes the Worker() constructor, which means that worker threads can create their own workers.\n\n### 15.13.3 Importing Code into a Worker\nWorkers were defined in web browsers before JavaScript had a module system, so workers have a unique system for including additional code. WorkerGlobalScope defines importScripts() as a global function that all workers have access to:\n```js\n// Before we start working, load the classes and utilities we'll need\nimportScripts(\"utils/Histogram.js\", \"utils/BitSet.js\");\n```\nimportScripts() takes one or more URL arguments, each of which should refer to a file of JavaScript code. Relative URLs are resolved relative to the URL that was passed to the Worker() constructor (not relative to the containing document). importScripts() synchronously loads and executes these files one after the other, in the order in which they were specified. If loading a script causes a network error, or if executing throws an error of any sort, none of the subsequent scripts are loaded or executed. A script loaded with importScripts() can itself call importScripts() to load the files it depends on. Note, however, that importScripts() does not try to keep track of what scripts have already loaded and does nothing to prevent dependency cycles.\n\nimportScripts() is a synchronous function: it does not return until all of the scripts have loaded and executed. You can start using the scripts you loaded as soon as importScripts() returns: there is no need for a callback, event handler, then() method or await. Once you have internalized the asynchronous nature of client-side JavaScript, it feels strange to go back to simple, synchronous programming again. But that is the beauty of threads: you can use a blocking function call in a worker without blocking the event loop in the main thread, and without blocking the computations being concurrently performed in other workers.\n\nMODULES IN WORKERS\nIn order to use modules in workers, you must pass a second argument to the Worker() constructor. This second argument must be an object with a type property set to the string “module.” Passing a type:\"module\" option to the Worker() constructor is much like using the type=\"module\" attribute on an HTML `<script>` tag: it means that the code should be interpreted as a module and that import declarations are allowed.\n\nWhen a worker loads a module instead of a traditional script, the WorkerGlobalScope does not define the importScripts() function.\n\nNote that as of early 2020, Chrome is the only browser that supports true modules and import declarations in workers.\n\n### 15.13.4 Worker Execution Model\nWorker threads run their code (and all imported scripts or modules) synchronously from top to bottom, and then enter an asynchronous phase in which they respond to events and timers. If a worker registers a “message” event handler, it will never exit as long as there is a possibility that message events will still arrive. But if a worker doesn’t listen for messages, it will run until there are no further pending tasks (such as fetch() promises and timers) and all task-related callbacks have been called. Once all registered callbacks have been called, there is no way a worker can begin a new task, so it is safe for the thread to exit, which it will do automatically. A worker can also explicitly shut itself down by calling the global close() function. Note that there are no properties or methods on the Worker object that specify whether a worker thread is still running or not, so workers should not close themselves without somehow coordinating this with their parent thread.\n\nERRORS IN WORKERS\nIf an exception occurs in a worker and is not caught by any catch clause, then an “error” event is triggered on the global object of the worker. If this event is handled and the handler calls the preventDefault() method of the event object, the error propagation ends. Otherwise, the “error” event is fired on the Worker object. If preventDefault() is called there, then propagation ends. Otherwise, an error message is printed in the developer console and the onerror handler (§15.1.7) of the Window object is invoked.\n```js\n// Handle uncaught worker errors with a handler inside the worker.\nself.onerror = function(e) {\n    console.log(`Error in worker at ${e.filename}:${e.lineno}: ${e.message}`);\n    e.preventDefault();\n};\n\n// Or, handle uncaught worker errors with a handler outside the worker.\nworker.onerror = function(e) {\n    console.log(`Error in worker at ${e.filename}:${e.lineno}: ${e.message}`);\n    e.preventDefault();\n};\n```\nLike windows, workers can register a handler to be invoked when a Promise is rejected and there is no .catch() function to handle it. Within a worker you can detect this by defining a self.onunhandledrejection function or by using addEventListener() to register a global handler for “unhandledrejection” events. The event object passed to this handler will have a promise property whose value is the Promise object that rejected and a reason property whose value is what would have been passed to a .catch() function.\n\n### 15.13.5 postMessage(), MessagePorts, and MessageChannels\nThe postMessage() method of the Worker object and the global postMesage() function defined inside a worker both work by invoking the postMessage() methods of a pair of MessagePort objects that are automatically created along with the worker. Client-side JavaScript can’t directly access these automatically created MessagePort objects, but it can create new pairs of connected ports with the MessageChannel() constructor:\n```js\nlet channel = new MessageChannel;                // Create a new channel.\nlet myPort = channel.port1;                      // It has two ports\nlet yourPort = channel.port2;                    // connected to each other.\n\nmyPort.postMessage(\"Can you hear me?\");          // A message posted to one will\nyourPort.onmessage = (e) => console.log(e.data); // be received on the other.\n```\nA MessageChannel is an object with port1 and port2 properties that refer to a pair of connected MessagePort objects. A MessagePort is an object with a postMessage() method and an onmessage event handler property. When postMessage() is called on one port of a connected pair, a “message” event is fired on the other port in the pair. You can receive these “message” events by setting the onmessage property or by using addEventListener() to register a listener for “message” events.\n\nMessages sent to a port are queued until the onmessage property is defined or until the start() method is called on the port. This prevents messages sent by one end of the channel from being missed by the other end. If you use addEventListener() with a MessagePort, don’t forget to call start() or you may never see a message delivered.\n\nAll the postMessage() calls we’ve seen so far have taken a single message argument. But the method also accepts an optional second argument. This second argument is an array of items that are to be transferred to the other end of the channel instead of having a copy sent across the channel. Values that can be transferred instead of copied are MessagePorts and ArrayBuffers. (Some browsers also implement other transferable types, such as ImageBitmap and OffscreenCanvas. These are not universally supported, however, and are not covered in this book.) If the first argument to postMessage() includes a MessagePort (nested anywhere within the message object), then that MessagePort must also appear in the second argument. If you do this, then the MessagePort will become available to the other end of the channel and will immediately become nonfunctional on your end. Suppose you have created a worker and want to have two channels for communicating with it: one channel for ordinary data exchange and one channel for high-priority messages. In the main thread, you might create a MessageChannel, then call postMessage() on the worker to pass one of the MessagePorts to it:\n```js\nlet worker = new Worker(\"worker.js\");\nlet urgentChannel = new MessageChannel();\nlet urgentPort = urgentChannel.port1;\nworker.postMessage({ command: \"setUrgentPort\", value: urgentChannel.port2 },\n                   [ urgentChannel.port2 ]);\n// Now we can receive urgent messages from the worker like this\nurgentPort.addEventListener(\"message\", handleUrgentMessage);\nurgentPort.start();  // Start receiving messages\n// And send urgent messages like this\nurgentPort.postMessage(\"test\");\n```\nMessageChannels are also useful if you create two workers and want to allow them to communicate directly with each other rather than requiring code on the main thread to relay messages between them.\n\nThe other use of the second argument to postMessage() is to transfer ArrayBuffers between workers without having to copy them. This is an important performance enhancement for large ArrayBuffers like those used to hold image data. When an ArrayBuffer is transferred over a MessagePort, the ArrayBuffer becomes unusable in the original thread so that there is no possibility of concurrent access to its contents. If the first argument to postMessage() includes an ArrayBuffer, or any value (such as a typed array) that has an ArrayBuffer, then that buffer may appear as an array element in the second postMessage() argument. If it does appear, then it will be transferred without copying. If not, then the ArrayBuffer will be copied rather than transferred. Example 15-14 will demonstrate the use of this transfer technique with ArrayBuffers.\n\n### 15.13.6 Cross-Origin Messaging with postMessage()\nThere is another use case for the postMessage() method in client-side JavaScript. It involves windows instead of workers, but there are enough similarities between the two cases that we will describe the postMessage() method of the Window object here.\n\nWhen a document contains an `<iframe>` element, that element acts as an embedded but independent window. The Element object that represents the `<iframe>` has a contentWindow property that is the Window object for the embedded document. And for scripts running within that nested iframe, the window.parent property refers to the containing Window object. When two windows display documents with the same origin, then scripts in each of those windows have access to the contents of the other window. But when the documents have different origins, the browser’s same-origin policy prevents JavaScript in one window from accessing the content of another window.\n\nFor workers, postMessage() provides a safe way for two independent threads to communicate without sharing memory. For windows, postMessage() provides a controlled way for two independent origins to safely exchange messages. Even if the same-origin policy prevents your script from seeing the content of another window, you can still call postMessage() on that window, and doing so will cause a “message” event to be triggered on that window, where it can be seen by the event handlers in that window’s scripts.\n\nThe postMessage() method of a Window is a little different than the postMessage() method of a Worker, however. The first argument is still an arbitrary message that will be copied by the structured clone algorithm. But the optional second argument listing objects to be transferred instead of copied becomes an optional third argument. The postMessage() method of a window takes a string as its required second argument. This second argument should be an origin (a protocol, hostname, and optional port) that specifies who you expect to be receiving the message. If you pass the string “https://good.example.com” as the second argument, but the window you are posting the message to actually contains content from “https://malware.example.com,” then the message you posted will not be delivered. If you are willing to send your message to content from any origin, then you can pass the wildcard “*” as the second argument.\n\nJavaScript code running inside a window or `<iframe>` can receive messages posted to that window or frame by defining the onmessage property of that window or by calling addEventListener() for “message” events. As with workers, when you receive a “message” event for a window, the data property of the event object is the message that was sent. In addition, however, “message” events delivered to windows also define source and origin properties. The source property specifies the Window object that sent the event, and you can use event.source.postMessage() to send a reply. The origin property specifies the origin of the content in the source window. This is not something the sender of the message can forge, and when you receive a “message” event, you will typically want to verify that it is from an origin you expect.\n\n## 15.14 Example: The Mandelbrot Set\nThis chapter on client-side JavaScript culminates with a long example that demonstrates using workers and messaging to parallelize computationally intensive tasks. But it is written to be an engaging, real-world web application and also demonstrates a number of the other APIs demonstrated in this chapter, including history management; use of the ImageData class with a `<canvas>`; and the use of keyboard, pointer, and resize events. It also demonstrates important core JavaScript features, including generators and a sophisticated use of Promises.\n\nThe example is a program for displaying and exploring the Mandelbrot set, a complex fractal that includes beautiful images like the one shown in Figure 15-16.\n\n<Figures figure=\"15-16\">A portion of the Mandelbrot set</Figures>\n\nThe Mandelbrot set is defined as the set of points on the complex plane, which, when put through a repeated process of complex multiplication and addition, produce a value whose magnitude remains bounded. The contours of the set are surprisingly complex, and computing which points are members of the set and which are not is computationally intensive: to produce a 500×500 image of the Mandelbrot set, you must individually compute the membership of each of the 250,000 pixels in your image. And to verify that the value associated with each pixel remains bounded, you may have to repeat the process of complex multiplication 1,000 times or more. (More iterations give more sharply defined boundaries for the set; fewer iterations produce fuzzier boundaries.) With up to 250 million steps of complex arithmetic required to produce a high-quality image of the Mandelbrot set, you can understand why using workers is a valuable technique. Example 15-14 shows the worker code we will use. This file is relatively compact: it is just the raw computational muscle for the larger program. Two things are worth noting about it, however:\n\nThe worker creates an ImageData object to represent the rectangular grid of pixels for which it is computing Mandelbrot set membership. But instead of storing actual pixel values in the ImageData, it uses a custom-typed array to treat each pixel as a 32-bit integer. It stores the number of iterations required for each pixel in this array. If the magnitude of the complex number computed for each pixel becomes greater than four, then it is mathematically guaranteed to grow without bounds from then on, and we say it has “escaped.” So the value this worker returns for each pixel is the number of iterations before the value escaped. We tell the worker the maximum number of iterations it should try for each value, and pixels that reach this maximum number are considered to be in the set.\n\nThe worker transfers the ArrayBuffer associated with the ImageData back to the main thread so the memory associated with it does not need to be copied.\n\nExample 15-14. Worker code for computing regions of the Mandelbrot set\n```js\n// This is a simple worker that receives a message from its parent thread,\n// performs the computation described by that message and then posts the\n// result of that computation back to the parent thread.\nonmessage = function(message) {\n    // First, we unpack the message we received:\n    //  - tile is an object with width and height properties. It specifies the\n    //    size of the rectangle of pixels for which we will be computing\n    //    Mandelbrot set membership.\n    //  - (x0, y0) is the point in the complex plane that corresponds to the\n    //    upper-left pixel in the tile.\n    //  - perPixel is the pixel size in both the real and imaginary dimensions.\n    //  - maxIterations specifies the maximum number of iterations we will\n    //    perform before deciding that a pixel is in the set.\n    const {tile, x0, y0, perPixel, maxIterations} = message.data;\n    const {width, height} = tile;\n\n    // Next, we create an ImageData object to represent the rectangular array\n    // of pixels, get its internal ArrayBuffer, and create a typed array view\n    // of that buffer so we can treat each pixel as a single integer instead of\n    // four individual bytes. We'll store the number of iterations for each\n    // pixel in this iterations array. (The iterations will be transformed into\n    // actual pixel colors in the parent thread.)\n    const imageData = new ImageData(width, height);\n    const iterations = new Uint32Array(imageData.data.buffer);\n\n    // Now we begin the computation. There are three nested for loops here.\n    // The outer two loop over the rows and columns of pixels, and the inner\n    // loop iterates each pixel to see if it \"escapes\" or not. The various\n    // loop variables are the following:\n    // - row and column are integers representing the pixel coordinate.\n    // - x and y represent the complex point for each pixel: x + yi.\n    // - index is the index in the iterations array for the current pixel.\n    // - n tracks the number of iterations for each pixel.\n    // - max and min track the largest and smallest number of iterations\n    //   we've seen so far for any pixel in the rectangle.\n    let index = 0, max = 0, min=maxIterations;\n    for(let row = 0, y = y0; row < height; row++, y += perPixel) {\n        for(let column = 0, x = x0; column < width; column++, x += perPixel) {\n            // For each pixel we start with the complex number c = x+yi.\n            // Then we repeatedly compute the complex number z(n+1) based on\n            // this recursive formula:\n            //    z(0) = c\n            //    z(n+1) = z(n)^2 + c\n            // If |z(n)| (the magnitude of z(n)) is > 2, then the\n            // pixel is not part of the set and we stop after n iterations.\n            let n;             // The number of iterations so far\n            let r = x, i = y;  // Start with z(0) set to c\n            for(n = 0; n < maxIterations; n++) {\n                let rr = r*r, ii = i*i; // Square the two parts of z(n).\n                if (rr + ii > 4) {      // If |z(n)|^2 is > 4 then\n                    break;              // we've escaped and can stop iterating.\n                }\n                i = 2*r*i + y;          // Compute imaginary part of z(n+1).\n                r = rr - ii + x;        // And the real part of z(n+1).\n            }\n            iterations[index++] = n;    // Remember # iterations for each pixel.\n            if (n > max) max = n;       // Track the maximum number we've seen.\n            if (n < min) min = n;       // And the minimum as well.\n        }\n    }\n\n    // When the computation is complete, send the results back to the parent\n    // thread. The imageData object will be copied, but the giant ArrayBuffer\n    // it contains will be transferred for a nice performance boost.\n    postMessage({tile, imageData, min, max}, [imageData.data.buffer]);\n};\n```\nThe Mandelbrot set viewer application that uses that worker code is shown in Example 15-15. Now that you have nearly reached the end of this chapter, this long example is something of a capstone experience that brings together a number of important core and client-side JavaScript features and APIs. The code is thoroughly commented, and I encourage you to read it carefully.\n\nExample 15-15. A web application for displaying and exploring the Mandelbrot set\n```js\n/*\n * This class represents a subrectangle of a canvas or image. We use Tiles to\n * divide a canvas into regions that can be processed independently by Workers.\n */\nclass Tile {\n    constructor(x, y, width, height) {\n        this.x = x;                     // The properties of a Tile object\n        this.y = y;                     // represent the position and size\n        this.width = width;             // of the tile within a larger\n        this.height = height;           // rectangle.\n    }\n\n    // This static method is a generator that divides a rectangle of the\n    // specified width and height into the specified number of rows and\n    // columns and yields numRows*numCols Tile objects to cover the rectangle.\n    static *tiles(width, height, numRows, numCols) {\n        let columnWidth = Math.ceil(width / numCols);\n        let rowHeight = Math.ceil(height / numRows);\n\n        for(let row = 0; row < numRows; row++) {\n            let tileHeight = (row < numRows-1)\n                ? rowHeight                          // height of most rows\n                : height - rowHeight * (numRows-1);  // height of last row\n            for(let col = 0; col < numCols; col++) {\n                let tileWidth = (col < numCols-1)\n                    ? columnWidth                    // width of most columns\n                    : width - columnWidth * (numCols-1); // and last column\n\n                yield new Tile(col * columnWidth, row * rowHeight,\n                               tileWidth, tileHeight);\n            }\n        }\n    }\n}\n\n/*\n * This class represents a pool of workers, all running the same code. The\n * worker code you specify must respond to each message it receives by\n * performing some kind of computation and then posting a single message with\n * the result of that computation.\n *\n * Given a WorkerPool and message that represents work to be performed, simply\n * call addWork(), with the message as an argument. If there is a Worker\n * object that is currently idle, the message will be posted to that worker\n * immediately. If there are no idle Worker objects, the message will be\n * queued and will be posted to a Worker when one becomes available.\n *\n * addWork() returns a Promise, which will resolve with the message recieved\n * from the work, or will reject if the worker throws an unhandled error.\n */\nclass WorkerPool {\n    constructor(numWorkers, workerSource) {\n        this.idleWorkers = [];       // Workers that are not currently working\n        this.workQueue = [];         // Work not currently being processed\n        this.workerMap = new Map();  // Map workers to resolve and reject funcs\n\n        // Create the specified number of workers, add message and error\n        // handlers and save them in the idleWorkers array.\n        for(let i = 0; i < numWorkers; i++) {\n            let worker = new Worker(workerSource);\n            worker.onmessage = message => {\n                this._workerDone(worker, null, message.data);\n            };\n            worker.onerror = error => {\n                this._workerDone(worker, error, null);\n            };\n            this.idleWorkers[i] = worker;\n        }\n    }\n\n    // This internal method is called when a worker finishes working, either\n    // by sending a message or by throwing an error.\n    _workerDone(worker, error, response) {\n        // Look up the resolve() and reject() functions for this worker\n        // and then remove the worker's entry from the map.\n        let [resolver, rejector] = this.workerMap.get(worker);\n        this.workerMap.delete(worker);\n\n        // If there is no queued work, put this worker back in\n        // the list of idle workers. Otherwise, take work from the queue\n        // and send it to this worker.\n        if (this.workQueue.length === 0) {\n            this.idleWorkers.push(worker);\n        } else {\n            let [work, resolver, rejector] = this.workQueue.shift();\n            this.workerMap.set(worker, [resolver, rejector]);\n            worker.postMessage(work);\n        }\n\n        // Finally, resolve or reject the promise associated with the worker.\n        error === null ? resolver(response) : rejector(error);\n    }\n\n    // This method adds work to the worker pool and returns a Promise that\n    // will resolve with a worker's response when the work is done. The work\n    // is a value to be passed to a worker with postMessage(). If there is an\n    // idle worker, the work message will be sent immediately. Otherwise it\n    // will be queued until a worker is available.\n    addWork(work) {\n        return new Promise((resolve, reject) => {\n            if (this.idleWorkers.length > 0) {\n                let worker = this.idleWorkers.pop();\n                this.workerMap.set(worker, [resolve, reject]);\n                worker.postMessage(work);\n            } else {\n                this.workQueue.push([work, resolve, reject]);\n            }\n        });\n    }\n}\n\n/*\n * This class holds the state information necessary to render a Mandelbrot set.\n * The cx and cy properties give the point in the complex plane that is the\n * center of the image. The perPixel property specifies how much the real and\n * imaginary parts of that complex number changes for each pixel of the image.\n * The maxIterations property specifies how hard we work to compute the set.\n * Larger numbers require more computation but produce crisper images.\n * Note that the size of the canvas is not part of the state. Given cx, cy, and\n * perPixel we simply render whatever portion of the Mandelbrot set fits in\n * the canvas at its current size.\n *\n * Objects of this type are used with history.pushState() and are used to read\n * the desired state from a bookmarked or shared URL.\n */\nclass PageState {\n    // This factory method returns an initial state to display the entire set.\n    static initialState() {\n        let s = new PageState();\n        s.cx = -0.5;\n        s.cy = 0;\n        s.perPixel = 3/window.innerHeight;\n        s.maxIterations = 500;\n        return s;\n    }\n\n    // This factory method obtains state from a URL, or returns null if\n    // a valid state could not be read from the URL.\n    static fromURL(url) {\n        let s = new PageState();\n        let u = new URL(url); // Initialize state from the url's search params.\n        s.cx = parseFloat(u.searchParams.get(\"cx\"));\n        s.cy = parseFloat(u.searchParams.get(\"cy\"));\n        s.perPixel = parseFloat(u.searchParams.get(\"pp\"));\n        s.maxIterations = parseInt(u.searchParams.get(\"it\"));\n        // If we got valid values, return the PageState object, otherwise null.\n        return (isNaN(s.cx) || isNaN(s.cy) || isNaN(s.perPixel)\n                || isNaN(s.maxIterations))\n            ? null\n            : s;\n    }\n\n    // This instance method encodes the current state into the search\n    // parameters of the browser's current location.\n    toURL() {\n        let u = new URL(window.location);\n        u.searchParams.set(\"cx\", this.cx);\n        u.searchParams.set(\"cy\", this.cy);\n        u.searchParams.set(\"pp\", this.perPixel);\n        u.searchParams.set(\"it\", this.maxIterations);\n        return u.href;\n    }\n}\n\n// These constants control the parallelism of the Mandelbrot set computation.\n// You may need to adjust them to get optimum performance on your computer.\nconst ROWS = 3, COLS = 4, NUMWORKERS = navigator.hardwareConcurrency || 2;\n\n// This is the main class of our Mandelbrot set program. Simply invoke the\n// constructor function with the <canvas> element to render into. The program\n// assumes that this <canvas> element is styled so that it is always as big\n// as the browser window.\nclass MandelbrotCanvas {\n    constructor(canvas) {\n        // Store the canvas, get its context object, and initialize a WorkerPool\n        this.canvas = canvas;\n        this.context = canvas.getContext(\"2d\");\n        this.workerPool = new WorkerPool(NUMWORKERS, \"mandelbrotWorker.js\");\n\n        // Define some properties that we'll use later\n        this.tiles = null;          // Subregions of the canvas\n        this.pendingRender = null;  // We're not currently rendering\n        this.wantsRerender = false; // No render is currently requested\n        this.resizeTimer = null;    // Prevents us from resizing too frequently\n        this.colorTable = null;     // For converting raw data to pixel values.\n\n        // Set up our event handlers\n        this.canvas.addEventListener(\"pointerdown\", e => this.handlePointer(e));\n        window.addEventListener(\"keydown\", e => this.handleKey(e));\n        window.addEventListener(\"resize\", e => this.handleResize(e));\n        window.addEventListener(\"popstate\", e => this.setState(e.state, false));\n\n        // Initialize our state from the URL or start with the initial state.\n        this.state =\n            PageState.fromURL(window.location) || PageState.initialState();\n\n        // Save this state with the history mechanism.\n        history.replaceState(this.state, \"\", this.state.toURL());\n\n        // Set the canvas size and get an array of tiles that cover it.\n        this.setSize();\n\n        // And render the Mandelbrot set into the canvas.\n        this.render();\n    }\n\n    // Set the canvas size and initialize an array of Tile objects. This\n    // method is called from the constructor and also by the handleResize()\n    // method when the browser window is resized.\n    setSize() {\n        this.width = this.canvas.width = window.innerWidth;\n        this.height = this.canvas.height = window.innerHeight;\n        this.tiles = [...Tile.tiles(this.width, this.height, ROWS, COLS)];\n    }\n\n    // This function makes a change to the PageState, then re-renders the\n    // Mandelbrot set using that new state, and also saves the new state with\n    // history.pushState(). If the first argument is a function that function\n    // will be called with the state object as its argument and should make\n    // changes to the state. If the first argument is an object, then we simply\n    // copy the properties of that object into the state object. If the optional\n    // second argument is false, then the new state will not be saved. (We\n    // do this when calling setState in response to a popstate event.)\n    setState(f, save=true) {\n        // If the argument is a function, call it to update the state.\n        // Otherwise, copy its properties into the current state.\n        if (typeof f === \"function\") {\n            f(this.state);\n        } else {\n            for(let property in f) {\n                this.state[property] = f[property];\n            }\n        }\n\n        // In either case, start rendering the new state ASAP.\n        this.render();\n\n        // Normally we save the new state. Except when we're called with\n        // a second argument of false which we do when we get a popstate event.\n        if (save) {\n            history.pushState(this.state, \"\", this.state.toURL());\n        }\n    }\n\n    // This method asynchronously draws the portion of the Mandelbrot set\n    // specified by the PageState object into the canvas. It is called by\n    // the constructor, by setState() when the state changes, and by the\n    // resize event handler when the size of the canvas changes.\n    render() {\n        // Sometimes the user may use the keyboard or mouse to request renders\n        // more quickly than we can perform them. We don't want to submit all\n        // the renders to the worker pool. Instead if we're rendering, we'll\n        // just make a note that a new render is needed, and when the current\n        // render completes, we'll render the current state, possibly skipping\n        // multiple intermediate states.\n        if (this.pendingRender) {        // If we're already rendering,\n            this.wantsRerender = true;   // make a note to rerender later\n            return;                      // and don't do anything more now.\n        }\n\n        // Get our state variables and compute the complex number for the\n        // upper left corner of the canvas.\n        let {cx, cy, perPixel, maxIterations} = this.state;\n        let x0 = cx - perPixel * this.width/2;\n        let y0 = cy - perPixel * this.height/2;\n\n        // For each of our ROWS*COLS tiles, call addWork() with a message\n        // for the code in mandelbrotWorker.js. Collect the resulting Promise\n        // objects into an array.\n        let promises = this.tiles.map(tile => this.workerPool.addWork({\n            tile: tile,\n            x0: x0 + tile.x * perPixel,\n            y0: y0 + tile.y * perPixel,\n            perPixel: perPixel,\n            maxIterations: maxIterations\n        }));\n\n        // Use Promise.all() to get an array of responses from the array of\n        // promises. Each response is the computation for one of our tiles.\n        // Recall from mandelbrotWorker.js that each response includes the\n        // Tile object, an ImageData object that includes iteration counts\n        // instead of pixel values, and the minimum and maximum iterations\n        // for that tile.\n        this.pendingRender = Promise.all(promises).then(responses => {\n\n            // First, find the overall max and min iterations over all tiles.\n            // We need these numbers so we can assign colors to the pixels.\n            let min = maxIterations, max = 0;\n            for(let r of responses) {\n                if (r.min < min) min = r.min;\n                if (r.max > max) max = r.max;\n            }\n\n            // Now we need a way to convert the raw iteration counts from the\n            // workers into pixel colors that will be displayed in the canvas.\n            // We know that all the pixels have between min and max iterations\n            // so we precompute the colors for each iteration count and store\n            // them in the colorTable array.\n\n            // If we haven't allocated a color table yet, or if it is no longer\n            // the right size, then allocate a new one.\n            if (!this.colorTable || this.colorTable.length !== maxIterations+1){\n                this.colorTable = new Uint32Array(maxIterations+1);\n            }\n\n            // Given the max and the min, compute appropriate values in the\n            // color table. Pixels in the set will be colored fully opaque\n            // black. Pixels outside the set will be translucent black with higher\n            // iteration counts resulting in higher opacity. Pixels with\n            // minimum iteration counts will be transparent and the white\n            // background will show through, resulting in a grayscale image.\n            if (min === max) {                // If all the pixels are the same,\n                if (min === maxIterations) {  // Then make them all black\n                    this.colorTable[min] = 0xFF000000;\n                } else {                      // Or all transparent.\n                    this.colorTable[min] = 0;\n                }\n            } else {\n                // In the normal case where min and max are different, use a\n                // logarithic scale to assign each possible iteration count an\n                // opacity between 0 and 255, and then use the shift left\n                // operator to turn that into a pixel value.\n                let maxlog = Math.log(1+max-min);\n                for(let i = min; i <= max; i++) {\n                    this.colorTable[i] =\n                        (Math.ceil(Math.log(1+i-min)/maxlog * 255) << 24);\n                }\n            }\n\n            // Now translate the iteration numbers in each response's\n            // ImageData to colors from the colorTable.\n            for(let r of responses) {\n                let iterations = new Uint32Array(r.imageData.data.buffer);\n                for(let i = 0; i < iterations.length; i++) {\n                    iterations[i] = this.colorTable[iterations[i]];\n                }\n            }\n\n            // Finally, render all the imageData objects into their\n            // corresponding tiles of the canvas using putImageData().\n            // (First, though, remove any CSS transforms on the canvas that may\n            // have been set by the pointerdown event handler.)\n            this.canvas.style.transform = \"\";\n            for(let r of responses) {\n                this.context.putImageData(r.imageData, r.tile.x, r.tile.y);\n            }\n        })\n        .catch((reason) => {\n            // If anything went wrong in any of our Promises, we'll log\n            // an error here. This shouldn't happen, but this will help with\n            // debugging if it does.\n            console.error(\"Promise rejected in render():\", reason);\n        })\n        .finally(() => {\n            // When we are done rendering, clear the pendingRender flags\n            this.pendingRender = null;\n            // And if render requests came in while we were busy, rerender now.\n            if (this.wantsRerender) {\n                this.wantsRerender = false;\n                this.render();\n            }\n        });\n     }\n\n    // If the user resizes the window, this function will be called repeatedly.\n    // Resizing a canvas and rerendering the Mandlebrot set is an expensive\n    // operation that we can't do multiple times a second, so we use a timer\n    // to defer handling the resize until 200ms have elapsed since the last\n    // resize event was received.\n    handleResize(event) {\n        // If we were already deferring a resize, clear it.\n        if (this.resizeTimer) clearTimeout(this.resizeTimer);\n        // And defer this resize instead.\n        this.resizeTimer = setTimeout(() => {\n            this.resizeTimer = null;  // Note that resize has been handled\n            this.setSize();           // Resize canvas and tiles\n            this.render();            // Rerender at the new size\n        }, 200);\n    }\n\n    // If the user presses a key, this event handler will be called.\n    // We call setState() in response to various keys, and setState() renders\n    // the new state, updates the URL, and saves the state in browser history.\n    handleKey(event) {\n        switch(event.key) {\n        case \"Escape\":     // Type Escape to go back to the initial state\n            this.setState(PageState.initialState());\n            break;\n        case \"+\":          // Type + to increase the number of iterations\n            this.setState(s => {\n                s.maxIterations = Math.round(s.maxIterations*1.5);\n            });\n            break;\n        case \"-\":          // Type - to decrease the number of iterations\n            this.setState(s => {\n                s.maxIterations = Math.round(s.maxIterations/1.5);\n                if (s.maxIterations < 1) s.maxIterations = 1;\n            });\n            break;\n        case \"o\":          // Type o to zoom out\n            this.setState(s => s.perPixel *= 2);\n            break;\n        case \"ArrowUp\":    // Up arrow to scroll up\n            this.setState(s => s.cy -= this.height/10 * s.perPixel);\n            break;\n        case \"ArrowDown\":  // Down arrow to scroll down\n            this.setState(s => s.cy += this.height/10 * s.perPixel);\n            break;\n        case \"ArrowLeft\":  // Left arrow to scroll left\n            this.setState(s => s.cx -= this.width/10 * s.perPixel);\n            break;\n        case \"ArrowRight\": // Right arrow to scroll right\n            this.setState(s => s.cx += this.width/10 * s.perPixel);\n            break;\n        }\n    }\n\n    // This method is called when we get a pointerdown event on the canvas.\n    // The pointerdown event might be the start of a zoom gesture (a click or\n    // tap) or a pan gesture (a drag). This handler registers handlers for\n    // the pointermove and pointerup events in order to respond to the rest\n    // of the gesture. (These two extra handlers are removed when the gesture\n    // ends with a pointerup.)\n    handlePointer(event) {\n        // The pixel coordinates and time of the initial pointer down.\n        // Because the canvas is as big as the window, these event coordinates\n        // are also canvas coordinates.\n        const x0 = event.clientX, y0 = event.clientY, t0 = Date.now();\n\n        // This is the handler for move events.\n        const pointerMoveHandler = event => {\n            // How much have we moved, and how much time has passed?\n            let dx=event.clientX-x0, dy=event.clientY-y0, dt=Date.now()-t0;\n\n            // If the pointer has moved enough or enough time has passed that\n            // this is not a regular click, then use CSS to pan the display.\n            // (We will rerender it for real when we get the pointerup event.)\n            if (dx > 10 || dy > 10 || dt > 500) {\n                this.canvas.style.transform = `translate(${dx}px, ${dy}px)`;\n            }\n        };\n\n        // This is the handler for pointerup events\n        const pointerUpHandler = event => {\n            // When the pointer goes up, the gesture is over, so remove\n            // the move and up handlers until the next gesture.\n            this.canvas.removeEventListener(\"pointermove\", pointerMoveHandler);\n            this.canvas.removeEventListener(\"pointerup\", pointerUpHandler);\n\n            // How much did the pointer move, and how much time passed?\n            const dx = event.clientX-x0, dy=event.clientY-y0, dt=Date.now()-t0;\n            // Unpack the state object into individual constants.\n            const {cx, cy, perPixel} = this.state;\n\n            // If the pointer moved far enough or if enough time passed, then\n            // this was a pan gesture, and we need to change state to change\n            // the center point. Otherwise, the user clicked or tapped on a\n            // point and we need to center and zoom in on that point.\n            if (dx > 10 || dy > 10 || dt > 500) {\n                // The user panned the image by (dx, dy) pixels.\n                // Convert those values to offsets in the complex plane.\n                this.setState({cx: cx - dx*perPixel, cy: cy - dy*perPixel});\n            } else {\n                // The user clicked. Compute how many pixels the center moves.\n                let cdx = x0 - this.width/2;\n                let cdy = y0 - this.height/2;\n\n                // Use CSS to quickly and temporarily zoom in\n                this.canvas.style.transform =\n                    `translate(${-cdx*2}px, ${-cdy*2}px) scale(2)`;\n\n                // Set the complex coordinates of the new center point and\n                // zoom in by a factor of 2.\n                this.setState(s => {\n                    s.cx += cdx * s.perPixel;\n                    s.cy += cdy * s.perPixel;\n                    s.perPixel /= 2;\n                });\n            }\n        };\n\n        // When the user begins a gesture we register handlers for the\n        // pointermove and pointerup events that follow.\n        this.canvas.addEventListener(\"pointermove\", pointerMoveHandler);\n        this.canvas.addEventListener(\"pointerup\", pointerUpHandler);\n    }\n}\n\n// Finally, here's how we set up the canvas. Note that this JavaScript file\n// is self-sufficient. The HTML file only needs to include this one <script>.\nlet canvas = document.createElement(\"canvas\"); // Create a canvas element\ndocument.body.append(canvas);                  // Insert it into the body\ndocument.body.style = \"margin:0\";              // No margin for the <body>\ncanvas.style.width = \"100%\";                   // Make canvas as wide as body\ncanvas.style.height = \"100%\";                  // and as high as the body.\nnew MandelbrotCanvas(canvas);                  // And start rendering into it!\n```\n## 15.15 Summary and Suggestions for Further Reading\nThis long chapter has covered the fundamentals of client-side JavaScript programming:\n\nHow scripts and JavaScript modules are included in web pages and how and when they are executed.\n\nClient-side JavaScript’s asynchronous, event-driven programming model.\n\nThe Document Object Model (DOM) that allows JavaScript code to inspect and modify the HTML content of the document it is embedded within. This DOM API is the heart of all client-side JavaScript programming.\n\nHow JavaScript code can manipulate the CSS styles that are applied to content within the document.\n\nHow JavaScript code can obtain the coordinates of document elements in the browser window and within the document itself.\n\nHow to create reusable UI “Web Components” with JavaScript, HTML, and CSS using the Custom Elements and Shadow DOM APIs.\n\nHow to display and dynamically generate graphics with SVG and the HTML `<canvas>` element.\n\nHow to add scripted sound effects (both recorded and synthesized) to your web pages.\n\nHow JavaScript can make the browser load new pages, go backward and forward in the user’s browsing history, and even add new entries to the browsing history.\n\nHow JavaScript programs can exchange data with web servers using the HTTP and WebSocket protocols.\n\nHow JavaScript programs can store data in the user’s browser.\n\nHow JavaScript programs can use worker threads to achieve a safe form of concurrency.\n\nThis has been the longest chapter of the book, by far. But it cannot come close to covering all the APIs available to web browsers. The web platform is sprawling and ever-evolving, and my goal for this chapter was to introduce the most important core APIs. With the knowledge you have from this book, you are well equipped to learn and use new APIs as you need them. But you can’t learn about a new API if you don’t know that it exists, so the short sections that follow end the chapter with a quick list of web platform features that you might want to investigate in the future.\n\n### 15.15.1 HTML and CSS\nThe web is built upon three key technologies: HTML, CSS, and JavaScript, and knowledge of JavaScript can take you only so far as a web developer unless you also develop your expertise with HTML and CSS. It is important to know how to use JavaScript to manipulate HTML elements and CSS styles, but that knowledge is is much more useful if you also know which HTML elements and which CSS styles to use.\n\nSo before you start exploring more JavaScript APIs, I would encourage you to invest some time in mastering the other tools in a web developer’s toolkit. HTML form and input elements, for example, have sophisticated behavior that is important to understand, and the flexbox and grid layout modes in CSS are incredibly powerful.\n\nTwo topics worth paying particular attention to in this area are accessibility (including ARIA attributes) and internationalization (including support for right-to-left writing directions).\n\n### 15.15.2 Performance\nOnce you have written a web application and released it to the world, the never-ending quest to make it fast begins. It is hard to optimize things that you can’t measure, however, so it is worth familiarizing yourself with the Performance APIs. The performance property of the window object is the main entry point to this API. It includes a high-resolution time source performance.now(), and methods performance.mark() and performance.measure() for marking critical points in your code and measuring the elapsed time between them. Calling these methods creates PerformanceEntry objects that you can access with performance.getEntries(). Browsers add their own PerformanceEntry objects any time the browser loads a new page or fetches a file over the network, and these automatically created PerformanceEntry objects include granular timing details of your application’s network performance. The related PerformanceObserver class allows you to specify a function to be invoked when new PerformanceEntry objects are created.\n\n### 15.15.3 Security\nThis chapter introduced the general idea of how to defend against cross-site scripting (XSS) security vulnerabilities in your websites, but we did not go into much detail. The topic of web security is an important one, and you may want to spend some time learning more about it. In addition to XSS, it is worth learning about the Content-Security-Policy HTTP header and understanding how CSP allows you to ask the web browser to restrict the capabilities it grants to JavaScript code. Understanding CORS (Cross-Origin Resource Sharing) is also important.\n\n### 15.15.4 WebAssembly\nWebAssembly (or “wasm”) is a low-level virtual machine bytecode format that is designed to integrate well with JavaScript interpreters in web browsers. There are compilers that allow you to compile C, C++, and Rust programs to WebAssembly bytecode and to run those programs in web browsers at close to native speed, without breaking the browser sandbox or security model. WebAssembly can export functions that can be called by JavaScript programs. A typical use case for WebAssembly would be to compile the standard C-language zlib compression library so that JavaScript code has access to high-speed compression and decompression algorithms. Learn more at https://webassembly.org.\n\n### 15.15.5 More Document and Window Features\nThe Window and Document objects have a number of features that were not covered in this chapter:\n\nThe Window object defines alert(), confirm(), and prompt() methods that display simple modal dialogues to the user. These methods block the main thread. The confirm() method synchronously returns a boolean value, and prompt() synchronously returns a string of user input. These are not suitable for production use but can be useful for simple projects and prototypes.\n\nThe navigator and screen properties of the Window object were mentioned in passing at the start of this chapter, but the Navigator and Screen objects that they reference have some features that were not described here that you may find useful.\n\nThe requestFullscreen() method of any Element object requests that that element (a `<video>` or `<canvas>` element, for example) be displayed in fullscreen mode. The exitFullscreen() method of the Document returns to normal display mode.\n\nThe requestAnimationFrame() method of the Window object takes a function as its argument and will execute that function when the browser is preparing to render the next frame. When you are making visual changes (especially repeated or animated ones), wrapping your code within a call to requestAnimationFrame() can help to ensure that the changes are rendered smoothly and in a way that is optimized by the browser.\n\nIf the user selects text within your document, you can obtain details of that selection with the Window method getSelection() and get the selected text with getSelection().toString(). In some browsers, navigator.clipboard is an object with an async API for reading and setting the content of the system clipboard to enable copy-and-paste interactions with applications outside of the browser.\n\nA little-known feature of web browsers is that HTML elements with a contenteditable=\"true\" attribute allow their content to be edited. The document.execCommand() method enables rich-text editing features for editable content.\n\nA MutationObserver allows JavaScript to monitor changes to, or beneath, a specified element in the document. Create a MutationObserver with the MutationObserver() constructor, passing the callback function that should be called when changes are made. Then call the observe() method of the MutationObserver to specify which parts of which element are to be monitored.\n\nAn IntersectionObserver allows JavaScript to determine which document elements are on the screen and which are close to being on the screen. It is particularly useful for applications that want to dynamically load content on demand as the user scrolls.\n\n### 15.15.6 Events\nThe sheer number and diversity of events supported by the web platform can be daunting. This chapter has discussed a variety of event types, but here are some more that you may find useful:\n\nBrowsers fire “online” and “offline” events at the Window object when the browser gains or loses an internet connection.\n\nBrowsers fire a “visiblitychange” event at the Document object when a document becomes visible or invisible (usually because a user has switched tabs). JavaScript can check document.visibilityState to determine whether its document is currently “visible” or “hidden.”\n\nBrowsers support a complicated API to support drag-and-drop UIs and to support data exchange with applications outside the browser. This API involves a number of events, including “dragstart,” “dragover,” “dragend,” and “drop.” This API is tricky to use correctly but useful when you need it. It is an important API to know about if you want to enable users to drag files from their desktop into your web application.\n\nThe Pointer Lock API enables JavaScript to hide the mouse pointer and get raw mouse events as relative movement amounts rather than absolute positions on the screen. This is typically useful for games. Call requestPointerLock() on the element you want all mouse events directed to. After you do this, “mousemove” events delivered to that element will have movementX and movementY properties.\n\nThe Gamepad API adds support for game controllers. Use navigator.getGamepads() to get connected Gamepad objects, and listen for “gamepadconnected” events on the Window object to be notified when a new controller is plugged in. The Gamepad object defines an API for querying the current state of the buttons on the controller.\n\n### 15.15.7 Progressive Web Apps and Service Workers\nThe term Progressive Web Apps, or PWAs, is a buzzword that describes web applications that are built using a few key technologies. Careful documentation of these key technologies would require a book of its own, and I have not covered them in this chapter, but you should be aware of all of these APIs. It is worth noting that powerful modern APIs like these are typically designed to work only on secure HTTPS connections. Websites that are still using http:// URLs will not be able to take advantage of these:\n\nA ServiceWorker is a kind of worker thread with the ability to intercept, inspect, and respond to network requests from the web application that it “services.” When a web application registers a service worker, that worker’s code becomes persistent in the browser’s local storage, and when the user visits the associated website again, the service worker is reactivated. Service workers can cache network responses (including files of JavaScript code), which means that web applications that use service workers can effectively install themselves onto the user’s computer for rapid startup and offline use. The Service Worker Cookbook at https://serviceworke.rs is a valuable resource for learning about service workers and their related technologies.\n\nThe Cache API is designed for use by service workers (but is also available to regular JavaScript code outside of workers). It works with the Request and Response objects defined by the fetch() API and implements a cache of Request/Response pairs. The Cache API enables a service worker to cache the scripts and other assets of the web app it serves and can also help to enable offline use of the web app (which is particularly important for mobile devices).\n\nA Web Manifest is a JSON-formatted file that describes a web application including a name, a URL, and links to icons in various sizes. If your web app uses a service worker and includes a `<link rel=\"manifest\">` tag that references a .webmanifest file, then browsers (particularly browsers on mobile devices) may give you the option to add an icon for the web app to your desktop or home screen.\n\nThe Notifications API allows web apps to display notifications using the native OS notification system on both mobile and desktop devices. Notifications can include an image and text, and your code can receive an event if the user clicks on the notification. Using this API is complicated by the fact that you must first request the user’s permission to display notifications.\n\nThe Push API allows web applications that have a service worker (and that have the user’s permission) to subscribe to notifications from a server, and to display those notifications even when the application itself is not running. Push notifications are common on mobile devices, and the Push API brings web apps closer to feature parity with native apps on mobile.\n\n### 15.15.8 Mobile Device APIs\nThere are a number of web APIs that are primarily useful for web apps running on mobile devices. (Unfortunately, a number of these APIs only work on Android devices and not iOS devices.)\n\nThe Geolocation API allows JavaScript (with the user’s permission) to determine the user’s physical location. It is well supported on desktop and mobile devices, including iOS devices. Use navigator.geolocation.getCurrentPosition() to request the user’s current position and use navigator.geolocation.watchPosition() to register a callback to be called when the user’s position changes.\n\nThe navigator.vibrate() method causes a mobile device (but not iOS) to vibrate. Often this is only allowed in response to a user gesture, but calling this method will allow your app to provide silent feedback that a gesture has been recognized.\n\nThe ScreenOrientation API enables a web application to query the current orientation of a mobile device screen and also to lock themselves to landscape or portrait orientation.\n\nThe “devicemotion” and “deviceorientation” events on the window object report accelerometer and magnetometer data for the device, enabling you to determine how the device is accelerating and how the user is orienting it in space. (These events do work on iOS.)\n\nThe Sensor API is not yet widely supported beyond Chrome on Android devices, but it enables JavaScript access to the full suite of mobile device sensors, including accelerometer, gyroscope, magnetometer, and ambient light sensor. These sensors enable JavaScript to determine which direction a user is facing or to detect when the user shakes their phone, for example.\n\n### 15.15.9 Binary APIs\nTyped arrays, ArrayBuffers, and the DataView class (all covered in §11.2) enable JavaScript to work with binary data. As described earlier in this chapter, the fetch() API enables JavaScript programs to load binary data over the network. Another source of binary data is files from the user’s local filesystem. For security reasons, JavaScript can’t just read local files. But if the user selects a file for upload (using an `<input type=\"file>` form element) or uses drag-and-drop to drop a file into your web application, then JavaScript can access that file as a File object.\n\nFile is a subclass of Blob, and as such, it is an opaque representation of a chunk of data. You can use a FileReader class to asynchronously get the content of a file as an ArrayBuffer or string. (In some browsers, you can skip the FileReader and instead use the Promise-based text() and arrayBuffer() methods defined by the Blob class, or the stream() method for streaming access to the file contents.)\n\nWhen working with binary data, especially streaming binary data, you may need to decode bytes into text or encode text as bytes. The TextEncoder and TextDecoder classes help with this task.\n\n### 15.15.10 Media APIs\nThe navigator.mediaDevices.getUserMedia() function allows JavaScript to request access to the user’s microphone and/or video camera. A successful request results in a MediaStream object. Video streams can be displayed in a `<video>` tag (by setting the srcObject property to the stream). Still frames of the video can be captured into an offscreen `<canvas>` with the canvas drawImage() function resulting in a relatively low-resolution photograph. Audio and video streams returned by getUserMedia() can be recorded and encoded to a Blob with a MediaRecorder object.\n\nThe more complex WebRTC API enables the transmission and reception of MediaStreams over the network, enabling peer-to-peer video conferencing, for example.\n\n### 15.15.11 Cryptography and Related APIs\nThe crypto property of the Window object exposes a getRandomValues() method for cryptographically secure pseudorandom numbers. Other methods for encryption, decryption, key generation, digital signatures, and so on are available through crypto.subtle. The name of this property is a warning to everyone who uses these methods that properly using cryptographic algorithms is difficult and that you should not use those methods unless you really know what you are doing. Also, the methods of crypto.subtle are only available to JavaScript code running within documents that were loaded over a secure HTTPS connection.\n\nThe Credential Management API and the Web Authentication API allow JavaScript to generate, store, and retrieve public key (and other types of) credentials and enables account creation and login without passwords. The JavaScript API consists primarily of the functions navigator.credentials.create() and navigator.credentials.get(), but substantial infrastructure is required on the server side to make these methods work. These APIs are not universally supported yet, but have the potential to revolutionize the way we log in to websites.\n\nThe Payment Request API adds browser support for making credit card payments on the web. It allows users to store their payment details securely in the browser so that they don’t have to type their credit card number each time they make a purchase. Web applications that want to request a payment create a PaymentRequest object and call its show() method to display the request to the user.\n\n1 Previous editions of this book had an extensive reference section covering the JavaScript standard library and web APIs. It was removed in the seventh edition because MDN has made it obsolete: today, it is quicker to look something up on MDN than it is to flip through a book, and my former colleagues at MDN do a better job at keeping their online documentation up to date than this book ever could.\n\n2 Some sources, including the HTML specification, make a technical distinction between handlers and listeners, based on the way in which they are registered. In this book, we treat the two terms as synonyms.\n\n3 If you have used the React framework to create client-side user interfaces, this may surprise you. React makes a number of minor changes to the client-side event model, and one of them is that in React, event handler property names are written in camelCase: onClick, onMouseOver, and so on. When working with the web platform natively, however, the event handler properties are written entirely in lowercase.\n\n4 The custom element specification allows subclassing of `<button>` and other specific element classes, but this is not supported in Safari and a different syntax is required to use a custom element that extends anything other than HTMLElement."
  },
  {
    "path": "content/posts/ch16.md",
    "content": "---\ntitle: \"第 16 章 服务器端 JavaScript\"\ndate: 2020-11-02T22:18:27+08:00\n---\n\nNode is JavaScript with bindings to the underlying operating system, making it possible to write JavaScript programs that read and write files, execute child processes, and communicate over the network. This makes Node useful as a:\n\n> Node是绑定到底层操作系统的JavaScript，使编写JavaScript程序能够读写文件、执行子进程和通过网络通信。这使得Node可以用作:\n\n- Modern alternative to shell scripts that does not suffer from the arcane syntax of bash and other Unix shells.\n- General-purpose programming language for running trusted programs, not subject to the security constraints imposed by web browsers on untrusted code.\n- Popular environment for writing efficient and highly concurrent web servers.\n\n---\n\n> - 不受bash和其他Unix shell的神秘语法影响的shell脚本的现代替代方案。\n> - 通用程式设计语言，用以运行受信任的程式，而不受网页浏览器对不受信任的程式所施加的保安限制。\n> - 编写高效和高并发web服务器的流行环境。\n\nThe defining feature of Node is its single-threaded event-based concurrency enabled by an asynchronous-by-default API. If you have programmed in other languages but have not done much JavaScript coding, or if you’re an experienced client-side JavaScript programmer used to writing code for web browers, using Node will be a bit of an adjustment, as is any new programming language or environment. This chapter begins by explaining the Node programming model, with an emphasis on concurrency, Node’s API for working with streaming data, and Node’s Buffer type for working with binary data. These initial sections are followed by sections that highlight and demonstrate some of the most important Node APIs, including those for working with files, networks, processes, and threads.\n\n> Node的定义特性是它的单线程的基于事件的并发性，这是通过一个默认异步的API实现的。如果您使用其他语言编写过程序，但没有进行过多的JavaScript编码，或者如果您是一名经验丰富的客户端JavaScript程序员，习惯于为web浏览器编写代码，那么使用Node就需要一点调整，任何新的编程语言或环境都是如此。本章从解释节点编程模型开始，重点介绍并发性、处理流数据的节点API和处理二进制数据的节点缓冲区类型。在这些初始部分之后是突出显示和演示一些最重要的节点api的部分，包括那些用于处理文件、网络、进程和线程的api。\n\nOne chapter is not enough to document all of Node’s APIs, but my hope is that this chapter will explain enough of the fundamentals to make you productive with Node, and confident that you can master any new APIs you need.\n\n> 一章不足以记录所有的Node api，但我希望这一章能够解释足够多的基础知识，使您能够高效地使用Node，并确信您能够掌握所需的任何新的api。\n\n#### INSTALLING NODE\n\nNode is open source software. Visit https://nodejs.org to download and install Node for Windows and MacOS. On Linux, you may be able to install Node with your normal package manager, or you can visit https://nodejs.org/en/download to download the binaries directly. If you work on containerized software, you can find official Node Docker images at https://hub.docker.com.\n\n> Node是开源软件。访问https://nodejs.org下载并安装用于Windows和MacOS的节点。在Linux上，您可以使用普通的包管理器安装Node，或者您可以访问https://nodejs.org/en/download来直接下载二进制文件。如果您使用的是容器化软件，您可以在https://hub.docker.com找到官方节点Docker镜像。\n\nIn addition to the Node executable, a Node installation also includes npm, a package manager that enables easy access to a vast ecosystem of JavaScript tools and libraries. The examples in this chapter will use only Node’s built-in packages and will not require npm or any external libraries.\n\n> 除了节点可执行文件之外，节点安装还包括npm，这是一个包管理器，可以方便地访问大量的JavaScript工具和库。本章中的示例将只使用Node的内置包，而不需要npm或任何外部库。\n\nFinally, do not overlook the official Node documentation, available at https://nodejs.org/api and https://nodejs.org/docs/guides. I have found it to be well organized and well written.\n\n> 最后，不要忽视官方节点文档，可以从https://nodejs.org/api和https://nodejs.org/docs/guides获得。我发现它组织得很好，写得也很好。\n\n## 16.1 Node Programming Basics\n\nWe’ll begin this chapter with a quick look at how Node programs are structured and how they interact with the operating system.\n\n> 在本章的开始，我们将快速了解节点程序是如何构建的，以及它们如何与操作系统交互。\n\n### 16.1.1 Console Output\n\nIf you are used to JavaScript programming for web browsers, one of the minor surprises about Node is that console.log() is not just for debugging, but is Node’s easiest way to display a message to the user, or, more generally, to send output to the stdout stream. Here’s the classic “Hello World” program in Node:\n\n> 如果您习惯了web浏览器的JavaScript编程，那么Node的一个小惊喜就是console.log()不仅用于调试，而且是Node向用户显示消息的最简单的方式，或者更一般地，将输出发送到stdout流的最简单方式。下面是Node中的经典“Hello World”程序:\n\n```js\nconsole.log(\"Hello World!\");\n```\n\nThere are lower-level ways to write to stdout, but no fancier or more official way than simply calling console.log().\n\n> 有一些低级别的方法可以写入stdout，但是没有比简单地调用console.log()更高级或更正式的方法了。\n\nIn web browsers, console.log(), console.warn(), and console.error() typically display little icons next to their output in the developer console to indicate the variety of the log message. Node does not do this, but output displayed with console.error() is distinguished from output displayed with console.log() because console.error() writes to the stderr stream. If you’re using Node to write a program that is designed to have stdout redirected to a file or a pipe, you can use console.error() to display text to the console where the user will see it, even though text printed with console.log() is hidden.\n\n> 在web浏览器中，console.log()、console.warn()和console.error()通常在开发人员控制台中它们的输出旁边显示小图标，以指示日志消息的种类。Node不这样做，但是使用console.error()显示的输出与使用console.log()显示的输出是不同的，因为console.error()写入stderr流。如果您正在使用Node编写一个程序，该程序设计为将stdout重定向到文件或管道，那么您可以使用console.error()将文本显示到控制台，用户将在那里看到它，即使使用console.log()打印的文本是隐藏的。\n\n### 16.1.2 Command-Line Arguments and Environment Variables\n\nIf you have previously written Unix-style programs designed to be invoked from a terminal or other command-line interface, you know that these programs typically get their input primarily from command-line arguments and secondarily from environment variables.\n\nNode follows these Unix conventions. A Node program can read its command-line arguments from the array of strings process.argv. The first element of this array is always the path to the Node executable. The second argument is the path to the file of JavaScript code that Node is executing. Any remaining elements in this array are the space-separated arguments that you passed on the command-line when you invoked Node.\n\nFor example, suppose you save this very short Node program to the file argv.js:\n\nconsole.log(process.argv);\nYou can then execute the program and see output like this:\n\n$ node --trace-uncaught argv.js --arg1 --arg2 filename\n[\n  '/usr/local/bin/node',\n  '/private/tmp/argv.js',\n  '--arg1',\n  '--arg2',\n  'filename'\n]\nThere are a couple of things to note here:\n\nThe first and second elements of process.argv will be fully qualified filesystem paths to the Node executable and the file of JavaScript that is being executed, even if you did not type them that way.\n\nCommand-line arguments that are intended for and interpreted by the Node executable itself are consumed by the Node executable and do not appear in process.argv. (The --trace-uncaught command-line argument isn’t actually doing anything useful in the previous example; it is just there to demonstrate that it does not appear in the output.) Any arguments (such as --arg1 and filename) that appear after the name of the JavaScript file will appear in process.argv.\n\nNode programs can also take input from Unix-style environment variables. Node makes these available though the process.env object. The property names of this object are environment variable names, and the property values (always strings) are the values of those variables.\n\nHere is a partial list of environment variables on my system:\n\n$ node -p -e 'process.env'\n{\n  SHELL: '/bin/bash',\n  USER: 'david',\n  PATH: '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin',\n  PWD: '/tmp',\n  LANG: 'en_US.UTF-8',\n  HOME: '/Users/david',\n}\nYou can use node -h or node --help to find out what the -p and -e command-line arguments do. However, as a hint, note that you could rewrite the line above as node --eval 'process.env' --print.\n\n### 16.1.3 Program Life Cycle\nThe node command expects a command-line argument that specifies the file of JavaScript code to be run. This initial file typically imports other modules of JavaScript code, and may also define its own classes and functions. Fundamentally, however, Node executes the JavaScript code in the specified file from top to bottom. Some Node programs exit when they are done executing the last line of code in the file. Often, however, a Node program will keep running long after the initial file has been executed. As we’ll discuss in the following sections, Node programs are often asynchronous and based on callbacks and event handlers. Node programs do not exit until they are done running the initial file and until all callbacks have been called and there are no more pending events. A Node-based server program that listens for incoming network connections will theoretically run forever because it will always be waiting for more events.\n\nA program can force itself to exit by calling process.exit(). Users can usually terminate a Node program by typing Ctrl-C in the terminal window where the program is running. A program can ignore Ctrl-C by registering a signal handler function with process.on(\"SIGINT\", ()=>{}).\n\nIf code in your program throws an exception and no catch clause catches it, the program will print a stack trace and exit. Because of Node’s asynchronous nature, exceptions that occur in callbacks or event handlers must be handled locally or not handled at all, which means that handling exceptions that occur in the asynchronous parts of your program can be a difficult problem. If you don’t want these exceptions to cause your program to completely crash, register a global handler function that will be invoked instead of crashing:\n\nprocess.setUncaughtExceptionCaptureCallback(e => {\n    console.error(\"Uncaught exception:\", e);\n});\nA similar situation arises if a Promise created by your program is rejected and there is no .catch() invocation to handle it. As of Node 13, this is not a fatal error that causes your program to exit, but it does print a verbose error message to the console. In some future version of Node, unhandled Promise rejections are expected to become fatal errors. If you do not want unhandled rejections, to print error messages or terminate your program, register a global handler function:\n\nprocess.on(\"unhandledRejection\", (reason, promise) => {\n    // reason is whatever value would have been passed to a .catch() function\n    // promise is the Promise object that rejected\n});\n### 16.1.4 Node Modules\nChapter 10 documented JavaScript module systems, covering both Node modules and ES6 modules. Because Node was created before JavaScript had a module system, Node had to create its own. Node’s module system uses the require() function to import values into a module and the exports object or the module.exports property to export values from a module. These are a fundamental part of the Node programming model, and they are covered in detail in §10.2.\n\nNode 13 adds support for standard ES6 modules as well as require-based modules (which Node calls “CommonJS modules”). The two module systems are not fully compatible, so this is somewhat tricky to do. Node needs to know—before it loads a module—whether that module will be using require() and module.exports or if it will be using import and export. When Node loads a file of JavaScript code as a CommonJS module, it automatically defines the require() function along with identifiers exports and module, and it does not enable the import and export keywords. On the other hand, when Node loads a file of code as an ES6 module, it must enable the import and export declarations, and it must not define extra identifiers like require, module, and exports.\n\nThe simplest way to tell Node what kind of module it is loading is to encode this information in the file extension. If you save your JavaScript code in a file that ends with .mjs, then Node will always load it as an ES6 module, will expect it to use import and export, and will not provide a require() function. And if you save your code in a file that ends with .cjs, then Node will always treat it as a CommonJS module, will provide a require() function, and will throw a SyntaxError if you use import or export declarations.\n\nFor files that do not have an explicit .mjs or .cjs extension, Node looks for a file named package.json in the same directory as the file and then in each of the containing directories. Once the nearest package.json file is found, Node checks for a top-level type property in the JSON object. If the value of the type property is “module”, then Node loads the file as an ES6 module. If the value of that property is “commonjs”, then Node loads the file as a CommonJS module. Note that you do not need to have a package.json file to run Node programs: when no such file is found (or when the file is found but it does not have a type property), Node defaults to using CommonJS modules. This package.json trick only becomes necessary if you want to use ES6 modules with Node and do not want to use the .mjs file extension.\n\nBecause there is an enormous amount of existing Node code written using CommonJS module format, Node allows ES6 modules to load CommonJS modules using the import keyword. The reverse is not true, however: a CommonJS module cannot use require() to load an ES6 module.\n\n### 16.1.5 The Node Package Manager\nWhen you install Node, you typically get a program named npm as well. This is the Node Package Manager, and it helps you download and manage libraries that your program depends on. npm keeps track of those dependencies (as well as other information about your program) in a file named package.json in the root directory of your project. This package.json file created by npm is where you would add \"type\":\"module\" if you wanted to use ES6 modules for your project.\n\nThis chapter does not cover npm in any detail (but see §17.4 for a little more depth). I’m mentioning it here because unless you write programs that do not use any external libraries, you will almost certainly be using npm or a tool like it. Suppose, for example, that you are going to be developing a web server and plan to use the Express framework (https://expressjs.com) to simplify the task. To get started, you might create a directory for your project, and then, in that directory type npm init. npm will ask you for your project name, version number, etc., and will then create an initial package.json file based on your responses.\n\nNow to start using Express, you’d type npm install express. This tells npm to download the Express library along with all of its dependencies and install all the packages in a local node_modules/ directory:\n```sh\n$ npm install express\nnpm notice created a lockfile as package-lock.json. You should commit this file.\nnpm WARN my-server@1.0.0 No description\nnpm WARN my-server@1.0.0 No repository field.\n\n+ express@4.17.1\nadded 50 packages from 37 contributors and audited 126 packages in 3.058s\nfound 0 vulnerabilities\n```\nWhen you install a package with npm, npm records this dependency—that your project depends on Express—in the package.json file. With this dependency recorded in package.json, you could give another programmer a copy of your code and your package.json, and they could simply type npm install to automatically download and install all of the libraries that your program needs in order to run.\n\n## 16.2 Node Is Asynchronous by Default\nJavaScript is a general-purpose programming language, so it is perfectly possible to write CPU-intensive programs that multiply large matrices or perform complicated statistical analyses. But Node was designed and optimized for programs—like network servers—that are I/O intensive. And in particular, Node was designed to make it possible to easily implement highly concurrent servers that can handle many requests at the same time.\n\nUnlike many programming languages, however, Node does not achieve concurrency with threads. Multithreaded programming is notoriously hard to do correctly, and difficult to debug. Also, threads are a relatively heavyweight abstraction and if you want to write a server that can handle hundreds of concurrent requests, using hundreds of threads may require a prohibitive amount of memory. So Node adopts the single-threaded JavaScript programming model that the web uses, and this turns out to be a vast simplification that makes the creation of network servers a routine skill rather than an arcane one.\n\nTRUE PARALLELISM WITH NODE\nNode programs can run multiple operating system processes, and Node 10 and later support Worker objects (§16.11), which are a kind of thread borrowed from web browsers. If you use multiple processes or create one or more Worker threads and run your program on a system with more than one CPU, then your program will no longer be single-threaded and your program will truly be executing multiple streams of code in parallel. These techniques can be valuable for CPU-intensive operations but are not commonly used for I/O-intensive programs like servers.\n\nIt is worth noting, however, that Node’s processes and Workers avoid the typical complexity of multithreaded programming because interprocess and inter-Worker communication is via message passing and they cannot easily share memory with each other.\n\nNode achieves high levels of concurrency while maintaining a single-threaded programming model by making its API asynchronous and nonblocking by default. Node takes its nonblocking approach very seriously and to an extreme that may surprise you. You probably expect functions that read from and write to the network to be asynchronous, but Node goes further and defines nonblocking asynchronous functions for reading and writing files from the local filesystem. This makes sense, when you think about it: the Node API was designed in the days when spinning hard drives were still the norm and there really were milliseconds of blocking “seek time” while waiting for the disc to spin around before a file operation could begin. And in modern datacenters, the “local” filesystem may actually be across the network somewhere with network latencies on top of drive latencies. But even if reading a file asynchronously seems normal to you, Node takes it still further: the default functions for initiating a network connection or looking up a file modification time, for example, are also nonblocking.\n\nSome functions in Node’s API are synchronous but nonblocking: they run to completion and return without ever needing to block. But most of the interesting functions perform some kind of input or output, and these are asynchronous functions so they can avoid even the tiniest amount of blocking. Node was created before JavaScript had a Promise class, so asynchronous Node APIs are callback-based. (If you have not yet read or have already forgotten Chapter 13, this would be a good time to skip back to that chapter.) Generally, the last argument you pass to an asynchronous Node function is a callback. Node uses error-first callbacks, which are typically invoked with two arguments. The first argument to an error-first callback is normally null in the case where no error occurred, and the second argument is whatever data or response was produced by the original asynchronous function you called. The reason for putting the error argument first is to make it impossible for you to omit it, and you should always check for a non-null value in this argument. If it is an Error object, or even an integer error code or string error message, then something went wrong. In this case, the second argument to your callback function is likely to be null.\n\nThe following code demonstrates how to use the nonblocking readFile() function to read a configuration file, parse it as JSON, and then pass the parsed configuration object to another callback:\n\nconst fs = require(\"fs\");  // Require the filesystem module\n\n// Read a config file, parse its contents as JSON, and pass the\n// resulting value to the callback. If anything goes wrong,\n// print an error message to stderr and invoke the callback with null\nfunction readConfigFile(path, callback) {\n    fs.readFile(path, \"utf8\", (err, text) => {\n        if (err) {    // Something went wrong reading the file\n            console.error(err);\n            callback(null);\n            return;\n        }\n        let data = null;\n        try {\n            data = JSON.parse(text);\n        } catch(e) {  // Something went wrong parsing the file contents\n            console.error(e);\n        }\n        callback(data);\n    });\n}\nNode predates standardized promises, but because it is fairly consistent about its error-first callbacks, it is easy to create Promise-based variants of its callback-based APIs using the util.promisify() wrapper. Here’s how we could rewrite the readConfigFile() function to return a Promise:\n\nconst util = require(\"util\");\nconst fs = require(\"fs\");  // Require the filesystem module\nconst pfs = {              // Promise-based variants of some fs functions\n    readFile: util.promisify(fs.readFile)\n};\n\nfunction readConfigFile(path) {\n    return pfs.readFile(path, \"utf-8\").then(text => {\n        return JSON.parse(text);\n    });\n}\nWe can also simpify the preceding Promise-based function using async and await (again, if you have not yet read through Chapter 13, this would be a good time to do so):\n\nasync function readConfigFile(path) {\n    let text = await pfs.readFile(path, \"utf-8\");\n    return JSON.parse(text);\n}\nThe util.promisify() wrapper can produce a Promise-based version of many Node functions. In Node 10 and later, the fs.promises object has a number of predefined Promise-based functions for working with the filesystem. We’ll discuss them later in this chapter, but note that in the preceding code, we could replace pfs.readFile() with fs.promises.readFile().\n\nWe had said that Node’s programming model is async-by-default. But for programmer convenience, Node does define blocking, synchronous variants of many of its functions, especially in the filesystem module. These functions typically have names that are clearly labeled with Sync at the end.\n\nWhen a server is first starting up and is reading its configuration files, it is not handling network requests yet, and little or no concurrency is actually possible. So in this situation, there is really no need to avoid blocking, and we can safely use blocking functions like fs.readFileSync(). We can drop the async and await from this code and write a purely synchronous version of our readConfigFile() function. Instead of invoking a callback or returning a Promise, this function simply returns the parsed JSON value or throws an exception:\n\nconst fs = require(\"fs\");\nfunction readConfigFileSync(path) {\n    let text = fs.readFileSync(path, \"utf-8\");\n    return JSON.parse(text);\n}\nIn addition to its error-first two-argument callbacks, Node also has a number of APIs that use event-based asynchrony, typically for handling streaming data. We’ll cover Node events in more detail later.\n\nNow that we’ve discussed Node’s aggressively nonblocking API, let’s turn back to the topic of concurrency. Node’s built-in nonblocking functions work using the operating system’s version of callbacks and event handlers. When you call one of these functions, Node takes action to get the operation started, then registers some kind of event handler with the operating system so that it will be notified when the operation is complete. The callback you passed to the Node function gets stored internally so that Node can invoke your callback when the operating system sends the appropriate event to Node.\n\nThis kind of concurrency is often called event-based concurrency. At its core, Node has a single thread that runs an “event loop.” When a Node program starts, it runs whatever code you’ve told it to run. This code presumably calls at least one nonblocking function causing a callback or event handler to be registered with the operating system. (If not, then you’ve written a synchronous Node program, and Node simply exits when it reaches the end.) When Node reaches the end of your program, it blocks until an event happens, at which time the OS starts it running again. Node maps the OS event to the JavaScript callback you registered and then invokes that function. Your callback function may invoke more nonblocking Node functions, causing more OS event handlers to be registered. Once your callback function is done running, Node goes back to sleep again and the cycle repeats.\n\nFor web servers and other I/O-intensive applications that spend most of their time waiting for input and output, this style of event-based concurrency is efficient and effective. A web server can concurrently handle requests from 50 different clients without needing 50 different threads as long as it uses nonblocking APIs and there is some kind of internal mapping from network sockets to JavaScript functions to invoke when activity occurs on those sockets.\n\n## 16.3 Buffers\nOne of the datatypes you’re likely to use frequently in Node—especially when reading data from files or from the network—is the Buffer class. A Buffer is a lot like a string, except that it is a sequence of bytes instead of a sequence of characters. Node was created before core JavaScript supported typed arrays (see §11.2) and there was no Uint8Array to represent an array of unsigned bytes. Node defined the Buffer class to fill that need. Now that Uint8Array is part of the JavaScript language, Node’s Buffer class is a subclass of Uint8Array.\n\nWhat distinguishes Buffer from its Uint8Array superclass is that it is designed to interoperate with JavaScript strings: the bytes in a buffer can be initialized from character strings or converted to character strings. A character encoding maps each character in some set of characters to an integer. Given a string of text and a character encoding, we can encode the characters in the string into a sequence of bytes. And given a (properly encoded) sequence of bytes and a character encoding, we can decode those bytes into a sequence of characters. Node’s Buffer class has methods that perform both encoding and decoding, and you can recognize these methods because they expect an encoding argument that specifies the encoding to be used.\n\nEncodings in Node are specified by name, as strings. The supported encodings are:\n\n\"utf8\"\nThis is the default when no encoding is specified, and is the Unicode encoding you are most likely to use.\n\n\"utf16le\"\nTwo-byte Unicode characters, with little-endian ordering. Codepoints above \\uffff are encoded as a pair of two-byte sequences. Encoding \"ucs2\" is an alias.\n\n\"latin1\"\nThe one-byte-per-character ISO-8859-1 encoding that defines a character set suitable for many Western European languages. Because there is a one-to-one mapping between bytes and latin-1 characters, this encoding is also known as \"binary\".\n\n\"ascii\"\nThe 7-bit English-only ASCII encoding, a strict subset of the \"utf8\" encoding.\n\n\"hex\"\nThis encoding converts each byte to a pair of ASCII hexadecimal digits.\n\n\"base64\"\nThis encoding converts each sequence of three bytes into a sequence of four ascii characters.\n\nHere is some example code that demonstrates how to work with Buffers and how to convert to and from strings:\n\nlet b = Buffer.from([0x41, 0x42, 0x43]);          // <Buffer 41 42 43>\nb.toString()                                      // => \"ABC\"; default \"utf8\"\nb.toString(\"hex\")                                 // => \"414243\"\n\nlet computer = Buffer.from(\"IBM3111\", \"ascii\");   // Convert string to Buffer\nfor(let i = 0; i < computer.length; i++) {        // Use Buffer as byte array\n    computer[i]--;                                // Buffers are mutable\n}\ncomputer.toString(\"ascii\")                        // => \"HAL2000\"\ncomputer.subarray(0,3).map(x=>x+1).toString()     // => \"IBM\"\n\n// Create new \"empty\" buffers with Buffer.alloc()\nlet zeros = Buffer.alloc(1024);                   // 1024 zeros\nlet ones = Buffer.alloc(128, 1);                  // 128 ones\nlet dead = Buffer.alloc(1024, \"DEADBEEF\", \"hex\"); // Repeating pattern of bytes\n\n// Buffers have methods for reading and writing multi-byte values\n// from and to a buffer at any specified offset.\ndead.readUInt32BE(0)       // => 0xDEADBEEF\ndead.readUInt32BE(1)       // => 0xADBEEFDE\ndead.readBigUInt64BE(6)    // => 0xBEEFDEADBEEFDEADn\ndead.readUInt32LE(1020)    // => 0xEFBEADDE\nIf you write a Node program that actually manipulates binary data, you may find yourself using the Buffer class extensively. On the other hand, if you are just working with text that is read from or written to a file or the network, then you may only encounter Buffer as an intermediate representation of your data. A number of Node APIs can take input or return output as either strings or Buffer objects. Typically, if you pass a string, or expect a string to be returned, from one of these APIs, you’ll need to specify the name of the text encoding you want to use. And if you do this, then you may not need to use a Buffer object at all.\n\n## 16.4 Events and EventEmitter\nAs described, all of Node’s APIs are asynchronous by default. For many of them, this asynchrony takes the form of two-argument error-first callbacks that are invoked when the requested operation is complete. But some of the more complicated APIs are event-based instead. This is typically the case when the API is designed around an object rather than a function, or when a callback function needs to be invoked multiple times, or when there are multiple types of callback functions that may be required. Consider the net.Server class, for example: an object of this type is a server socket that is used to accept incoming connections from clients. It emits a “listening” event when it first starts listening for connections, a “connection” event every time a client connects, and a “close” event when it has been closed and is no longer listening.\n\nIn Node, objects that emit events are instances of EventEmitter or a subclass of EventEmitter:\n\nconst EventEmitter = require(\"events\"); // Module name does not match class name\nconst net = require(\"net\");\nlet server = new net.Server();          // create a Server object\nserver instanceof EventEmitter          // => true: Servers are EventEmitters\nThe main feature of EventEmitters is that they allow you to register event handler functions with the on() method. EventEmitters can emit multiple types of events, and event types are identified by name. To register an event handler, call the on() method, passing the name of the event type and the function that should be invoked when an event of that type occurs. EventEmitters can invoke handler functions with any number of arguments, and you need to read the documentation for a specific kind of event from a specific EventEmitter to know what arguments you should expect to be passed:\n\nconst net = require(\"net\");\nlet server = new net.Server();          // create a Server object\nserver.on(\"connection\", socket => {     // Listen for \"connection\" events\n    // Server \"connection\" events are passed a socket object\n    // for the client that just connected. Here we send some data\n    // to the client and disconnect.\n    socket.end(\"Hello World\", \"utf8\");\n});\nIf you prefer more explicit method names for registering event listeners, you can also use addListener(). And you can remove a previously registered event listener with off() or removeListener(). As a special case, you can register an event listener that will be automatically removed after it is triggered for the first time by calling once() instead of on().\n\nWhen an event of a particular type occurs for a particular EventEmitter object, Node invokes all of the handler functions that are currently registered on that EventEmitter for events of that type. They are invoked in order from the first registered to the last registered. If there is more than one handler function, they are invoked sequentially on a single thread: there is no parallelism in Node, remember. And, importantly, event handling functions are invoked synchronously, not asynchronously. What this means is that the emit() method does not queue up event handlers to be invoked at some later time. emit() invokes all the registered handlers, one after the other, and does not return until the last event handler has returned.\n\nWhat this means, in effect, is that when one of the built-in Node APIs emits an event, that API is basically blocking on your event handlers. If you write an event handler that calls a blocking function like fs.readFileSync(), no further event handling will happen until your synchronous file read is complete. If your program is one—like a network server—that needs to be responsive, then it is important that you keep your event handler functions nonblocking and fast. If you need to do a lot of computation when an event occurs, it is often best to use the handler to schedule that computation asynchronously using setTimeout() (see §11.10). Node also defines setImmediate(), which schedules a function to be invoked immediately after all pending callbacks and events have been handled.\n\nThe EventEmitter class also defines an emit() method that causes the registered event handler functions to be invoked. This is useful if you are defining your own event-based API, but is not commonly used when you’re just programming with existing APIs. emit() must be invoked with the name of the event type as its first argument. Any additional arguments that are passed to emit() become arguments to the registered event handler functions. The handler functions are also invoked with the this value set to the EventEmitter object itself, which is often convenient. (Remember, though, that arrow functions always use the this value of the context in which they are defined, and they cannot be invoked with any other this value. Nevertheless, arrow functions are often the most convenient way to write event handlers.)\n\nAny value returned by an event handler function is ignored. If an event handler function throws an exception, however, it propagates out from the emit() call and prevents the execution of any handler functions that were registered after the one that threw the exception.\n\nRecall that Node’s callback-based APIs use error-first callbacks, and it is important that you always check the first callback argument to see if an error occurred. With event-based APIs, the equivalent is “error” events. Since event-based APIs are often used for networking and other forms of streaming I/O, they are vulnerable to unpredictable asynchronous errors, and most EventEmitters define an “error” event that they emit when an error occurs. Whenever you use an event-based API, you should make it a habit to register a handler for “error” events. “Error” events get special treatment by the EventEmitter class. If emit() is called to emit an “error” event, and if there are no handlers registered for that event type, then an exception will be thrown. Since this occurs asynchronously, there is no way for you to handle the exception in a catch block, so this kind of error typically causes your program to exit.\n\n## 16.5 Streams\nWhen implementing an algorithm to process data, it is almost always easiest to read all the data into memory, do the processing, and then write the data out. For example, you could write a Node function to copy a file like this.1\n\nconst fs = require(\"fs\");\n\n// An asynchronous but nonstreaming (and therefore inefficient) function.\nfunction copyFile(sourceFilename, destinationFilename, callback) {\n    fs.readFile(sourceFilename, (err, buffer) => {\n        if (err) {\n            callback(err);\n        } else {\n            fs.writeFile(destinationFilename, buffer, callback);\n        }\n    });\n}\nThis copyFile() function uses asynchronous functions and callbacks, so it does not block and is suitable for use in concurrent programs like servers. But notice that it must allocate enough memory to hold the entire contents of the file in memory at once. This may be fine in some use cases, but it starts to fail if the files to be copied are very large, or if your program is highly concurrent and there may be many files being copied at the same time. Another shortcoming of this copyFile() implementation is that it cannot start writing the new file until it has finished reading the old file.\n\nThe solution to these problems is to use streaming algorithms where data “flows” into your program, is processed, and then flows out of your program. The idea is that your algorithm processes the data in small chunks and the full dataset is never held in memory at once. When streaming solutions are possible, they are more memory efficient and can also be faster. Node’s networking APIs are stream-based and Node’s filesystem module defines streaming APIs for reading and writing files, so you are likely to use a streaming API in many of the Node programs that you write. We’ll see a streaming version of the copyFile() function in “Flowing mode”.\n\nNode supports four basic stream types:\n\nReadable\nReadable streams are sources of data. The stream returned by fs.createReadStream(), for example, is a stream from which the content of a specified file can be read. process.stdin is another Readable stream that returns data from standard input.\n\nWritable\nWritable streams are sinks or destinations for data. The return value of fs.createWriteStream(), for example, is a Writable stream: it allows data to be written to it in chunks, and outputs all of that data to a specified file.\n\nDuplex\nDuplex streams combine a Readable stream and a Writable stream into one object. The Socket objects returned by net.connect() and other Node networking APIs, for example, are Duplex streams. If you write to a socket, your data is sent across the network to whatever computer the socket is connected to. And if you read from a socket, you access the data written by that other computer.\n\nTransform\nTransform streams are also readable and writable, but they differ from Duplex streams in an important way: data written to a Transform stream becomes readable—usually in some transformed form—from the same stream. The zlib.createGzip() function, for example, returns a Transform stream that compresses (with the gzip algorithm) the data written to it. In a similar way, the crypto.createCipheriv() function returns a Transform stream that encrypts or decrypts data that is written to it.\n\nBy default, streams read and write buffers. If you call the setEncoding() method of a Readable stream, it will return decoded strings to you instead of Buffer objects. And if you write a string to a Writable buffer, it will be automatically encoded using the buffer’s default encoding or whatever encoding you specify. Node’s stream API also supports an “object mode” where streams read and write objects more complex than buffers and strings. None of Node’s core APIs use this object mode, but you may encounter it in other libraries.\n\nReadable streams have to read their data from somewhere, and Writable streams have to write their data to somewhere, so every stream has two ends: an input and an output or a source and a destination. The tricky thing about stream-based APIs is that the two ends of the stream will almost always flow at different speeds. Perhaps the code that reads from a stream wants to read and process data more quickly than the data is actually being written into the stream. Or the reverse: perhaps data is written to a stream more quickly than it can be read and pulled out of the stream on the other end. Stream implementations almost always include an internal buffer to hold data that has been written but not yet read. Buffering helps to ensure that there is data available to read when it’s requested, and that there is space to hold data when it is written. But neither of these things can ever be guaranteed, and it is the nature of stream-based programming that readers will sometimes have to wait for data to be written (because the stream buffer is empty), and writers will sometimes have to wait for data to be read (because the stream buffer is full).\n\nIn programming environments that use thread-based concurrency, stream APIs typically have blocking calls: a call to read data does not return until data arrives in the stream and a call to write data blocks until there is enough room in the stream’s internal buffer to accommodate the new data. With an event-based concurrency model, however, blocking calls do not make sense, and Node’s stream APIs are event- and callback-based. Unlike other Node APIs, there are not “Sync” versions of the methods that will be described later in this chapter.\n\nThe need to coordinate stream readability (buffer not empty) and writability (buffer not full) via events makes Node’s stream APIs somewhat complicated. This is compounded by the fact that these APIs have evolved and changed over the years: for Readable streams, there are two completely distinct APIs that you can use. Despite the complexity, it is worth understanding and mastering Node’s streaming APIs because they enable high-throughput I/O in your programs.\n\nThe subsections that follow demonstrate how to read and write from Node’s stream classes.\n\n### 16.5.1 Pipes\nSometimes, you need to read data from a stream simply to turn around and write that same data to another stream. Imagine, for example, that you are writing a simple HTTP server that serves a directory of static files. In this case, you will need to read data from a file input stream and write it out to a network socket. But instead of writing your own code to handle the reading and writing, you can instead simply connect the two sockets together as a “pipe” and let Node handle the complexities for you. Simply pass the Writable stream to the pipe() method of the Readable stream:\n\nconst fs = require(\"fs\");\n\nfunction pipeFileToSocket(filename, socket) {\n    fs.createReadStream(filename).pipe(socket);\n}\nThe following utility function pipes one stream to another and invokes a callback when done or when an error occurs:\n\nfunction pipe(readable, writable, callback) {\n    // First, set up error handling\n    function handleError(err) {\n        readable.close();\n        writable.close();\n        callback(err);\n    }\n\n    // Next define the pipe and handle the normal termination case\n    readable\n        .on(\"error\", handleError)\n        .pipe(writable)\n        .on(\"error\", handleError)\n        .on(\"finish\", callback);\n}\nTransform streams are particularly useful with pipes, and create pipelines that involve more than two streams. Here’s an example function that compresses a file:\n\nconst fs = require(\"fs\");\nconst zlib = require(\"zlib\");\n\nfunction gzip(filename, callback) {\n    // Create the streams\n    let source = fs.createReadStream(filename);\n    let destination = fs.createWriteStream(filename + \".gz\");\n    let gzipper = zlib.createGzip();\n\n    // Set up the pipeline\n    source\n        .on(\"error\", callback)   // call callback on read error\n        .pipe(gzipper)\n        .pipe(destination)\n        .on(\"error\", callback)   // call callback on write error\n        .on(\"finish\", callback); // call callback when writing is complete\n}\nUsing the pipe() method to copy data from a Readable stream to a Writable stream is easy, but in practice, you often need to process the data somehow as it streams through your program. One way to do this is to implement your own Transform stream to do that processing, and this approach allows you to avoid manually reading and writing the streams. Here, for example, is a function that works like the Unix grep utility: it reads lines of text from an input stream, but writes only the lines that match a specified regular expression:\n\nconst stream = require(\"stream\");\n\nclass GrepStream extends stream.Transform {\n    constructor(pattern) {\n        super({decodeStrings: false});// Don't convert strings back to buffers\n        this.pattern = pattern;       // The regular expression we want to match\n        this.incompleteLine = \"\";     // Any remnant of the last chunk of data\n    }\n\n    // This method is invoked when there is a string ready to be\n    // transformed. It should pass transformed data to the specified\n    // callback function. We expect string input so this stream should\n    // only be connected to readable streams that have had\n    // setEncoding() called on them.\n    _transform(chunk, encoding, callback) {\n        if (typeof chunk !== \"string\") {\n            callback(new Error(\"Expected a string but got a buffer\"));\n            return;\n        }\n        // Add the chunk to any previously incomplete line and break\n        // everything into lines\n        let lines = (this.incompleteLine + chunk).split(\"\\n\");\n\n        // The last element of the array is the new incomplete line\n        this.incompleteLine = lines.pop();\n\n        // Find all matching lines\n        let output = lines                     // Start with all complete lines,\n            .filter(l => this.pattern.test(l)) // filter them for matches,\n            .join(\"\\n\");                       // and join them back up.\n\n        // If anything matched, add a final newline\n        if (output) {\n            output += \"\\n\";\n        }\n\n        // Always call the callback even if there is no output\n        callback(null, output);\n    }\n\n    // This is called right before the stream is closed.\n    // It is our chance to write out any last data.\n    _flush(callback) {\n        // If we still have an incomplete line, and it matches\n        // pass it to the callback\n        if (this.pattern.test(this.incompleteLine)) {\n            callback(null, this.incompleteLine + \"\\n\");\n        }\n    }\n}\n\n// Now we can write a program like 'grep' with this class.\nlet pattern = new RegExp(process.argv[2]); // Get a RegExp from command line.\nprocess.stdin                              // Start with standard input,\n    .setEncoding(\"utf8\")                   // read it as Unicode strings,\n    .pipe(new GrepStream(pattern))         // pipe it to our GrepStream,\n    .pipe(process.stdout)                  // and pipe that to standard out.\n    .on(\"error\", () => process.exit());    // Exit gracefully if stdout closes.\n### 16.5.2 Asynchronous Iteration\nIn Node 12 and later, Readable streams are asynchronous iterators, which means that within an async function you can use a for/await loop to read string or Buffer chunks from a stream using code that is structured like synchronous code would be. (See §13.4 for more on asynchronous iterators and for/await loops.)\n\nUsing an asynchronous iterator is almost as easy as using the pipe() method, and is probably easier when you need to process each chunk you read in some way. Here’s how we could rewrite the grep program in the previous section using an async function and a for/await loop:\n\n// Read lines of text from the source stream, and write any lines\n// that match the specified pattern to the destination stream.\nasync function grep(source, destination, pattern, encoding=\"utf8\") {\n    // Set up the source stream for reading strings, not Buffers\n    source.setEncoding(encoding);\n\n    // Set an error handler on the destination stream in case standard\n    // output closes unexpectedly (when piping output to `head`, e.g.)\n    destination.on(\"error\", err => process.exit());\n\n    // The chunks we read are unlikely to end with a newline, so each will\n    // probably have a partial line at the end. Track that here\n    let incompleteLine = \"\";\n\n    // Use a for/await loop to asynchronously read chunks from the input stream\n    for await (let chunk of source) {\n        // Split the end of the last chunk plus this one into lines\n        let lines = (incompleteLine + chunk).split(\"\\n\");\n        // The last line is incomplete\n        incompleteLine = lines.pop();\n        // Now loop through the lines and write any matches to the destination\n        for(let line of lines) {\n            if (pattern.test(line)) {\n                destination.write(line + \"\\n\", encoding);\n            }\n        }\n    }\n    // Finally, check for a match on any trailing text.\n    if (pattern.test(incompleteLine)) {\n        destination.write(incompleteLine + \"\\n\", encoding);\n    }\n}\n\nlet pattern = new RegExp(process.argv[2]);   // Get a RegExp from command line.\ngrep(process.stdin, process.stdout, pattern) // Call the async grep() function.\n    .catch(err => {                          // Handle asynchronous exceptions.\n        console.error(err);\n        process.exit();\n    });\n### 16.5.3 Writing to Streams and Handling Backpressure\nThe async grep() function in the preceding code example demonstrated how to use a Readable stream as an asynchronous iterator, but it also demonstrated that you can write data to a Writable stream simply by passing it to the write() method. The write() method takes a buffer or string as the first argument. (Object streams expect other kinds of objects, but are beyond the scope of this chapter.) If you pass a buffer, the bytes of that buffer will be written directly. If you pass a string, it will be encoded to a buffer of bytes before being written. Writable streams have a default encoding that is used when you pass a string as the only argument to write(). The default encoding is typically “utf8,” but you can set it explicitly by calling setDefaultEncoding() on the Writable stream. Alternatively, when you pass a string as the first argument to write() you can pass an encoding name as the second argument.\n\nwrite() optionally takes a callback function as its third argument. This will be invoked when the data has actually been written and is no longer in the Writable stream’s internal buffer. (This callback may also be invoked if an error occurs, but this is not guaranteed. You should register an “error” event handler on the Writable stream to detect errors.)\n\nThe write() method has a very important return value. When you call write() on a stream, it will always accept and buffer the chunk of data you have passed. It then returns true if the internal buffer is not yet full. Or, if the buffer is now full or overfull, it returns false. This return value is advisory, and you can ignore it—Writable streams will enlarge their internal buffer as much as needed if you keep calling write(). But remember that the reason to use a streaming API in the first place is to avoid the cost of keeping lots of data in memory at once.\n\nA return value of false from the write() method is a form of backpressure: a message from the stream that you have written data more quickly than it can be handled. The proper response to this kind of backpressure is to stop calling write() until the stream emits a “drain” event, signaling that there is once again room in the buffer. Here, for example, is a function that writes to a stream, and then invokes a callback when it is OK to write more data to the stream:\n\nfunction write(stream, chunk, callback) {\n    // Write the specified chunk to the specified stream\n    let hasMoreRoom = stream.write(chunk);\n\n    // Check the return value of the write() method:\n    if (hasMoreRoom) {                  // If it returned true, then\n        setImmediate(callback);         // invoke callback asynchronously.\n    } else {                            // If it returned false, then\n        stream.once(\"drain\", callback); // invoke callback on drain event.\n    }\n}\nThe fact that it is sometimes OK to call write() multiple times in a row and sometimes you have to wait for an event between writes makes for awkward algorithms. This is one of the reasons that using the pipe() method is so appealing: when you use pipe(), Node handles backpressure for you automatically.\n\nIf you are using await and async in your program, and are treating Readable streams as asynchronous iterators, it is straightforward to implement a Promise-based version of the write() utility function above to properly handle backpressure. In the async grep() function we just looked at, we did not handle backpressure. The async copy() function in the following example demonstrates how it can be done correctly. Note that this function just copies chunks from a source stream to a destination stream and calling copy(source, destination) is much like calling source.pipe(destination):\n\n// This function writes the specified chunk to the specified stream and\n// returns a Promise that will be fulfilled when it is OK to write again.\n// Because it returns a Promise, it can be used with await.\nfunction write(stream, chunk) {\n    // Write the specified chunk to the specified stream\n    let hasMoreRoom = stream.write(chunk);\n\n    if (hasMoreRoom) {                     // If buffer is not full, return\n        return Promise.resolve(null);      // an already resolved Promise object\n    } else {\n        return new Promise(resolve => {    // Otherwise, return a Promise that\n            stream.once(\"drain\", resolve); // resolves on the drain event.\n        });\n    }\n}\n\n// Copy data from the source stream to the destination stream\n// respecting backpressure from the destination stream.\n// This is much like calling source.pipe(destination).\nasync function copy(source, destination) {\n    // Set an error handler on the destination stream in case standard\n    // output closes unexpectedly (when piping output to `head`, e.g.)\n    destination.on(\"error\", err => process.exit());\n\n    // Use a for/await loop to asynchronously read chunks from the input stream\n    for await (let chunk of source) {\n        // Write the chunk and wait until there is more room in the buffer.\n        await write(destination, chunk);\n    }\n}\n\n// Copy standard input to standard output\ncopy(process.stdin, process.stdout);\nBefore we conclude this discussion of writing to streams, note again that failing to respond to backpressure can cause your program to use more memory than it should when the internal buffer of a Writable stream overflows and grows larger and larger. If you are writing a network server, this can be a remotely exploitable security issue. Suppose you write an HTTP server that delivers files over the network, but you didn’t use pipe() and you didn’t take the time to handle backpressure from the write() method. An attacker could write an HTTP client that initiates requests for large files (such as images) but never actually reads the body of the request. Since the client is not reading the data over the network, and the server isn’t responding to backpressure, buffers on the server are going to overflow. With enough concurrent connections from the attacker, this can turn into a denial-of-service attack that slows your server down or even crashes it.\n\n### 16.5.4 Reading Streams with Events\nNode’s readable streams have two modes, each of which has its own API for reading. If you can’t use pipes or asynchronous iteration in your program, you will need to pick one of these two event-based APIs for handling streams. It is important that you use only one or the other and do not mix the two APIs.\n\nFLOWING MODE\nIn flowing mode, when readable data arrives, it is immediately emitted in the form of a “data” event. To read from a stream in this mode, simply register an event handler for “data” events, and the stream will push chunks of data (buffers or strings) to you as soon as they becomes available. Note that there is no need to call the read() method in flowing mode: you only need to handle “data” events. Note that newly created streams do not start off in flowing mode. Registering a “data” event handler switches a stream into flowing mode. Conveniently, this means that a stream does not emit “data” events until you register the first “data” event handler.\n\nIf you are using flowing mode to read data from a Readable stream, process it, then write it to a Writable stream, then you may need to handle backpressure from the Writable stream. If the write() method returns false to indicate that the write buffer is full, you can call pause() on the Readable stream to temporarily stop data events. Then, when you get a “drain” event from the Writable stream, you can call resume() on the Readable stream to start the “data” events flowing again.\n\nA stream in flowing mode emits an “end” event when the end of the stream is reached. This event indicates that no more “data” events will ever be emitted. And, as with all streams, an “error” event is emitted if an error occurs.\n\nAt the beginning of this section on streams, we showed a nonstreaming copyFile() function and promised a better version to come. The following code shows how to implement a streaming copyFile() function that uses the flowing mode API and handles backpressure. This would have been easier to implement with a pipe() call, but it serves here as a useful demonstration of the multiple event handlers that are used to coordinate data flow from one stream to the other.\n\nconst fs = require(\"fs\");\n\n// A streaming file copy function, using \"flowing mode\".\n// Copies the contents of the named source file to the named destination file.\n// On success, invokes the callback with a null argument. On error,\n// invokes the callback with an Error object.\nfunction copyFile(sourceFilename, destinationFilename, callback) {\n    let input = fs.createReadStream(sourceFilename);\n    let output = fs.createWriteStream(destinationFilename);\n\n    input.on(\"data\", (chunk) => {          // When we get new data,\n        let hasRoom = output.write(chunk); // write it to the output stream.\n        if (!hasRoom) {                    // If the output stream is full\n            input.pause();                 // then pause the input stream.\n        }\n    });\n    input.on(\"end\", () => {                // When we reach the end of input,\n        output.end();                      // tell the output stream to end.\n    });\n    input.on(\"error\", err => {             // If we get an error on the input,\n        callback(err);                     // call the callback with the error\n        process.exit();                    // and quit.\n    });\n\n    output.on(\"drain\", () => {             // When the output is no longer full,\n        input.resume();                    // resume data events on the input\n    });\n    output.on(\"error\", err => {            // If we get an error on the output,\n        callback(err);                     // call the callback with the error\n        process.exit();                    // and quit.\n    });\n    output.on(\"finish\", () => {            // When output is fully written\n        callback(null);                    // call the callback with no error.\n    });\n}\n\n// Here's a simple command-line utility to copy files\nlet from = process.argv[2], to = process.argv[3];\nconsole.log(`Copying file ${from} to ${to}...`);\ncopyFile(from, to, err => {\n    if (err) {\n        console.error(err);\n    } else {\n        console.log(\"done.\");\n    }\n});\nPAUSED MODE\nThe other mode for Readable streams is “paused mode.” This is the mode that streams start in. If you never register a “data” event handler and never call the pipe() method, then a Readable stream remains in paused mode. In paused mode, the stream does not push data to you in the form of “data” events. Instead, you pull data from the stream by explicitly calling its read() method. This is not a blocking call, and if there is no data available to read on the stream, it will return null. Since there is not a synchronous API to wait for data, the paused mode API is also event-based. A Readable stream in paused mode emits “readable” events when data becomes available to read on the stream. In response, your code should call the read() method to read that data. You must do this in a loop, calling read() repeatedly until it returns null. It is necessary to completely drain the stream’s buffer like this in order to trigger a new “readable” event in the future. If you stop calling read() while there is still readable data, you will not get another “readable” event and your program is likely to hang.\n\nStreams in paused mode emit “end” and “error” events just like flowing mode streams do. If you are writing a program that reads data from a Readable stream and writes it to a Writable stream, then paused mode may not be a good choice. In order to properly handle backpressure, you only want to read when the input stream is readable and the output stream is not backed up. In paused mode, that means reading and writing until read() returns null or write() returns false, and then starting reading or writing again on a readable or drain event. This is inelegant, and you may find that flowing mode (or pipes) is easier in this case.\n\nThe following code demonstrates how you can compute a SHA256 hash for the contents of a specified file. It uses a Readable stream in paused mode to read the contents of a file in chunks, then passes each chunk to the object that computes the hash. (Note that in Node 12 and later, it would be simpler to write this function using a for/await loop.)\n\nconst fs = require(\"fs\");\nconst crypto = require(\"crypto\");\n\n// Compute a sha256 hash of the contents of the named file and pass the\n// hash (as a string) to the specified error-first callback function.\nfunction sha256(filename, callback) {\n    let input = fs.createReadStream(filename); // The data stream.\n    let hasher = crypto.createHash(\"sha256\");  // For computing the hash.\n\n    input.on(\"readable\", () => {         // When there is data ready to read\n        let chunk;\n        while(chunk = input.read()) {    // Read a chunk, and if non-null,\n            hasher.update(chunk);        // pass it to the hasher,\n        }                                // and keep looping until not readable\n    });\n    input.on(\"end\", () => {              // At the end of the stream,\n        let hash = hasher.digest(\"hex\"); // compute the hash,\n        callback(null, hash);            // and pass it to the callback.\n    });\n    input.on(\"error\", callback);         // On error, call callback\n}\n\n// Here's a simple command-line utility to compute the hash of a file\nsha256(process.argv[2], (err, hash) => { // Pass filename from command line.\n    if (err) {                           // If we get an error\n        console.error(err.toString());   // print it as an error.\n    } else {                             // Otherwise,\n        console.log(hash);               // print the hash string.\n    }\n});\n## 16.6 Process, CPU, and Operating System Details\nThe global Process object has a number of useful properties and functions that generally relate to the state of the currently running Node process. Consult the Node documentation for complete details, but here are some properties and functions you should be aware of:\n\nprocess.argv            // An array of command-line arguments.\nprocess.arch            // The CPU architecture: \"x64\", for example.\nprocess.cwd()           // Returns the current working directory.\nprocess.chdir()         // Sets the current working directory.\nprocess.cpuUsage()      // Reports CPU usage.\nprocess.env             // An object of environment variables.\nprocess.execPath        // The absolute filesystem path to the node executable.\nprocess.exit()          // Terminates the program.\nprocess.exitCode        // An integer code to be reported when the program exits.\nprocess.getuid()        // Return the Unix user id of the current user.\nprocess.hrtime.bigint() // Return a \"high-resolution\" nanosecond timestamp.\nprocess.kill()          // Send a signal to another process.\nprocess.memoryUsage()   // Return an object with memory usage details.\nprocess.nextTick()      // Like setImmediate(), invoke a function soon.\nprocess.pid             // The process id of the current process.\nprocess.ppid            // The parent process id.\nprocess.platform        // The OS: \"linux\", \"darwin\", or \"win32\", for example.\nprocess.resourceUsage() // Return an object with resource usage details.\nprocess.setuid()        // Sets the current user, by id or name.\nprocess.title           // The process name that appears in `ps` listings.\nprocess.umask()         // Set or return the default permissions for new files.\nprocess.uptime()        // Return Node's uptime in seconds.\nprocess.version         // Node's version string.\nprocess.versions        // Version strings for the libraries Node depends on.\nThe “os” module (which, unlike process, needs to be explicitly loaded with require()) provides access to similarly low-level details about the computer and operating system that Node is running on. You may never need to use any of these features, but it is worth knowing that Node makes them available:\n\nconst os = require(\"os\");\nos.arch()              // Returns CPU architecture. \"x64\" or \"arm\", for example.\nos.constants           // Useful constants such as os.constants.signals.SIGINT.\nos.cpus()              // Data about system CPU cores, including usage times.\nos.endianness()        // The CPU's native endianness \"BE\" or \"LE\".\nos.EOL                 // The OS native line terminator: \"\\n\" or \"\\r\\n\".\nos.freemem()           // Returns the amount of free RAM in bytes.\nos.getPriority()       // Returns the OS scheduling priority of a process.\nos.homedir()           // Returns the current user's home directory.\nos.hostname()          // Returns the hostname of the computer.\nos.loadavg()           // Returns the 1, 5, and 15-minute load averages.\nos.networkInterfaces() // Returns details about available network. connections.\nos.platform()          // Returns OS: \"linux\", \"darwin\", or \"win32\", for example.\nos.release()           // Returns the version number of the OS.\nos.setPriority()       // Attempts to set the scheduling priority for a process.\nos.tmpdir()            // Returns the default temporary directory.\nos.totalmem()          // Returns the total amount of RAM in bytes.\nos.type()              // Returns OS: \"Linux\", \"Darwin\", or \"Windows_NT\", e.g.\nos.uptime()            // Returns the system uptime in seconds.\nos.userInfo()          // Returns uid, username, home, and shell of current user.\n## 16.7 Working with Files\nNode’s “fs” module is a comprehensive API for working with files and directories. It is complemented by the “path” module, which defines utility functions for working with file and directory names. The “fs” module contains a handful of high-level functions for easily reading, writing, and copying files. But most of the functions in the module are low-level JavaScript bindings to Unix system calls (and their equivalents on Windows). If you have worked with low-level filesystem calls before (in C or other languages), then the Node API will be familiar to you. If not, you may find parts of the “fs” API to be terse and unintuitive. The function to delete a file, for example, is called unlink().\n\nThe “fs” module defines a large API, mainly because there are usually multiple variants of each fundamental operation. As discussed at the beginning of the chapter, most functions such as fs.readFile() are nonblocking, callback-based, and asynchronous. Typically, though, each of these functions has a synchronous blocking variant, such as fs.readFileSync(). In Node 10 and later, many of these functions also have a Promise-based asynchronous variant such as fs.promises.readFile(). Most “fs” functions take a string as their first argument, specifying the path (filename plus optional directory names) to the file that is to be operated on. But a number of these functions also support a variant that takes an integer “file descriptor” as the first argument instead of a path. These variants have names that begin with the letter “f.” For example, fs.truncate() truncates a file specified by path, and fs.ftruncate() truncates a file specified by file descriptor. There is a Promise-based fs.promises.truncate() that expects a path and another Promise-based version that is implemented as a method of a FileHandle object. (The FileHandle class is the equivalent of a file descriptor in the Promise-based API.) Finally, there are a handful of functions in the “fs” module that have variants whose names are prefixed with the letter “l.” These “l” variants are like the base function but do not follow symbolic links in the filesystem and instead operate directly on the symbolic links themselves.\n\n### 16.7.1 Paths, File Descriptors, and FileHandles\nIn order to use the “fs” module to work with files, you first need to be able to name the file you want to work with. Files are most often specified by path, which means the name of the file itself, plus the hierarchy of directories in which the file appears. If a path is absolute, it means that directories all the way up to the filesystem root are specified. Otherwise, the path is relative and is only meaningful in relation to some other path, usually the current working directory. Working with paths can be a little tricky because different operating systems use different characters to separate directory names, it is easy to accidentally double those separator characters when concatenating paths, and because ../ parent directory path segments need special handling. Node’s “path” module and a couple of other important Node features help:\n\n// Some important paths\nprocess.cwd()      // Absolute path of the current working directory.\n__filename         // Absolute path of the file that holds the current code.\n__dirname          // Absolute path of the directory that holds __filename.\nos.homedir()       // The user's home directory.\n\nconst path = require(\"path\");\n\npath.sep                         // Either \"/\" or \"\\\" depending on your OS\n\n// The path module has simple parsing functions\nlet p = \"src/pkg/test.js\";       // An example path\npath.basename(p)                 // => \"test.js\"\npath.extname(p)                  // => \".js\"\npath.dirname(p)                  // => \"src/pkg\"\npath.basename(path.dirname(p))   // => \"pkg\"\npath.dirname(path.dirname(p))    // => \"src\"\n\n// normalize() cleans up paths:\npath.normalize(\"a/b/c/../d/\")    // => \"a/b/d/\": handles ../ segments\npath.normalize(\"a/./b\")          // => \"a/b\": strips \"./\" segments\npath.normalize(\"//a//b//\")       // => \"/a/b/\": removes duplicate /\n\n// join() combines path segments, adding separators, then normalizes\npath.join(\"src\", \"pkg\", \"t.js\")  // => \"src/pkg/t.js\"\n\n// resolve() takes one or more path segments and returns an absolute\n// path. It starts with the last argument and works backward, stopping\n// when it has built an absolute path or resolving against process.cwd().\npath.resolve()                   // => process.cwd()\npath.resolve(\"t.js\")             // => path.join(process.cwd(), \"t.js\")\npath.resolve(\"/tmp\", \"t.js\")     // => \"/tmp/t.js\"\npath.resolve(\"/a\", \"/b\", \"t.js\") // => \"/b/t.js\"\nNote that path.normalize() is simply a string manipulation function that has no access to the actual filesystem. The fs.realpath() and fs.realpathSync() functions perform filesystem-aware canonicalization: they resolve symbolic links and interpret relative pathnames relative to the current working directory.\n\nIn the previous examples, we assumed that the code is running on a Unix-based OS and path.sep is “/.” If you want to work with Unix-style paths even when on a Windows system, then use path.posix instead of path. And conversely, if you want to work with Windows paths even when on a Unix system, path.win32. path.posix and path.win32 define the same properties and functions as path itself.\n\nSome of the “fs” functions we’ll be covering in the next sections expect a file descriptor instead of a file name. File descriptors are integers used as OS-level references to “open” files. You obtain a descriptor for a given name by calling the fs.open() (or fs.openSync()) function. Processes are only allowed to have a limited number of files open at one time, so it is important that you call fs.close() on your file descriptors when you are done with them. You need to open files if you want to use the lowest-level fs.read() and fs.write() functions that allow you to jump around within a file, reading and writing bits of it at different times. There are other functions in the “fs” module that use file descriptors, but they all have name-based versions, and it only really makes sense to use the descriptor-based functions if you were going to open the file to read or write anyway.\n\nFinally, in the Promise-based API defined by fs.promises, the equivalent of fs.open() is fs.promises.open(), which returns a Promise that resolves to a FileHandle object. This FileHandle object serves the same purpose as a file descriptor. Again, however, unless you need to use the lowest-level read() and write() methods of a FileHandle, there is really no reason to create one. And if you do create a FileHandle, you should remember to call its close() method once you are done with it.\n\n### 16.7.2 Reading Files\nNode allows you to read file content all at once, via a stream, or with the low-level API.\n\nIf your files are small, or if memory usage and performance are not the highest priority, then it is often easiest to read the entire content of a file with a single call. You can do this synchronously, with a callback, or with a Promise. By default, you’ll get the bytes of the file as a buffer, but if you specify an encoding, you’ll get a decoded string instead.\n\nconst fs = require(\"fs\");\nlet buffer = fs.readFileSync(\"test.data\");      // Synchronous, returns buffer\nlet text = fs.readFileSync(\"data.csv\", \"utf8\"); // Synchronous, returns string\n\n// Read the bytes of the file asynchronously\nfs.readFile(\"test.data\", (err, buffer) => {\n    if (err) {\n        // Handle the error here\n    } else {\n        // The bytes of the file are in buffer\n    }\n});\n\n// Promise-based asynchronous read\nfs.promises\n    .readFile(\"data.csv\", \"utf8\")\n    .then(processFileText)\n    .catch(handleReadError);\n\n// Or use the Promise API with await inside an async function\nasync function processText(filename, encoding=\"utf8\") {\n    let text = await fs.promises.readFile(filename, encoding);\n    // ... process the text here...\n}\nIf you are able to process the contents of a file sequentially and do not need to have the entire content of the file in memory at the same time, then reading a file via a stream may be the most efficient approach. We’ve covered streams extensively: here is how you might use a stream and the pipe() method to write the contents of a file to standard output:\n\nfunction printFile(filename, encoding=\"utf8\") {\n    fs.createReadStream(filename, encoding).pipe(process.stdout);\n}\nFinally, if you need low-level control over exactly what bytes you read from a file and when you read them, you can open a file to get a file descriptor and then use fs.read(), fs.readSync(), or fs.promises.read() to read a specified number of bytes from a specified source location of the file into a specified buffer at the specified destination position:\n\nconst fs = require(\"fs\");\n\n// Reading a specific portion of a data file\nfs.open(\"data\", (err, fd) => {\n    if (err) {\n        // Report error somehow\n        return;\n    }\n    try {\n        // Read bytes 20 through 420 into a newly allocated buffer.\n        fs.read(fd, Buffer.alloc(400), 0, 400, 20, (err, n, b) => {\n            // err is the error, if any.\n            // n is the number of bytes actually read\n            // b is the buffer that they bytes were read into.\n        });\n    }\n    finally {          // Use a finally clause so we always\n        fs.close(fd);  // close the open file descriptor\n    }\n});\nThe callback-based read() API is awkward to use if you need to read more than one chunk of data from a file. If you can use the synchronous API (or the Promise-based API with await), it becomes easy to read multiple chunks from a file:\n\nconst fs = require(\"fs\");\n\nfunction readData(filename) {\n    let fd = fs.openSync(filename);\n    try {\n        // Read the file header\n        let header = Buffer.alloc(12); // A 12 byte buffer\n        fs.readSync(fd, header, 0, 12, 0);\n\n        // Verify the file's magic number\n        let magic = header.readInt32LE(0);\n        if (magic !== 0xDADAFEED) {\n            throw new Error(\"File is of wrong type\");\n        }\n\n        // Now get the offset and length of the data from the header\n        let offset = header.readInt32LE(4);\n        let length = header.readInt32LE(8);\n\n        // And read those bytes from the file\n        let data = Buffer.alloc(length);\n        fs.readSync(fd, data, 0, length, offset);\n        return data;\n    } finally {\n        // Always close the file, even if an exception is thrown above\n        fs.closeSync(fd);\n    }\n}\n### 16.7.3 Writing Files\nWriting files in Node is a lot like reading them, with a few extra details that you need to know about. One of these details is that the way you create a new file is simply by writing to a filename that does not already exist.\n\nAs with reading, there are three basic ways to write files in Node. If you have the entire content of the file in a string or a buffer, you can write the entire thing in one call with fs.writeFile() (callback-based), fs.writeFileSync() (synchronous), or fs.promises.writeFile() (Promise-based):\n\nfs.writeFileSync(path.resolve(__dirname, \"settings.json\"),\n                 JSON.stringify(settings));\nIf the data you are writing to the file is a string, and you want to use an encoding other than “utf8,” pass the encoding as an optional third argument.\n\nThe related functions fs.appendFile(), fs.appendFileSync(), and fs.promises.appendFile() are similar, but when the specified file already exists, they append their data to the end rather than overwriting the existing file content.\n\nIf the data you want to write to a file is not all in one chunk, or if it is not all in memory at the same time, then using a Writable stream is a good approach, assuming that you plan to write the data from beginning to end without skipping around in the file:\n\nconst fs = require(\"fs\");\nlet output = fs.createWriteStream(\"numbers.txt\");\nfor(let i = 0; i < 100; i++) {\n    output.write(`${i}\\n`);\n}\noutput.end();\nFinally, if you want to write data to a file in multiple chunks, and you want to be able to control the exact position within the file at which each chunk is written, then you can open the file with fs.open(), fs.openSync(), or fs.promises.open() and then use the resulting file descriptor with the fs.write() or fs.writeSync() functions. These functions come in different forms for strings and buffers. The string variant takes a file descriptor, a string, and the file position at which to write that string (with an encoding as an optional fourth argument). The buffer variant takes a file descriptor, a buffer, an offset, and a length that specify a chunk of data within the buffer, and a file position at which to write the bytes of that chunk. And if you have an array of Buffer objects that you want to write, you can do this with a single fs.writev() or fs.writevSync(). Similar low-level functions exist for writing buffers and strings using fs.promises.open() and the FileHandle object it produces.\n\nFILE MODE STRINGS\nWe saw the fs.open() and fs.openSync() methods before when using the low-level API to read files. In that use case, it was sufficient to just pass the filename to the open function. When you want to write a file, however, you must also specify a second string argument that specifies how you intend to use the file descriptor. Some of the available flag strings are as follows:\n\n\"w\"\nOpen the file for writing\n\n\"w+\"\nOpen for writing and reading\n\n\"wx\"\nOpen for creating a new file; fails if the named file already exists\n\n\"wx+\"\nOpen for creation, and also allow reading; fails if the named file already exists\n\n\"a\"\nOpen the file for appending; existing content won’t be overwritten\n\n\"a+\"\nOpen for appending, but also allow reading\n\nIf you do not pass one of these flag strings to fs.open() or fs.openSync(), they use the default “r” flag, making the file descriptor read-only. Note that it can also be useful to pass these flags to other file-writing methods:\n\n// Write to a file in one call, but append to anything that is already there.\n// This works like fs.appendFileSync()\nfs.writeFileSync(\"messages.log\", \"hello\", { flag: \"a\" });\n\n// Open a write stream, but throw an error if the file already exists.\n// We don't want to accidentally overwrite something!\n// Note that the option above is \"flag\" and is \"flags\" here\nfs.createWriteStream(\"messages.log\", { flags: \"wx\" });\nYou can chop off the end of a file with fs.truncate(), fs.truncateSync(), or fs.promises.truncate(). These functions take a path as their first argument and a length as their second, and modify the file so that it has the specified length. If you omit the length, zero is used and the file becomes empty. Despite the name of these functions, they can also be used to extend a file: if you specify a length that is longer than the current file size, the file is extended with zero bytes to the new size. If you have already opened the file you wish to modify, you can use ftruncate() or ftruncateSync() with the file descriptor or FileHandle.\n\nThe various file-writing functions described here return or invoke their callback or resolve their Promise when the data has been “written” in the sense that Node has handed it off to the operating system. But this does not necessarily mean that the data has actually been written to persistent storage yet: at least some of your data may still be buffered somewhere in the operating system or in a device driver waiting to be written to disk. If you call fs.writeSync() to synchronously write some data to a file, and if there is a power outage immediately after the function returns, you may still lose data. If you want to force your data out to disk so you know for sure that it has been safely saved, use fs.fsync() or fs.fsyncSync(). These functions only work with file descriptors: there is no path-based version.\n\n### 16.7.4 File Operations\nThe preceding discussion of Node’s stream classes included two examples of copyFile() functions. These are not practical utilities that you would actually use because the “fs” module defines its own fs.copyFile() method (and also fs.copyFileSync() and fs.promises.copyFile(), of course).\n\nThese functions take the name of the original file and the name of the copy as their first two arguments. These can be specified as strings or as URL or Buffer objects. An optional third argument is an integer whose bits specify flags that control details of the copy operation. And for the callback-based fs.copyFile(), the final argument is a callback function that will be called with no arguments when the copy is complete, or that will be called with an error argument if something fails. Following are some examples:\n\n// Basic synchronous file copy.\nfs.copyFileSync(\"ch15.txt\", \"ch15.bak\");\n\n// The COPYFILE_EXCL argument copies only if the new file does not already\n// exist. It prevents copies from overwriting existing files.\nfs.copyFile(\"ch15.txt\", \"ch16.txt\", fs.constants.COPYFILE_EXCL, err => {\n    // This callback will be called when done. On error, err will be non-null.\n});\n\n// This code demonstrates the Promise-based version of the copyFile function.\n// Two flags are combined with the bitwise OR opeartor |. The flags mean that\n// existing files won't be overwritten, and that if the filesystem supports\n// it, the copy will be a copy-on-write clone of the original file, meaning\n// that no additional storage space will be required until either the original\n// or the copy is modified.\nfs.promises.copyFile(\"Important data\",\n                     `Important data ${new Date().toISOString()}\"\n                     fs.constants.COPYFILE_EXCL | fs.constants.COPYFILE_FICLONE)\n    .then(() => {\n        console.log(\"Backup complete\");\n    });\n    .catch(err => {\n        console.error(\"Backup failed\", err);\n    });\nThe fs.rename() function (along with the usual synchronous and Promise-based variants) moves and/or renames a file. Call it with the current path to the file and the desired new path to the file. There is no flags argument, but the callback-based version takes a callback as the third argument:\n\nfs.renameSync(\"ch15.bak\", \"backups/ch15.bak\");\nNote that there is no flag to prevent renaming from overwriting an existing file. Also keep in mind that files can only be renamed within a filesystem.\n\nThe functions fs.link() and fs.symlink() and their variants have the same signatures as fs.rename() and behave something like fs.copyFile() except that they create hard links and symbolic links, respectively, rather than creating a copy.\n\nFinally, fs.unlink(), fs.unlinkSync(), and fs.promises.unlink() are Node’s functions for deleting a file. (The unintuitive naming is inherited from Unix where deleting a file is basically the opposite of creating a hard link to it.) Call this function with the string, buffer, or URL path to the file to be deleted, and pass a callback if you are using the callback-based version:\n\nfs.unlinkSync(\"backups/ch15.bak\");\n### 16.7.5 File Metadata\nThe fs.stat(), fs.statSync(), and fs.promises.stat() functions allow you to obtain metadata for a specified file or directory. For example:\n\nconst fs = require(\"fs\");\nlet stats = fs.statSync(\"book/ch15.md\");\nstats.isFile()         // => true: this is an ordinary file\nstats.isDirectory()    // => false: it is not a directory\nstats.size             // file size in bytes\nstats.atime            // access time: Date when it was last read\nstats.mtime            // modification time: Date when it was last written\nstats.uid              // the user id of the file's owner\nstats.gid              // the group id of the file's owner\nstats.mode.toString(8) // the file's permissions, as an octal string\nThe returned Stats object contains other, more obscure properties and methods, but this code demonstrates those that you are most likely to use.\n\nfs.lstat() and its variants work just like fs.stat(), except that if the specified file is a symbolic link, Node will return metadata for the link itself rather than following the link.\n\nIf you have opened a file to produce a file descriptor or a FileHandle object, then you can use fs.fstat() or its variants to get metadata information for the opened file without having to specify the filename again.\n\nIn addition to querying metadata with fs.stat() and all of its variants, there are also functions for changing metadata.\n\nfs.chmod(), fs.lchmod(), and fs.fchmod() (along with synchronous and Promise-based versions) set the “mode” or permissions of a file or directory. Mode values are integers in which each bit has a specific meaning and are easiest to think about in octal notation. For example, to make a file read-only to its owner and inaccessible to everyone else, use 0o400:\n\nfs.chmodSync(\"ch15.md\", 0o400);  // Don't delete it accidentally!\nfs.chown(), fs.lchown(), and fs.fchown() (along with synchronous and Promise-based versions) set the owner and group (as IDs) for a file or directory. (These matter because they interact with the file permissions set by fs.chmod().)\n\nFinally, you can set the access time and modification time of a file or directory with fs.utimes() and fs.futimes() and their variants.\n\n### 16.7.6 Working with Directories\nTo create a new directory in Node, use fs.mkdir(), fs.mkdirSync(), or fs.promises.mkdir(). The first argument is the path of the directory to be created. The optional second argument can be an integer that specifies the mode (permissions bits) for the new directory. Or you can pass an object with optional mode and recursive properties. If recursive is true, then this function will create any directories in the path that do not already exist:\n\n// Ensure that dist/ and dist/lib/ both exist.\nfs.mkdirSync(\"dist/lib\", { recursive: true });\nfs.mkdtemp() and its variants take a path prefix you provide, append some random characters to it (this is important for security), create a directory with that name, and return (or pass to a callback) the directory path to you.\n\nTo delete a directory, use fs.rmdir() or one of its variants. Note that directories must be empty before they can be deleted:\n\n// Create a random temporary directory and get its path, then\n// delete it when we are done\nlet tempDirPath;\ntry {\n    tempDirPath = fs.mkdtempSync(path.join(os.tmpdir(), \"d\"));\n    // Do something with the directory here\n} finally {\n    // Delete the temporary directory when we're done with it\n    fs.rmdirSync(tempDirPath);\n}\nThe “fs” module provides two distinct APIs for listing the contents of a directory. First, fs.readdir(), fs.readdirSync(), and fs.promises.readdir() read the entire directory all at once and give you an array of strings or an array of Dirent objects that specify the names and types (file or directory) of each item. Filenames returned by these functions are just the local name of the file, not the entire path. Here are examples:\n\nlet tempFiles = fs.readdirSync(\"/tmp\");  // returns an array of strings\n\n// Use the Promise-based API to get a Dirent array, and then\n// print the paths of subdirectories\nfs.promises.readdir(\"/tmp\", {withFileTypes: true})\n    .then(entries => {\n        entries.filter(entry => entry.isDirectory())\n            .map(entry => entry.name)\n            .forEach(name => console.log(path.join(\"/tmp/\", name)));\n    })\n    .catch(console.error);\nIf you anticipate needing to list directories that might have thousands of entries, you might prefer the streaming approach of fs.opendir() and its variants. These functions return a Dir object representing the specified directory. You can use the read() or readSync() methods of the Dir object to read one Dirent at a time. If you pass a callback function to read(), it will call the callback. And if you omit the callback argument, it will return a Promise. When there are no more directory entries, you’ll get null instead of a Dirent object.\n\nThe easiest way to use Dir objects is as async iterators with a for/await loop. Here, for example, is a function that uses the streaming API to list directory entries, calls stat() on each entry, and prints file and directory names and sizes:\n\nconst fs = require(\"fs\");\nconst path = require(\"path\");\n\nasync function listDirectory(dirpath) {\n    let dir = await fs.promises.opendir(dirpath);\n    for await (let entry of dir) {\n        let name = entry.name;\n        if (entry.isDirectory()) {\n            name += \"/\";  // Add a trailing slash to subdirectories\n        }\n        let stats = await fs.promises.stat(path.join(dirpath, name));\n        let size = stats.size;\n        console.log(String(size).padStart(10), name);\n    }\n}\n## 16.8 HTTP Clients and Servers\nNode’s “http,” “https,” and “http2” modules are full-featured but relatively low-level implementations of the HTTP protocols. They define comprehensive APIs for implementing HTTP clients and servers. Because the APIs are relatively low-level, there is not room in this chapter to cover all the features. But the examples that follow demonstrate how to write basic clients and servers.\n\nThe simplest way to make a basic HTTP GET request is with http.get() or https.get(). The first argument to these functions is the URL to fetch. (If it is an http:// URL, you must use the “http” module, and if it is an https:// URL you must use the “https” module.) The second argument is a callback that will be invoked with an IncomingMessage object when the server’s response has started to arrive. When the callback is called, the HTTP status and headers are available, but the body may not be ready yet. The IncomingMessage object is a Readable stream, and you can use the techniques demonstrated earlier in this chapter to read the response body from it.\n\nThe getJSON() function at the end of §13.2.6 used the http.get() function as part of a demonstration of the Promise() constructor. Now that you know about Node streams and the Node programming model more generally, it is worth revisiting that example to see how http.get() is used.\n\nhttp.get() and https.get() are slightly simplified variants of the more general http.request() and https.request() functions. The following postJSON() function demonstrates how to use https.request() to make an HTTPS POST request that includes a JSON request body. Like the getJSON() function of Chapter 13, it expects a JSON response and returns a Promise that fulfills to the parsed version of that response:\n\nconst https = require(\"https\");\n\n/*\n * Convert the body object to a JSON string then HTTPS POST it to the\n * specified API endpoint on the specified host. When the response arrives,\n * parse the response body as JSON and resolve the returned Promise with\n * that parsed value.\n */\nfunction postJSON(host, endpoint, body, port, username, password) {\n    // Return a Promise object immediately, then call resolve or reject\n    // when the HTTPS request succeeds or fails.\n    return new Promise((resolve, reject) => {\n        // Convert the body object to a string\n        let bodyText = JSON.stringify(body);\n\n        // Configure the HTTPS request\n        let requestOptions = {\n            method: \"POST\",       // Or \"GET\", \"PUT\", \"DELETE\", etc.\n            host: host,           // The host to connect to\n            path: endpoint,       // The URL path\n            headers: {            // HTTP headers for the request\n                \"Content-Type\": \"application/json\",\n                \"Content-Length\": Buffer.byteLength(bodyText)\n            }\n        };\n\n        if (port) {                      // If a port is specified,\n            requestOptions.port = port;  // use it for the request.\n        }\n        // If credentials are specified, add an Authorization header.\n        if (username && password) {\n            requestOptions.auth = `${username}:${password}`;\n        }\n\n        // Now create the request based on the configuration object\n        let request = https.request(requestOptions);\n\n        // Write the body of the POST request and end the request.\n        request.write(bodyText);\n        request.end();\n\n        // Fail on request errors (such as no network connection)\n        request.on(\"error\", e => reject(e));\n\n        // Handle the response when it starts to arrive.\n        request.on(\"response\", response => {\n            if (response.statusCode !== 200) {\n                reject(new Error(`HTTP status ${response.statusCode}`));\n                // We don't care about the response body in this case, but\n                // we don't want it to stick around in a buffer somewhere, so\n                // we put the stream into flowing mode without registering\n                // a \"data\" handler so that the body is discarded.\n                response.resume();\n                return;\n            }\n\n            // We want text, not bytes. We're assuming the text will be\n            // JSON-formatted but aren't bothering to check the\n            // Content-Type header.\n            response.setEncoding(\"utf8\");\n\n            // Node doesn't have a streaming JSON parser, so we read the\n            // entire response body into a string.\n            let body = \"\";\n            response.on(\"data\", chunk => { body += chunk; });\n\n            // And now handle the response when it is complete.\n            response.on(\"end\", () => {          // When the response is done,\n                try {                           // try to parse it as JSON\n                    resolve(JSON.parse(body));  // and resolve the result.\n                } catch(e) {                    // Or, if anything goes wrong,\n                    reject(e);                  // reject with the error\n                }\n            });\n        });\n    });\n}\nIn addition to making HTTP and HTTPS requests, the “http” and “https” modules also allow you to write servers that respond to those requests. The basic approach is as follows:\n\nCreate a new Server object.\n\nCall its listen() method to begin listening for requests on a specified port.\n\nRegister an event handler for “request” events, use that handler to read the client’s request (particularly the request.url property), and write your response.\n\nThe code that follows creates a simple HTTP server that serves static files from the local filesystem and also implements a debugging endpoint that responds to a client’s request by echoing that request.\n\n// This is a simple static HTTP server that serves files from a specified\n// directory. It also implements a special /test/mirror endpoint that\n// echoes the incoming request, which can be useful when debugging clients.\nconst http = require(\"http\");   // Use \"https\" if you have a certificate\nconst url = require(\"url\");     // For parsing URLs\nconst path = require(\"path\");   // For manipulating filesystem paths\nconst fs = require(\"fs\");       // For reading files\n\n// Serve files from the specified root directory via an HTTP server that\n// listens on the specified port.\nfunction serve(rootDirectory, port) {\n    let server = new http.Server();  // Create a new HTTP server\n    server.listen(port);             // Listen on the specified port\n    console.log(\"Listening on port\", port);\n\n    // When requests come in, handle them with this function\n    server.on(\"request\", (request, response) => {\n        // Get the path portion of the request URL, ignoring\n        // any query parameters that are appended to it.\n        let endpoint = url.parse(request.url).pathname;\n\n        // If the request was for \"/test/mirror\", send back the request\n        // verbatim. Useful when you need to see the request headers and body.\n        if (endpoint === \"/test/mirror\") {\n            // Set response header\n            response.setHeader(\"Content-Type\", \"text/plain; charset=UTF-8\");\n\n            // Specify response status code\n            response.writeHead(200);  // 200 OK\n\n            // Begin the response body with the request\n            response.write(`${request.method} ${request.url} HTTP/${\n                               request.httpVersion\n                           }\\r\\n`);\n\n            // Output the request headers\n            let headers = request.rawHeaders;\n            for(let i = 0; i < headers.length; i += 2) {\n                response.write(`${headers[i]}: ${headers[i+1]}\\r\\n`);\n            }\n\n            // End headers with an extra blank line\n            response.write(\"\\r\\n\");\n\n            // Now we need to copy any request body to the response body\n            // Since they are both streams, we can use a pipe\n            request.pipe(response);\n        }\n        // Otherwise, serve a file from the local directory.\n        else {\n            // Map the endpoint to a file in the local filesystem\n            let filename = endpoint.substring(1); // strip leading /\n            // Don't allow \"../\" in the path because it would be a security\n            // hole to serve anything outside the root directory.\n            filename = filename.replace(/\\.\\.\\//g, \"\");\n            // Now convert from relative to absolute filename\n            filename = path.resolve(rootDirectory, filename);\n\n            // Now guess the type file's content type based on extension\n            let type;\n            switch(path.extname(filename))  {\n            case \".html\":\n            case \".htm\": type = \"text/html\"; break;\n            case \".js\":  type = \"text/javascript\"; break;\n            case \".css\": type = \"text/css\"; break;\n            case \".png\": type = \"image/png\"; break;\n            case \".txt\": type = \"text/plain\"; break;\n            default:     type = \"application/octet-stream\"; break;\n            }\n\n            let stream = fs.createReadStream(filename);\n            stream.once(\"readable\", () => {\n                // If the stream becomes readable, then set the\n                // Content-Type header and a 200 OK status. Then pipe the\n                // file reader stream to the response. The pipe will\n                // automatically call response.end() when the stream ends.\n                response.setHeader(\"Content-Type\", type);\n                response.writeHead(200);\n                stream.pipe(response);\n            });\n\n            stream.on(\"error\", (err) => {\n                // Instead, if we get an error trying to open the stream\n                // then the file probably does not exist or is not readable.\n                // Send a 404 Not Found plain-text response with the\n                // error message.\n                response.setHeader(\"Content-Type\", \"text/plain; charset=UTF-8\");\n                response.writeHead(404);\n                response.end(err.message);\n            });\n        }\n    });\n}\n\n// When we're invoked from the command line, call the serve() function\nserve(process.argv[2] || \"/tmp\", parseInt(process.argv[3]) || 8000);\nNode’s built-in modules are all you need to write simple HTTP and HTTPS servers. Note, however, that production servers are not typically built directly on top of these modules. Instead, most nontrivial servers are implemented using external libraries—such as the Express framework—that provide “middleware” and other higher-level utilities that backend web developers have come to expect.\n\n## 16.9 Non-HTTP Network Servers and Clients\nWeb servers and clients have become so ubiquitous that it is easy to forget that it is possible to write clients and servers that do not use HTTP. Even though Node has a reputation as a good environment for writing web servers, Node also has full support for writing other types of network servers and clients.\n\nIf you are comfortable working with streams, then networking is relatively simple, because network sockets are simply a kind of Duplex stream. The “net” module defines Server and Socket classes. To create a server, call net.createServer(), then call the listen() method of the resulting object to tell the server what port to listen on for connections. The Server object will generate “connection” events when a client connects on that port, and the value passed to the event listener will be a Socket object. The Socket object is a Duplex stream, and you can use it to read data from the client and write data to the client. Call end() on the Socket to disconnect.\n\nWriting a client is even easier: pass a port number and hostname to net.createConnection() to create a socket to communicate with whatever server is running on that host and listening on that port. Then use that socket to read and write data from and to the server.\n\nThe following code demonstrates how to write a server with the “net” module. When the client connects, the server tells a knock-knock joke:\n\n// A TCP server that delivers interactive knock-knock jokes on port 6789.\n// (Why is six afraid of seven? Because seven ate nine!)\nconst net = require(\"net\");\nconst readline = require(\"readline\");\n\n// Create a Server object and start listening for connections\nlet server = net.createServer();\nserver.listen(6789, () => console.log(\"Delivering laughs on port 6789\"));\n\n// When a client connects, tell them a knock-knock joke.\nserver.on(\"connection\", socket => {\n    tellJoke(socket)\n        .then(() => socket.end())  // When the joke is done, close the socket.\n        .catch((err) => {\n            console.error(err);    // Log any errors that occur,\n            socket.end();          // but still close the socket!\n        });\n});\n\n// These are all the jokes we know.\nconst jokes = {\n    \"Boo\": \"Don't cry...it's only a joke!\",\n    \"Lettuce\": \"Let us in! It's freezing out here!\",\n    \"A little old lady\": \"Wow, I didn't know you could yodel!\"\n};\n\n// Interactively perform a knock-knock joke over this socket, without blocking.\nasync function tellJoke(socket) {\n    // Pick one of the jokes at random\n    let randomElement = a => a[Math.floor(Math.random() * a.length)];\n    let who = randomElement(Object.keys(jokes));\n    let punchline = jokes[who];\n\n    // Use the readline module to read the user's input one line at a time.\n    let lineReader = readline.createInterface({\n        input: socket,\n        output: socket,\n        prompt: \">> \"\n    });\n\n    // A utility function to output a line of text to the client\n    // and then (by default) display a prompt.\n    function output(text, prompt=true) {\n        socket.write(`${text}\\r\\n`);\n        if (prompt) lineReader.prompt();\n    }\n\n    // Knock-knock jokes have a call-and-response structure.\n    // We expect different input from the user at different stages and\n    // take different action when we get that input at different stages.\n    let stage = 0;\n\n    // Start the knock-knock joke off in the traditional way.\n    output(\"Knock knock!\");\n\n    // Now read lines asynchronously from the client until the joke is done.\n    for await (let inputLine of lineReader) {\n        if (stage === 0) {\n            if (inputLine.toLowerCase() === \"who's there?\") {\n                // If the user gives the right response at stage 0\n                // then tell the first part of the joke and go to stage 1.\n                output(who);\n                stage = 1;\n            } else  {\n                // Otherwise teach the user how to do knock-knock jokes.\n                output('Please type \"Who\\'s there?\".');\n            }\n        } else if (stage === 1) {\n            if (inputLine.toLowerCase() === `${who.toLowerCase()} who?`) {\n                // If the user's response is correct at stage 1, then\n                // deliver the punchline and return since the joke is done.\n                output(`${punchline}`, false);\n                return;\n            } else {\n                // Make the user play along.\n                output(`Please type \"${who} who?\".`);\n            }\n        }\n    }\n}\nSimple text-based servers like this do not typically need a custom client. If the nc (“netcat”) utility is installed on your system, you can use it to communicate with this server as follows:\n\n$ nc localhost 6789\nKnock knock!\n>> Who's there?\nA little old lady\n>> A little old lady who?\nWow, I didn't know you could yodel!\nOn the other hand, writing a custom client for the joke server is easy in Node. We just connect to the server, then pipe the server’s output to stdout and pipe stdin to the server’s input:\n\n// Connect to the joke port (6789) on the server named on the command line\nlet socket = require(\"net\").createConnection(6789, process.argv[2]);\nsocket.pipe(process.stdout);              // Pipe data from the socket to stdout\nprocess.stdin.pipe(socket);               // Pipe data from stdin to the socket\nsocket.on(\"close\", () => process.exit()); // Quit when the socket closes.\nIn addition to supporting TCP-based servers, Node’s “net” module also supports interprocess communication over “Unix domain sockets” that are identified by a filesystem path rather than by a port number. We are not going to cover that kind of socket in this chapter, but the Node documentation has details. Other Node features that we don’t have space to cover here include the “dgram” module for UDP-based clients and servers and the “tls” module that is to “net” as “https” is to “http.” The tls.Server and tls.TLSSocket classes allow the creation of TCP servers (like the knock-knock joke server) that use SSL-encrypted connections like HTTPS servers do.\n\n## 16.10 Working with Child Processes\nIn addition to writing highly concurrent servers, Node also works well for writing scripts that execute other programs. In Node the “child_process” module defines a number of functions for running other programs as child processes. This section demonstrates some of those functions, starting with the simplest and moving to the more complicated.\n\n### 16.10.1 execSync() and execFileSync()\nThe easiest way to run another program is with child_process.execSync(). This function takes the command to run as its first argument. It creates a child process, runs a shell in that process, and uses the shell to execute the command you passed. Then it blocks until the command (and the shell) exit. If the command exits with an error, then execSync() throws an exception. Otherwise, execSync() returns whatever output the command writes to its stdout stream. By default this return value is a buffer, but you can specify an encoding in an optional second argument to get a string instead. If the command writes any output to stderr, that output just gets passed through to the parent process’s stderr stream.\n\nSo, for example, if you are writing a script and performance is not a concern, you might use child_process.execSync() to list a directory with a familiar Unix shell command rather than using the fs.readdirSync() function:\n\nconst child_process = require(\"child_process\");\nlet listing = child_process.execSync(\"ls -l web/*.html\", {encoding: \"utf8\"});\nThe fact that execSync() invokes a full Unix shell means that the string you pass to it can include multiple semicolon-separated commands, and can take advantage of shell features such as filename wildcards, pipes, and output redirection. This also means that you must be careful to never pass a command to execSync() if any portion of that command is user input or comes from a similar untrusted source. The complex syntax of shell commands can be easily subverted to allow an attacker to run arbitrary code.\n\nIf you don’t need the features of a shell, you can avoid the overhead of starting a shell by using child_process.execFileSync(). This function executes a program directly, without invoking a shell. But since no shell is involved, it can’t parse a command line, and you must pass the executable as the first argument and an array of command-line arguments as the second argument:\n\nlet listing = child_process.execFileSync(\"ls\", [\"-l\", \"web/\"],\n                                         {encoding: \"utf8\"});\nCHILD PROCESS OPTIONS\nexecSync() and many of the other child_process functions have a second or third optional argument that specifies additional details about how the child process is to run. The encoding property of this object was used earlier to specify that we’d like the command output to be delivered as a string rather than as a buffer. Other important properties that you can specify include the following (note that not all options are available to all child process functions):\n\ncwd specifies the working directory for the child process. If you omit this, then the child process inherits the value of process.cwd().\n\nenv specifies the environment variables that the child process will have access to. By default, child processes simply inherit process.env, but you can specify a different object if you want.\n\ninput specifies a string or buffer of input data that should be used as the standard input to the child process. This option is only available to the synchronous functions that do not return a ChildProcess object.\n\nmaxBuffer specifies the maximum number of bytes of output that will be collected by the exec functions. (It does not apply to spawn() and fork(), which use streams.) If a child process produces more output than this, it will be killed and will exit with an error.\n\nshell specifies the path to a shell executable or true. For child process functions that normally execute a shell command, this option allows you to specify which shell to use. For functions that do not normally use a shell, this option allows you to specify that a shell should be used (by setting the property to true) or to specify exactly which shell to use.\n\ntimeout specifies the maximum number of milliseconds that the child process should be allowed to run. If it has not exited before this time elapses, it will be killed and will exit with an error. (This option applies to the exec functions but not to spawn() or fork().)\n\nuid specifies the user ID (a number) under which the program should be run. If the parent process is running in a privileged account, it can use this option to run the child with reduced privileges.\n\n### 16.10.2 exec() and execFile()\nThe execSync() and execFileSync() functions are, as their names indicate, synchronous: they block and do not return until the child process exits. Using these functions is a lot like typing Unix commands in a terminal window: they allow you to run a sequence of commands one at a time. But if you’re writing a program that needs to accomplish a number of tasks, and those tasks don’t depend on each other in any way, then you may want to parallelize them and run multiple commands at the same time. You can do this with the asynchronous functions child_process.exec() and child_process.execFile().\n\nexec() and execFile() are like their synchronous variants except that they return immediately with a ChildProcess object that represents the running child process, and they take an error-first callback as their final argument. The callback is invoked when the child process exits, and it is actually called with three arguments. The first is the error, if any; it will be null if the process terminated normally. The second argument is the collected output that was sent to the child’s standard output stream. And the third argument is any output that was sent to the child’s standard error stream.\n\nThe ChildProcess object returned by exec() and execFile() allows you to terminate the child process, and to write data to it (which it can then read from its standard input). We’ll cover ChildProcess in more detail when we discuss the child_process.spawn() function.\n\nIf you plan to execute multiple child processes at the same time, then it may be easiest to use the “promisified” version of exec() which returns a Promise object which, if the child process exits without error, resolves to an object with stdout and stderr properties. Here, for example, is a function that takes an array of shell commands as its input and returns a Promise that resolves to the result of all of those commands:\n\nconst child_process = require(\"child_process\");\nconst util = require(\"util\");\nconst execP = util.promisify(child_process.exec);\n\nfunction parallelExec(commands) {\n    // Use the array of commands to create an array of Promises\n    let promises = commands.map(command => execP(command, {encoding: \"utf8\"}));\n    // Return a Promise that will fulfill to an array of the fulfillment\n    // values of each of the individual promises. (Instead of returning objects\n    // with stdout and stderr properties we just return the stdout value.)\n    return Promise.all(promises)\n        .then(outputs => outputs.map(out => out.stdout));\n}\n\nmodule.exports = parallelExec;\n### 16.10.3 spawn()\nThe various exec functions described so far—both synchronous and asynchronous—are designed to be used with child processes that run quickly and do not produce a lot of output. Even the asynchronous exec() and execFile() are nonstreaming: they return the process output in a single batch, only after the process has exited.\n\nThe child_process.spawn() function allows you streaming access to the output of the child process, while the process is still running. It also allows you to write data to the child process (which will see that data as input on its standard input stream): this means it is possible to dynamically interact with a child process, sending it input based on the output it generates.\n\nspawn() does not use a shell by default, so you must invoke it like execFile() with the executable to be run and a separate array of command-line arguments to pass to it. spawn() returns a ChildProcess object like execFile() does, but it does not take a callback argument. Instead of using a callback function, you listen to events on the ChildProcess object and on its streams.\n\nThe ChildProcess object returned by spawn() is an event emitter. You can listen for the “exit” event to be notified when the child process exits. A ChildProcess object also has three stream properties. stdout and stderr are Readable streams: when the child process writes to its stdout and its stderr streams, that output becomes readable through the ChildProcess streams. Note the inversion of the names here. In the child process, “stdout” is a Writable output stream, but in the parent process, the stdout property of a ChildProcess object is a Readable input stream.\n\nSimilarly, the stdin property of the ChildProcess object is a Writeable stream: anything you write to this stream becomes available to the child process on its standard input.\n\nThe ChildProcess object also defines a pid property that specifies the process id of the child. And it defines a kill() method that you can use to terminate a child process.\n\n### 16.10.4 fork()\nchild_process.fork() is a specialized function for running a module of JavaScript code in a child Node process. fork() expects the same arguments as spawn(), but the first argument should specify the path to a file of JavaScript code instead of an executable binary file.\n\nA child process created with fork() can communicate with the parent process via its standard input and standard output streams, as described in the previous section for spawn(). But in addition, fork() enables another, much easier, communication channel between the parent and child processes.\n\nWhen you create a child process with fork(), you can use the send() method of the returned ChildProcess object to send a copy of an object to the child process. And you can listen for the “message” event on the ChildProcess to receive messages from the child. The code running in the child process can use process.send() to send a message to the parent and can listen for “message” events on process to receive messages from the parent.\n\nHere, for example, is some code that uses fork() to create a child process, then sends that child a message and waits for a response:\n\nconst child_process = require(\"child_process\");\n\n// Start a new node process running the code in child.js in our directory\nlet child = child_process.fork(`${__dirname}/child.js`);\n\n// Send a message to the child\nchild.send({x: 4, y: 3});\n\n// Print the child's response when it arrives.\nchild.on(\"message\", message => {\n    console.log(message.hypotenuse); // This should print \"5\"\n    // Since we only send one message we only expect one response.\n    // After we receive it we call disconnect() to terminate the connection\n    // between parent and child. This allows both processes to exit cleanly.\n    child.disconnect();\n});\nAnd here is the code that runs in the child process:\n\n// Wait for messages from our parent process\nprocess.on(\"message\", message => {\n    // When we receive one, do a calculation and send the result\n    // back to the parent.\n    process.send({hypotenuse: Math.hypot(message.x, message.y)});\n});\nStarting child processes is an expensive operation, and the child process would have to be doing orders of magnitude more computation before it would make sense to use fork() and interprocess communication in this way. If you are writing a program that needs to be very responsive to incoming events and also needs to perform time-consuming computations, then you might consider using a separate child process to perform the computations so that they don’t block the event loop and reduce the responsiveness of the parent process. (Though a thread—see §16.11—may be a better choice than a child process in this scenario.)\n\nThe first argument to send() will be serialized with JSON.stringify() and deserialized in the child process with JSON.parse(), so you should only include values that are supported by the JSON format. send() has a special second argument, however, that allows you to transfer Socket and Server objects (from the “net” module) to a child process. Network servers tend to be IO-bound rather than compute-bound, but if you have written a server that needs to do more computation than a single CPU can handle, and if you’re running that server on a machine with multiple CPUs, then you could use fork() to create multiple child processes for handling requests. In the parent process, you might listen for “connection” events on your Server object, then get the Socket object from that “connection” event and send() it—using the special second argument—to one of the child processes to be handled. (Note that this is an unlikely solution to an uncommon scenario. Rather than writing a server that forks child processes, it is probably simpler to keep your server single-threaded and deploy multiple instances of it in production to handle the load.)\n\n## 16.11 Worker Threads\nAs explained at the beginning of this chapter, Node’s concurrency model is single-threaded and event-based. But in version 10 and later, Node does allow true multithreaded programming, with an API that closely mirrors the Web Workers API defined by web browsers (§15.13). Multithreaded programming has a well-deserved reputation for being difficult. This is almost entirely because of the need to carefully synchronize access by threads to shared memory. But JavaScript threads (in both Node and browsers) do not share memory by default, so the dangers and difficulties of using threads do not apply to these “workers” in JavaScript.\n\nInstead of using shared memory, JavaScript’s worker threads communicate by message passing. The main thread can send a message to a worker thread by calling the postMessage() method of the Worker object that represents that thread. The worker thread can receive messages from its parent by listening for “message” events. And workers can send messages to the main thread with their own version of postMessage(), which the parent can receive with its own “message” event handler. The example code will make it clear how this works.\n\nThere are three reasons why you might want to use worker threads in a Node application:\n\nIf your application actually needs to do more computation than one CPU core can handle, then threads allow you to distribute work across the multiple cores, which have become commonplace on computers today. If you’re doing scientific computing or machine learning or graphics processing in Node, then you may want to use threads simply to throw more computing power at your problem.\n\nEven if your application is not using the full power of one CPU, you may still want to use threads to maintain the responsiveness of the main thread. Consider a server that handles large but relatively infrequent requests. Suppose it gets only one request a second, but needs to spend about half a second of (blocking CPU-bound) computation to process each request. On average, it will be idle 50% of the time. But when two requests arrive within a few milliseconds of each other, the server will not even be able to begin a response to the second request until the computation of the first response is complete. Instead, if the server uses a worker thread to perform the computation, the server can begin the response to both requests immediately and provide a better experience for the server’s clients. Assuming the server has more than one CPU core, it can also compute the body of both responses in parallel, but even if there is only a single core, using workers still improves the responsiveness.\n\nIn general, workers allow us to turn blocking synchronous operations into nonblocking asynchronous operations. If you are writing a program that depends on legacy code that is unavoidably synchronous, you may be able to use workers to avoid blocking when you need to call that legacy code.\n\nWorker threads are not nearly as heavyweight as child processes, but they are not lightweight. It does not generally make sense to create a worker unless you have significant work for it to do. And, generally speaking, if your program is not CPU-bound and is not having responsiveness problems, then you probably do not need worker threads.\n\n### 16.11.1 Creating Workers and Passing Messages\nThe Node module that defines workers is known as “worker_threads.” In this section we’ll refer to it with the identifier threads:\n\nconst threads = require(\"worker_threads\");\nThis module defines a Worker class to represent a worker thread, and you can create a new thread with the threads.Worker() constructor. The following code demonstrates using this constructor to create a worker, and shows how to pass messages from main thread to worker and from worker to main thread. It also demonstrates a trick that allows you to put the main thread code and the worker thread code in the same file.2\n\nconst threads = require(\"worker_threads\");\n\n// The worker_threads module exports the boolean isMainThread property.\n// This property is true when Node is running the main thread and it is\n// false when Node is running a worker. We can use this fact to implement\n// the main and worker threads in the same file.\nif (threads.isMainThread) {\n    // If we're running in the main thread, then all we do is export\n    // a function. Instead of performing a computationally intensive\n    // task on the main thread, this function passes the task to a worker\n    // and returns a Promise that will resolve when the worker is done.\n    module.exports = function reticulateSplines(splines) {\n        return new Promise((resolve,reject) => {\n            // Create a worker that loads and runs this same file of code.\n            // Note the use of the special __filename variable.\n            let reticulator = new threads.Worker(__filename);\n\n            // Pass a copy of the splines array to the worker\n            reticulator.postMessage(splines);\n\n            // And then resolve or reject the Promise when we get\n            // a message or error from the worker.\n            reticulator.on(\"message\", resolve);\n            reticulator.on(\"error\", reject);\n        });\n    };\n} else {\n    // If we get here, it means we're in the worker, so we register a\n    // handler to get messages from the main thread. This worker is designed\n    // to only receive a single message, so we register the event handler\n    // with once() instead of on(). This allows the worker to exit naturally\n    // when its work is complete.\n    threads.parentPort.once(\"message\", splines => {\n        // When we get the splines from the parent thread, loop\n        // through them and reticulate all of them.\n        for(let spline of splines) {\n            // For the sake of example, assume that spline objects usually\n            // have a reticulate() method that does a lot of computation.\n            spline.reticulate ? spline.reticulate() : spline.reticulated = true;\n        }\n\n        // When all the splines have (finally!) been reticulated\n        // pass a copy back to the main thread.\n        threads.parentPort.postMessage(splines);\n    });\n}\nThe first argument to the Worker() constructor is the path to a file of JavaScript code that is to run in the thread. In the preceding code, we used the predefined __filename identifier to create a worker that loads and runs the same file as the main thread. In general, though, you will be passing a file path. Note that if you specify a relative path, it is relative to process.cwd(), not relative to the currently running module. If you want a path relative to the current module, use something like path.resolve(__dirname, 'workers/reticulator.js').\n\nThe Worker() constructor can also accept an object as its second argument, and the properties of this object provide optional configuration for the worker. We’ll cover a number of these options later, but for now note that if you pass {eval: true} as the second argument, then the first argument to Worker() is interpreted as a string of JavaScript code to be evaluated instead of a filename:\n\nnew threads.Worker(`\n    const threads = require(\"worker_threads\");\n    threads.parentPort.postMessage(threads.isMainThread);\n`, {eval: true}).on(\"message\", console.log);  // This will print \"false\"\nNode makes a copy of the object passed to postMessage() rather than sharing it directly with the worker thread. This prevents the worker thread and the main thread from sharing memory. You might expect that this copying would be done with JSON.stringify() and JSON.parse() (§11.6). But in fact, Node borrows a more robust technique known as the structured clone algorithm from web browsers.\n\nThe structured clone algorithm enables serialization of most JavaScript types, including Map, Set, Date, and RegExp objects and typed arrays, but it cannot, in general, copy types defined by the Node host environment, such as sockets and streams. Note, however, that Buffer objects are partially supported: if you pass a Buffer to postMessage() it will be received as a Uint8Array, and can be converted back into a Buffer with Buffer.from(). Read more about the structured clone algorithm in “The Structured Clone Algorithm”.\n\n### 16.11.2 The Worker Execution Environment\nFor the most part, JavaScript code in a Node worker thread runs just like it would in Node’s main thread. There are a few differences that you should be aware of, and some of these differences involve properties of the optional second argument to the Worker() constructor:\n\nAs we’ve seen, threads.isMainThread is true in the main thread but is always false in any worker thread.\n\nIn a worker thread, you can use threads.parentPort.postMessage() to send a message to the parent thread and threads.parentPort.on to register event handlers for messages from the parent thread. In the main thread, threads.parentPort is always null.\n\nIn a worker thread, threads.workerData is set to a copy of the workerData property of the second argument to the Worker() constructor. In the main thread, this property is always null. You can use this workerData property to pass an initial message to the worker that will be available as soon as it starts so that the worker does not have to wait for a “message” event before it can start doing work.\n\nBy default, process.env in a worker thread is a copy of process.env in the parent thread. But the parent thread can specify a custom set of environment variables by setting the env property of the second argument to the Worker() constructor. As a special (and potentially dangerous) case, the parent thread can set the env property to threads.SHARE_ENV, which will cause the two threads to share a single set of environment variables so that a change in one thread is visible in the other.\n\nBy default, the process.stdin stream in a worker never has any readable data on it. You can change this default by passing stdin: true in the second argument to the Worker() constructor. If you do that, then the stdin property of the Worker object is a Writable stream. Any data that the parent writes to worker.stdin becomes readable on process.stdin in the worker.\n\nBy default, the process.stdout and process.stderr streams in the worker are simply piped to the corresponding streams in the parent thread. This means, for example, that console.log() and console.error() produce output in exactly the same way in a worker thread as they do in the main thread. You can override this default by passing stdout:true or stderr:true in the second argument to the Worker() constructor. If you do this, then any output the worker writes to those streams becomes readable by the parent thread on the worker.stdout and worker.stderr threads. (There is a potentially confusing inversion of stream directions here, and we saw the same thing with with child processes earlier in the chapter: the output streams of a worker thread are input streams for the parent thread, and the input stream of a worker is an output stream for the parent.)\n\nIf a worker thread calls process.exit(), only the thread exits, not the entire process.\n\nWorker threads are not allowed to change shared state of the process they are part of. Functions like process.chdir() and process.setuid() will throw exceptions when invoked from a worker.\n\nOperating system signals (like SIGINT and SIGTERM) are only delivered to the main thread; they cannot be received or handled in worker threads.\n\n### 16.11.3 Communication Channels and MessagePorts\nWhen a new worker thread is created, a communication channel is created along with it that allows messages to be passed back and forth between the worker and the parent thread. As we’ve seen, the worker thread uses threads.parentPort to send and receive messages to and from the parent thread, and the parent thread uses the Worker object to send and receive messages to and from the worker thread.\n\nThe worker thread API also allows the creation of custom communication channels using the MessageChannel API defined by web browsers and covered in §15.13.5. If you have read that section, much of what follows will sound familiar to you.\n\nSuppose a worker needs to handle two different kinds of messages sent by two different modules in the main thread. These two different modules could both share the default channel and send messages with worker.postMessage(), but it would be cleaner if each module has its own private channel for sending messages to the worker. Or consider the case where the main thread creates two independent workers. A custom communication channel can allow the two workers to communicate directly with each other instead of having to send all their messages via the parent.\n\nCreate a new message channel with the MessageChannel() constructor. A MessageChannel object has two properties, named port1 and port2. These properties refer to a pair of MessagePort objects. Calling postMessage() on one of the ports will cause a “message” event to be generated on the other with a structured clone of the Message object:\n\nconst threads = require(\"worker_threads\");\nlet channel = new threads.MessageChannel();\nchannel.port2.on(\"message\", console.log);  // Log any messages we receive\nchannel.port1.postMessage(\"hello\");        // Will cause \"hello\" to be printed\nYou can also call close() on either port to break the connection between the two ports and to signal that no more messages will be exchanged. When close() is called on either port, a “close” event is delivered to both ports.\n\nNote that the code example above creates a pair of MessagePort objects and then uses those objects to transmit a message within the main thread. In order to use custom communication channels with workers, we must transfer one of the two ports from the thread in which it is created to the thread in which it will be used. The next section explains how to do this.\n\n### 16.11.4 Transferring MessagePorts and Typed Arrays\nThe postMessage() function uses the structured clone algorithm, and as we’ve noted, it cannot copy objects like SSockets and Streams. It can handle MessagePort objects, but only as a special case using a special technique. The postMessage() method (of a Worker object, of threads.parentPort, or of any MessagePort object) takes an optional second argument. This argument (called transferList) is an array of objects that are to be transferred between threads rather than being copied.\n\nA MessagePort object cannot be copied by the structured clone algorithm, but it can be transferred. If the first argument to postMessage() has included one or more MessagePorts (nested arbitrarily deeply within the Message object), then those MessagePort objects must also appear as members of the array passed as the second argument. Doing this tells Node that it does not need to make a copy of the MessagePort, and can instead just give the existing object to the other thread. The key thing to understand, however, about transferring values between threads is that once a value is transferred, it can no longer be used in the thread that called postMessage().\n\nHere is how you might create a new MessageChannel and transfer one of its MessagePorts to a worker:\n\n// Create a custom communication channel\nconst threads = require(\"worker_threads\");\nlet channel = new threads.MessageChannel();\n\n// Use the worker's default channel to transfer one end of the new\n// channel to the worker. Assume that when the worker receives this\n// message it immediately begins to listen for messages on the new channel.\nworker.postMessage({ command: \"changeChannel\", data: channel.port1 },\n                   [ channel.port1 ]);\n\n// Now send a message to the worker using our end of the custom channel\nchannel.port2.postMessage(\"Can you hear me now?\");\n\n// And listen for responses from the worker as well\nchannel.port2.on(\"message\", handleMessagesFromWorker);\nMessagePort objects are not the only ones that can be transferred. If you call postMessage() with a typed array as the message (or with a message that contains one or more typed arrays nested arbitrarily deep within the message), that typed array (or those typed arrays) will simply be copied by the structured clone algorithm. But typed arrays can be large; for example, if you are using a worker thread to do image processing on millions of pixels. So for efficiency, postMessage() also gives us the option to transfer typed arrays rather than copying them. (Threads share memory by default. Worker threads in JavaScript generally avoid shared memory, but when we allow this kind of controlled transfer, it can be done very efficiently.) What makes this safe is that when a typed array is transferred to another thread, it becomes unusable in the thread that transferred it. In the image-processing scenario, the main thread could transfer the pixels of an image to the worker thread, and then the worker thread could transfer the processed pixels back to the main thread when it was done. The memory would not need to be copied, but it would never be accessible by two threads at once.\n\nTo transfer a typed array instead of copying it, include the ArrayBuffer that backs the array in the second argument to postMessage():\n\nlet pixels = new Uint32Array(1024*1024);  // 4 megabytes of memory\n\n// Assume we read some data into this typed array, and then transfer the\n// pixels to a worker without copying. Note that we don't put the array\n// itself in the transfer list, but the array's Buffer object instead.\nworker.postMessage(pixels, [ pixels.buffer ]);\nAs with transferred MessagePorts, a transferred typed array becomes unusable once transferred. No exceptions are thrown if you attempt to use a MessagePort or typed array that has been transferred; these objects simply stop doing anything when you interact with them.\n\n### 16.11.5 Sharing Typed Arrays Between Threads\nIn addition to transferring typed arrays between threads, it is actually possible to share a typed array between threads. Simply create a SharedArrayBuffer of the desired size and then use that buffer to create a typed array. When a typed array that is backed by a SharedArrayBuffer is passed via postMessage(), the underlying memory will be shared between the threads. You should not include the shared buffer in the second argument to postMessage() in this case.\n\nYou really should not do this, however, because JavaScript was never designed with thread safety in mind and multithreaded programming is very difficult to get right. (And this is why SharedArrayBuffer was not covered in §11.2: it is a niche feature that is difficult to get right.) Even the simple ++ operator is not thread-safe because it needs to read a value, increment it, and write it back. If two threads are incrementing a value at the same time, it will often only be incremented once, as the following code demonstrates:\n\nconst threads = require(\"worker_threads\");\n\nif (threads.isMainThread) {\n    // In the main thread, we create a shared typed array with\n    // one element. Both threads will be able to read and write\n    // sharedArray[0] at the same time.\n    let sharedBuffer = new SharedArrayBuffer(4);\n    let sharedArray = new Int32Array(sharedBuffer);\n\n    // Now create a worker thread, passing the shared array to it with\n    // as its initial workerData value so we don't have to bother with\n    // sending and receiving a message\n    let worker = new threads.Worker(__filename, { workerData: sharedArray });\n\n    // Wait for the worker to start running and then increment the\n    // shared integer 10 million times.\n    worker.on(\"online\", () => {\n        for(let i = 0; i < 10_000_000; i++) sharedArray[0]++;\n\n        // Once we're done with our increments, we start listening for\n        // message events so we know when the worker is done.\n        worker.on(\"message\", () => {\n            // Although the shared integer has been incremented\n            // 20 million times, its value will generally be much less.\n            // On my computer the final value is typically under 12 million.\n            console.log(sharedArray[0]);\n        });\n    });\n} else {\n    // In the worker thread, we get the shared array from workerData\n    // and then increment it 10 million times.\n    let sharedArray = threads.workerData;\n    for(let i = 0; i < 10_000_000; i++) sharedArray[0]++;\n    // When we're done incrementing, let the main thread know\n    threads.parentPort.postMessage(\"done\");\n}\nOne scenario in which it might be reasonable to use a SharedArrayBuffer is when the two threads operate on entirely separate sections of the shared memory. You might enforce this by creating two typed arrays that serve as views of nonoverlapping regions of the shared buffer, and then have your two threads use those two separate typed arrays. A parallel merge sort could be done like this: one thread sorts the bottom half of an array and the other thread sorts the top half, for example. Or some kinds of image-processing algorithms are also suitable for this approach: multiple threads working on disjoint regions of the image.\n\nIf you really must allow multiple threads to access the same region of a shared array, you can take one step toward thread safety with the functions defined by the Atomics object. Atomics was added to JavaScript when SharedArrayBuffer was to define atomic operations on the elements of a shared array. For example, the Atomics.add() function reads the specified element of a shared array, adds a specified value to it, and writes the sum back into the array. It does this atomically as if it was a single operation, and ensures that no other thread can read or write the value while the operation is taking place. Atomics.add() allows us to rewrite the parallel increment code we just looked at and get the correct result of 20 million increments of a shared array element:\n\nconst threads = require(\"worker_threads\");\n\nif (threads.isMainThread) {\n    let sharedBuffer = new SharedArrayBuffer(4);\n    let sharedArray = new Int32Array(sharedBuffer);\n    let worker = new threads.Worker(__filename, { workerData: sharedArray });\n\n    worker.on(\"online\", () => {\n        for(let i = 0; i < 10_000_000; i++) {\n            Atomics.add(sharedArray, 0, 1);  // Threadsafe atomic increment\n        }\n\n        worker.on(\"message\", (message) => {\n            // When both threads are done, use a threadsafe function\n            // to read the shared array and confirm that it has the\n            // expected value of 20,000,000.\n            console.log(Atomics.load(sharedArray, 0));\n        });\n    });\n} else {\n    let sharedArray = threads.workerData;\n    for(let i = 0; i < 10_000_000; i++) {\n        Atomics.add(sharedArray, 0, 1);      // Threadsafe atomic increment\n    }\n    threads.parentPort.postMessage(\"done\");\n}\nThis new version of the code correctly prints the number 20,000,000. But it is about nine times slower than the incorrect code it replaces. It would be much simpler and much faster to just do all 20 million increments in one thread. Also note that atomic operations may be able to ensure thread safety for image-processing algorithms for which each array element is a value entirely independent of all other values. But in most real-world programs, multiple array elements are often related to one another and some kind of higher-level thread synchronization is required. The low-level Atomics.wait() and Atomics.notify() function can help with this, but a discussion of their use is out of scope for this book.\n\n## 16.12 Summary\nAlthough JavaScript was created to run in web browsers, Node has made JavaScript into a general-purpose programming language. It is particularly popular for implementing web servers, but its deep bindings to the operating system mean that it is also a good alternative to shell scripts.\n\nThe most important topics covered in this long chapter include:\n\nNode’s asynchronous-by-default APIs and its single-threaded, callback, and event-based style of concurrency.\n\nNode’s fundamental datatypes, buffers, and streams.\n\nNode’s “fs” and “path” modules for working with the filesystem.\n\nNode’s “http” and “https” modules for writing HTTP clients and servers.\n\nNode’s “net” module for writing non-HTTP clients and servers.\n\nNode’s “child_process” module for creating and communicating with child processes.\n\nNode’s “worker_threads” module for true multithreaded programming using message-passing instead of shared memory.\n\n1 Node defines a fs.copyFile() function that you would actually use in practice.\n\n2 It is often cleaner and simpler to define the worker code in a separate file. But this trick of having two threads run different sections of the same file blew my mind when I first encountered it for the Unix fork() system call. And I think it is worth demonstrating this technique simply for its strange elegance."
  },
  {
    "path": "content/posts/ch17.md",
    "content": "---\ntitle: \"第 17 章 JavaScript 工具和扩展\"\ndate: 2020-11-02T22:18:26+08:00\n---\n\nCongratulations on reaching the final chapter of this book. If you have read everything that comes before, you now have a detailed understanding of the JavaScript language and know how to use it in Node and in web browsers. This chapter is a kind of graduation present: it introduces a handful of important programming tools that many JavaScript programmers find useful, and also describes two widely used extensions to the core JavaScript language. Whether or not you choose to use these tools and extensions for your own projects, you are almost certain to see them used in other projects, so it is important to at least know what they are.\n\n> 恭喜你读到了这本书的最后一章。如果您已经阅读了前面介绍的所有内容，那么您现在对JavaScript语言有了详细的了解，并且知道如何在Node和web浏览器中使用它。这一章是一份毕业礼物:它介绍了许多JavaScript程序员认为有用的重要编程工具，并描述了两个广泛使用的核心JavaScript语言扩展。无论您是否选择在您自己的项目中使用这些工具和扩展，您几乎肯定会在其他项目中看到它们的使用，所以至少知道它们是什么是很重要的。\n\nThe tools and language extensions covered in this chapter are:\n\n> 本章涵盖的工具和语言扩展包括:\n\n- ESLint for finding potential bugs and style problems in your code.\n- Prettier for formatting your JavaScript code in a standardized way.\n- Jest as an all-in-one solution for writing JavaScript unit tests.\n- npm for managing and installing the software libraries that your program depends on.\n- Code-bundling tools—like webpack, Rollup, and Parcel—that convert your modules of JavaScript code into a single bundle for use on the web.\n- Babel for translating JavaScript code that uses brand-new language features (or that uses language extensions) into JavaScript code that can run in current web browsers.\n- The JSX language extension (used by the React framework) that allows you to describe user interfaces using JavaScript expressions that look like HTML markup.\n- The Flow language extension (or the similar TypeScript extension) that allows you to annotate your JavaScript code with types and check your code for type safety.\n\n---\n\n> - ESLint用于查找代码中潜在的错误和样式问题。\n> - 以标准化的方式格式化你的JavaScript代码更漂亮。\n> - Jest作为编写JavaScript单元测试的一体化解决方案。\n> - npm用于管理和安装您的程序所依赖的软件库。\n> - 代码打包工具——如webpack、Rollup和parcels——将JavaScript代码模块转换为一个单独的包，以供web上使用。\n> - Babel，用于将使用全新语言特性(或使用语言扩展)的JavaScript代码转换为可以在当前浏览器中运行的JavaScript代码。\n> - JSX语言扩展(由React框架使用)，它允许您使用看起来像HTML标记的JavaScript表达式来描述用户界面。\n> - 流语言扩展(或类似的TypeScript扩展)，允许你用类型注释你的JavaScript代码，并检查你的代码的类型安全。\n\nThis chapter does not document these tools and extensions in any comprehensive way. The goal is simply to explain them in enough depth that you can understand why they are useful and when you might want to use them. Everything covered in this chapter is widely used in the JavaScript programming world, and if you do decide to adopt a tool or extension, you’ll find lots of documentation and tutorials online.\n\n> 本章不以任何全面的方式记录这些工具和扩展。我们的目标仅仅是对它们进行足够深入的解释，以便您能够理解它们为什么有用以及何时需要使用它们。本章所涵盖的所有内容都在JavaScript编程领域得到了广泛的应用，如果您决定采用一种工具或扩展，您将在网上找到大量的文档和教程。\n\n## 17.1 Linting with ESLint\n\nIn programming, the term lint refers to code that, while technically correct, is unsightly, or a possible bug, or suboptimal in some way. A linter is a tool for detecting lint in your code, and linting is the process of running a linter on your code (and then fixing your code to remove the lint so that the linter no longer complains).\n\n> 在编程中，术语lint指的是尽管在技术上是正确的，但不美观的代码，或者可能存在bug，或者在某种程度上不是最优的代码。linter是用于检测代码中的linter的工具，linting是在代码上运行linter的过程(然后修改代码以删除linter，使linter不再报错)。\n\nThe most commonly used linter for JavaScript today is ESLint. If you run it and then take the time to actually fix the issues it points out, it will make your code cleaner and less likely to have bugs. Consider the following code:\n\n> 目前JavaScript最常用的linter是ESLint。如果您运行它，然后花时间实际修复它指出的问题，它将使您的代码更干净，并且不太可能出现错误。考虑以下代码:\n\n```js\nvar x = 'unused';\n\nexport function factorial(x) {\n    if (x == 1) {\n      return 1;\n    } else {\n        return x * factorial(x-1)\n    }\n}\n```\nIf you run ESLint on this code, you might get output like this:\n```js\n$ eslint code/ch17/linty.js\n\ncode/ch17/linty.js\n  1:1   error    Unexpected var, use let or const instead      no-var\n  1:5   error    'x' is assigned a value but never used        no-unused-vars\n  1:9   warning  Strings must use doublequote                  quotes\n  4:11  error    Expected '===' and instead saw '=='           eqeqeq\n  5:1   error    Expected indentation of 8 spaces but found 6  indent\n  7:28  error    Missing semicolon                             semi\n\n✖ 6 problems (5 errors, 1 warning)\n  3 errors and 1 warning potentially fixable with the `--fix` option.\n```\nLinters can seem nitpicky sometimes. Does it really matter whether we used double quotes or single quotes for our strings? On the other hand, getting indentation right is important for readability, and using === and let instead of == and var protects you from subtle bugs. And unused variables are dead weight in your code—there is no reason to keep those around.\n\nESLint defines many linting rules and has an ecosystem of plug-ins that add many more. But ESLint is fully configurable, and you can define a configuration file that tunes ESLint to enforce exactly the rules you want and only those rules.\n\n## 17.2 JavaScript Formatting with Prettier\nOne of the reasons that some projects use linters is to enforce a consistent coding style so that when a team of programmers is working on a shared codebase, they use compatible code conventions. This includes code indentation rules, but can also include things like what kind of quotation marks are preferred and whether there should be a space between the for keyword and the open parenthesis that follows it.\n\nA modern alternative to enforcing code formatting rules via a linter is to adopt a tool like Prettier to automatically parse and reformat all of your code.\n\nSuppose you have written the following function, which works, but is formatted unconventionally:\n```js\nfunction factorial(x)\n{\n         if(x===1){return 1}\n           else{return x*factorial(x-1)}\n}\n```\nRunning Prettier on this code fixes the indentation, adds missing semicolons, adds spaces around binary operators and inserts line breaks after { and before }, resulting in much more conventional-looking code:\n```js\n$ prettier factorial.js\nfunction factorial(x) {\n  if (x === 1) {\n    return 1;\n  } else {\n    return x * factorial(x - 1);\n  }\n}\n```\nIf you invoke Prettier with the --write option, it will simply reformat the specified file in place rather than printing a reformatted version. If you use git to manage your source code, you can invoke Prettier with the --write option in a commit hook so that code is automatically formatted before being checked in.\n\nPrettier is particularly powerful if you configure your code editor to run it automatically every time you save a file. I find it liberating to write sloppy code and see it fixed automatically for me.\n\nPrettier is configurable, but it only has a few options. You can select the maximum line length, the indentation amount, whether semicolons should be used, whether strings should be single- or double-quoted, and a few other things. In general, Prettier’s default options are quite reasonable. The idea is that you just adopt Prettier for your project and then never have to think about code formatting again.\n\nPersonally, I really like using Prettier on JavaScript projects. I have not used it for the code in this book, however, because in much of my code I rely on careful hand formatting to align my comments vertically, and Prettier messes them up.\n\n## 17.3 Unit Testing with Jest\nWriting tests is an important part of any nontrivial programming project. Dynamic languages like JavaScript support testing frameworks that dramatically reduce the effort required to write tests, and almost make test writing fun! There are a lot of test tools and libraries for JavaScript, and many are written in a modular way so that it is possible to pick one library as your test runner, another library for assertions, and a third for mocking. In this section, however, we’ll describe Jest, which is a popular framework that includes everything you need in a single package.\n\nSuppose you’ve written the following function:\n\nconst getJSON = require(\"./getJSON.js\");\n```js\n/**\n * getTemperature() takes the name of a city as its input, and returns\n * a Promise that will resolve to the current temperature of that city,\n * in degrees Fahrenheit. It relies on a (fake) web service that returns\n * world temperatures in degrees Celsius.\n */\nmodule.exports = async function getTemperature(city) {\n    // Get the temperature in Celsius from the web service\n    let c = await getJSON(\n        `https://globaltemps.example.com/api/city/${city.toLowerCase()}`\n    );\n    // Convert to Fahrenheit and return that value.\n    return (c * 5 / 9) + 32;  // TODO: double-check this formula\n};\nA good set of tests for this function might verify that getTemperature() is fetching the right URL, and that it is converting temperature scales correctly. We can do this with a Jest-based test like the following. This code defines a mock implementation of getJSON() so that the test does not actually make a network request. And because getTemperature() is an async function, the tests are async as well—it can be tricky to test asynchronous functions, but Jest makes it relatively easy:\n\n// Import the function we are going to test\nconst getTemperature = require(\"./getTemperature.js\");\n\n// And mock the getJSON() module that getTemperature() depends on\njest.mock(\"./getJSON\");\nconst getJSON = require(\"./getJSON.js\");\n\n// Tell the mock getJSON() function to return an already resolved Promise\n// with fulfillment value 0.\ngetJSON.mockResolvedValue(0);\n\n// Our set of tests for getTemperature() begins here\ndescribe(\"getTemperature()\", () => {\n    // This is the first test. We're ensuring that getTemperature() calls\n    // getJSON() with the URL that we expect\n    test(\"Invokes the correct API\", async () => {\n        let expectedURL = \"https://globaltemps.example.com/api/city/vancouver\";\n        let t = await(getTemperature(\"Vancouver\"));\n        // Jest mocks remember how they were called, and we can check that.\n        expect(getJSON).toHaveBeenCalledWith(expectedURL);\n    });\n\n    // This second test verifies that getTemperature() converts\n    // Celsius to Fahrenheit correctly\n    test(\"Converts C to F correctly\", async () => {\n        getJSON.mockResolvedValue(0);                // If getJSON returns 0C\n        expect(await getTemperature(\"x\")).toBe(32);  // We expect 32F\n\n        // 100C should convert to 212F\n        getJSON.mockResolvedValue(100);              // If getJSON returns 100C\n        expect(await getTemperature(\"x\")).toBe(212); // We expect 212F\n    });\n});\n```\nWith the test written, we can use the jest command to run it, and we discover that one of our tests fails:\n```js\n$ jest getTemperature\n FAIL  ch17/getTemperature.test.js\n  getTemperature()\n    ✓ Invokes the correct API (4ms)\n    ✕ Converts C to F correctly (3ms)\n\n  ● getTemperature() › Converts C to F correctly\n\n    expect(received).toBe(expected) // Object.is equality\n\n    Expected: 212\n    Received: 87.55555555555556\n\n      29 |         // 100C should convert to 212F\n      30 |         getJSON.mockResolvedValue(100); // If getJSON returns 100C\n    > 31 |         expect(await getTemperature(\"x\")).toBe(212); // Expect 212F\n         |                                           ^\n      32 |     });\n      33 | });\n      34 |\n\n      at Object.<anonymous> (ch17/getTemperature.test.js:31:43)\n\nTest Suites: 1 failed, 1 total\nTests:       1 failed, 1 passed, 2 total\nSnapshots:   0 total\nTime:        1.403s\nRan all test suites matching /getTemperature/i.\n```\nOur getTemperature() implementation is using the wrong formula for converting C to F. It multiplies by 5 and divides by 9 rather than multiplying by 9 and dividing by 5. If we fix the code and run Jest again, we can see the tests pass. And, as a bonus, if we add the --coverage argument when we invoke jest, it will compute and display the code coverage for our tests:\n```js\n$ jest --coverage getTemperature\n PASS  ch17/getTemperature.test.js\n  getTemperature()\n    ✓ Invokes the correct API (3ms)\n    ✓ Converts C to F correctly (1ms)\n\n------------------|--------|---------|---------|---------|------------------|\nFile              | % Stmts| % Branch|  % Funcs|  % Lines| Uncovered Line #s|\n------------------|--------|---------|---------|---------|------------------|\nAll files         |   71.43|      100|    33.33|    83.33|                  |\n getJSON.js       |   33.33|      100|        0|       50|                 2|\n getTemperature.js|     100|      100|      100|      100|                  |\n------------------|--------|---------|---------|---------|------------------|\nTest Suites: 1 passed, 1 total\nTests:       2 passed, 2 total\nSnapshots:   0 total\nTime:        1.508s\nRan all test suites matching /getTemperature/i.\n```\nRunning our test gave us 100% code coverage for the module we were testing, which is exactly what we wanted. It only gave us partial coverage of getJSON(), but we mocked that module and were not trying to test it, so that is expected.\n\n## 17.4 Package Management with npm\nIn modern software development, it is common for any nontrivial program that you write to depend on third-party software libraries. If you’re writing a web server in Node, for example, you might be using the Express framework. And if you’re creating a user interface to be displayed in a web browser, you might use a frontend framework like React or LitElement or Angular. A package manager makes it easy to find and install third-party packages like these. Just as importantly, a package manager keeps track of what packages your code depends on and saves this information into a file so that when someone else wants to try your program, they can download your code and your list of dependencies, then use their own package manager to install all the third-party packages that your code needs.\n\nnpm is the package manager that is bundled with Node, and was introduced in §16.1.5. It is just as useful for client-side JavaScript programming as it is for server-side programming with Node, however.\n\nIf you are trying out someone else’s JavaScript project, then one of the first things you will often do after downloading their code is to type npm install. This reads the dependencies listed in the package.json file and downloads the third-party packages that the project needs and saves them in a node_modules/ directory.\n\nYou can also type npm install `<package-name>` to install a particular package to your project’s node_modules/ directory:\n```js\n$ npm install express\n```\nIn addition to installing the named package, npm also makes a record of the dependency in the package.json file for the project. Recording dependencies in this way is what allows others to install those dependencies simply by typing npm install.\n\nThe other kind of dependency is on developer tools that are needed by developers who want to work on your project, but aren’t actually needed to run the code. If a project uses Prettier, for example, to ensure that all of its code is consistently formatted, then Prettier is a “dev dependency,” and you can install and record one of these with --save-dev:\n```js\n$ npm install --save-dev prettier\n```\nSometimes you might want to install developer tools globally so that they are accessible anywhere even for code that is not part of a formal project with a package.json file and a node_modules/ directory. For that you can use the -g (for global) option:\n```js\n$ npm install -g eslint jest\n/usr/local/bin/eslint -> /usr/local/lib/node_modules/eslint/bin/eslint.js\n/usr/local/bin/jest -> /usr/local/lib/node_modules/jest/bin/jest.js\n+ jest@24.9.0\n+ eslint@6.7.2\nadded 653 packages from 414 contributors in 25.596s\n\n$ which eslint\n/usr/local/bin/eslint\n$ which jest\n/usr/local/bin/jest\n```\nIn addition to the “install” command, npm supports “uninstall” and “update” commands, which do what their names say. npm also has an interesting “audit” command that you can use to find and fix security vulnerabilities in your dependencies:\n```js\n$ npm audit --fix\n\n                       === npm audit security report ===\n\nfound 0 vulnerabilities\n in 876354 scanned packages\n```\nWhen you install a tool like ESLint locally for a project, the eslint script winds up in ./node_modules/.bin/eslint, which makes the command awkward to run. Fortunately, npm is bundled with a command known as “npx,” which you can use to run locally installed tools with commands like npx eslint or npx jest. (And if you use npx to invoke a tool that has not been installed yet, it will install it for you.)\n\nThe company behind npm also maintains the https://npmjs.com package repository, which holds hundreds of thousands of open source packages. But you don’t have to use the npm package manager to access this repository of packages. Alternatives include yarn and pnpm.\n\n## 17.5 Code Bundling\nIf you are writing a large JavaScript program to run in web browsers, you will probably want to use a code-bundling tool, especially if you use external libraries that are delivered as modules. Web developers have been using ES6 modules (§10.3) for years, since well before the import and export keywords were supported on the web. In order to do this, programmers use a code-bundler tool that starts at the main entry point (or entry points) of the program and follows the tree of import directives to find all modules that the program depends on. It then combines all of those individual module files into a single bundle of JavaScript code and rewrites the import and export directives to make the code work in this new form. The result is a single file of code that can be loaded into a web browser that does not support modules.\n\nES6 modules are nearly universally supported by web browsers today, but web developers still tend to use code bundlers, at least when releasing production code. Developers find that user experience is best when a single medium-sized bundle of code is loaded when a user first visits a website than when many small modules are loaded.\n\n#### NOTE\nWeb performance is a notoriously tricky topic and there are lots of variables to consider, including ongoing improvements by browser vendors, so the only way to be sure of the fastest way to load your code is by testing thoroughly and measuring carefully. Keep in mind that there is one variable that is completely under your control: code size. Less JavaScript code will always load and run faster than more JavaScript code!\n\nThere are a number of good JavaScript bundler tools available. Commonly used bundlers include webpack, Rollup and Parcel. The basic features of bundlers are more or less the same, and they are differentiated based on how configurable they are or how easy they are to use. Webpack has been around for a long time, has a large ecosystem of plug-ins, is highly configurable, and can support older nonmodule libraries. But it can also be complex and hard to configure. At the other end of the spectrum is Parcel which is intended as a zero-configuration alternative that simply does the right thing.\n\nIn addition to performing basic bundling, bundler tools can also provide some additional features:\n\n- Some programs have more than one entry point. A web application with multiple pages, for example, could be written with a different entry point for each page. Bundlers generally allow you to create one bundle per entry point or to create a single bundle that supports multiple entry points.\n- Programs can use import() in its functional form (§10.3.6) instead of its static form to dynamically load modules when they are actually needed rather than statically loading them at program startup time. Doing this is often a good way to improve the startup time for your program. Bundler tools that support import() may be able to produce multiple output bundles: one to load at startup time, and one or more that are loaded dynamically when needed. This can work well if there are only a few calls to import() in your program and they load modules with relatively disjoint sets of dependencies. If the dynamically loaded modules share dependencies then it becomes tricky to figure out how many bundles to produce, and you are likely to have to manually configure your bundler to sort this out.\n- Bundlers can generally output a source map file that defines a mapping between the lines of code in the bundle and the corresponding lines in the original source files. This allows browser developer tools to automatically display JavaScript errors at their original unbundled locations.\n- Sometimes when you import a module into your program, you only use a few of its features. A good bundler tool can analyze the code to determine which parts are unused and can be omitted from the bundles. This feature goes by the whimsical name of “tree-shaking.”\n- Bundlers typically have a plug-in–based architecture and support plug-ins that allow importing and bundling “modules” that are not actually files of JavaScript code. Suppose that your program includes a large JSON-compatible data structure. Code bundlers can be configured to allow you to move that data structure into a separate JSON file and then import it into your program with a declaration like import widgets from \"./big-widget-list.json\". Similarly, web developers who embed CSS into their JavaScript programs can use bundler plug-ins that allow them to import CSS files with an import directive. Note, however, that if you import anything other than a JavaScript file, you are using a nonstandard JavaScript extension and making your code dependent on the bundler tool.\n- In a language like JavaScript that does not require compilation, running a bundler tool feels like a compilation step, and it is frustrating to have to run a bundler after every code edit before you can run the code in your browser. Bundlers typically support filesystem watchers that detect edits to any files in a project directory and automatically regenerate the necessary bundles. With this feature in place you can typically save your code and then immediately reload your web browser window to try it out.\n- Some bundlers also support a “hot module replacement” mode for developers where each time a bundle is regenerated, it is automatically loaded into the browser. When this works, it is a magical experience for developers, but there are some tricks going on under the hood to make it work, and it is not suitable for all projects.\n\n## 17.6 Transpilation with Babel\nBabel is a tool that compiles JavaScript written using modern language features into JavaScript that does not use those modern language features. Because it compiles JavaScript to JavaScript, Babel is sometimes called a “transpiler.” Babel was created so that web developers could use the new language features of ES6 and later while still targeting web browsers that only supported ES5.\n\nLanguage features such as the ** exponentiation operator and arrow functions can be transformed relatively easily into Math.pow() and function expressions. Other language features, such as the class keyword, require much more complex transformations, and, in general, the code output by Babel is not meant to be human readable. Like bundler tools, however, Babel can produce source maps that map transformed code locations back to their original source locations, and this helps dramatically when working with transformed code.\n\nBrowser vendors are doing a better job of keeping up with the evolution of the JavaScript language, and there is much less need today to compile away arrow functions and class declarations. Babel can still help when you want to use the very latest features like underscore separators in numeric literals.\n\nLike most of the other tools described in this chapter, you can install Babel with npm and run it with npx. Babel reads a .babelrc configuration file that tells it how you would like your JavaScript code transformed. Babel defines “presets” that you can choose from depending on which language extensions you want to use and how aggressively you want to transform standard language features. One of Babel’s interesting presets is for code compression by minification (stripping comments and whitespace, renaming variables, and so on).\n\nIf you use Babel and a code-bundling tool, you may be able to set up the code bundler to automatically run Babel on your JavaScript files as it builds the bundle for you. If so, this can be a convenient option because it simplifies the process of producing runnable code. Webpack, for example, supports a “babel-loader” module that you can install and configure to run Babel on each JavaScript module as it is bundled up.\n\nEven though there is less need to transform the core JavaScript language today, Babel is still commonly used to support nonstandard extensions to the language, and we’ll describe two of these language extensions in the sections that follow.\n\n## 17.7 JSX: Markup Expressions in JavaScript\nJSX is an extension to core JavaScript that uses HTML-style syntax to define a tree of elements. JSX is most closely associated with the React framework for user interfaces on the web. In React, the trees of elements defined with JSX are ultimately rendered into a web browser as HTML. Even if you have no plans to use React yourself, its popularity means that you are likely to see code that uses JSX. This section explains what you need to know to make sense of of it. (This section is about the JSX language extension, not about React, and it explains only enough of React to provide context for the JSX syntax.)\n\nYou can think of a JSX element as a new type of JavaScript expression syntax. JavaScript string literals are delimited with quotation marks, and regular expression literals are delimited with slashes. In the same way, JSX expression literals are delimited with angle brackets. Here is a very simple one:\n```js\nlet line = <hr/>;\n```\nIf you use JSX, you will need to use Babel (or a similar tool) to compile JSX expressions into regular JavaScript. The transformation is simple enough that some developers choose to use React without using JSX. Babel transforms the JSX expression in this assignment statement into a simple function call:\n```js\nlet line = React.createElement(\"hr\", null);\n```\nJSX syntax is HTML-like, and like HTML elements, React elements can have attributes like these:\n```js\nlet image = <img src=\"logo.png\" alt=\"The JSX logo\" hidden/>;\n```\nWhen an element has one or more attributes, they become properties of an object passed as the second argument to createElement():\n```js\nlet image = React.createElement(\"img\", {\n              src: \"logo.png\",\n              alt: \"The JSX logo\",\n              hidden: true\n            });\n```\nLike HTML elements, JSX elements can have strings and other elements as children. Just as JavaScript’s arithmetic operators can be used to write arithmetic expressions of arbitrary complexity, JSX elements can also be nested arbitrarily deeply to create trees of elements:\n```js\nlet sidebar = (\n  <div className=\"sidebar\">\n    <h1>Title</h1>\n    <hr/>\n    <p>This is the sidebar content</p>\n  </div>\n);\n```\nRegular JavaScript function call expressions can also be nested arbitrarily deeply, and these nested JSX expressions translate into a set of nested createElement() calls. When an JSX element has children, those children (which are typically strings and other JSX elements) are passed as the third and subsequent arguments:\n```js\nlet sidebar = React.createElement(\n    \"div\", { className: \"sidebar\"},  // This outer call creates a <div>\n    React.createElement(\"h1\", null,  // This is the first child of the <div/>\n                        \"Title\"),    // and its own first child.\n    React.createElement(\"hr\", null), // The second child of the <div/>.\n    React.createElement(\"p\", null,   // And the third child.\n                        \"This is the sidebar content\"));\n```\nThe value returned by React.createElement() is an ordinary JavaScript object that is used by React to render output in a browser window. Since this section is about the JSX syntax and not about React, we’re not going to go into any detail about the returned Element objects or the rendering process. It is worth noting that you can configure Babel to compile JSX elements to invocations of a different function, so if you think that JSX syntax would be a useful way to express other kinds of nested data structures, you can adopt it for your own non-React uses.\n\nAn important feature of JSX syntax is that you can embed regular JavaScript expressions within JSX expressions. Within a JSX expression, text within curly braces is interpreted as plain JavaScript. These nested expressions are allowed as attribute values and as child elements. For example:\n```js\nfunction sidebar(className, title, content, drawLine=true) {\n  return (\n    <div className={className}>\n      <h1>{title}</h1>\n      { drawLine && <hr/> }\n      <p>{content}</p>\n    </div>\n  );\n}\n```\nThe sidebar() function returns a JSX element. It takes four arguments that it uses within the JSX element. The curly brace syntax may remind you of template literals that use ${} to include JavaScript expressions within strings. Since we know that JSX expressions compile into function invocations, it should not be surprising that arbitrary JavaScript expressions can be included because function invocations can be written with arbitrary expressions as well. This example code is translated by Babel into the following:\n```js\nfunction sidebar(className, title, content, drawLine=true) {\n  return React.createElement(\"div\", { className: className },\n                             React.createElement(\"h1\", null, title),\n                             drawLine && React.createElement(\"hr\", null),\n                             React.createElement(\"p\", null, content));\n}\n```\nThis code is easy to read and understand: the curly braces are gone and the resulting code passes the incoming function parameters to React.createElement() in a natural way. Note the neat trick that we’ve done here with the drawLine parameter and the short-circuiting && operator. If you call sidebar() with only three arguments, then drawLine defaults to true, and the fourth argument to the outer createElement() call is the `<hr/>` element. But if you pass false as the fourth argument to sidebar(), then the fourth argument to the outer createElement() call evaluates to false, and no `<hr/>` element is ever created. This use of the && operator is a common idiom in JSX to conditionally include or exclude a child element depending on the value of some other expression. (This idiom works with React because React simply ignores children that are false or null and does not produce any output for them.)\n\nWhen you use JavaScript expressions within JSX expressions, you are not limited to simple values like the string and boolean values in the preceding example. Any JavaScript value is allowed. In fact, it is quite common in React programming to use objects, arrays, and functions. Consider the following function, for example:\n```js\n// Given an array of strings and a callback function return a JSX element\n// representing an HTML <ul> list with an array of <li> elements as its child.\nfunction list(items, callback) {\n  return (\n    <ul style={ {padding:10, border:\"solid red 4px\"} }>\n      {items.map((item,index) => {\n        <li onClick={() => callback(index)} key={index}>{item}</li>\n      })}\n    </ul>\n  );\n}\n```\nThis function uses an object literal as the value of the style attribute on the `<ul>` element. (Note that double curly braces are required here.) The `<ul>` element has a single child, but the value of that child is an array. The child array is the array created by using the map() function on the input array to create an array of `<li>` elements. (This works with React because the React library flattens the children of an element when it renders them. An element with one array child is the same as that element with each of those array elements as children.) Finally, note that each of the nested `<li>` elements has an onClick event handler attribute whose value is an arrow function. The JSX code compiles to the following pure JavaScript code (which I have formatted with Prettier):\n```js\nfunction list(items, callback) {\n  return React.createElement(\n    \"ul\",\n    { style: { padding: 10, border: \"solid red 4px\" } },\n    items.map((item, index) =>\n      React.createElement(\n        \"li\",\n        { onClick: () => callback(index), key: index },\n        item\n      )\n    )\n  );\n}\n```\nOne other use of object expressions in JSX is with the object spread operator (§6.10.4) to specify multiple attributes at once. Suppose that you find yourself writing a lot of JSX expressions that repeat a common set of attributes. You can simplify your expressions by defining the attributes as properties of an object and “spreading them into” your JSX elements:\n```js\nlet hebrew = { lang: \"he\", dir: \"rtl\" }; // Specify language and direction\nlet shalom = <span className=\"emphasis\" {...hebrew}>שלום</span>;\nBabel compiles this to use an _extends() function (omitted here) that combines that className attribute with the attributes contained in the hebrew object:\n\nlet shalom = React.createElement(\"span\",\n                                 _extends({className: \"emphasis\"}, hebrew),\n                                 \"\\u05E9\\u05DC\\u05D5\\u05DD\");\n```\nFinally, there is one more important feature of JSX that we have not covered yet. As you’ve seen, all JSX elements begin with an identifier immediately after the opening angle bracket. If the first letter of this identifier is lowercase (as it has been in all of the examples here), then the identifier is passed to createElement() as a string. But if the first letter of the identifier is uppercase, then it is treated as an actual identifer, and it is the JavaScript value of that identifier that is passed as the first argument to createElement(). This means that the JSX expression `<Math/>` compiles to JavaScript code that passes the global Math object to React.createElement().\n\nFor React, this ability to pass non-string values as the first argument to createElement() enables the creation of components. A component is a way of writing a simple JSX expression (with an uppercase component name) that represents a more complex expression (using lowercase HTML tag names).\n\nThe simplest way to define a new component in React is to write a function that takes a “props object” as its argument and returns a JSX expression. A props object is simply a JavaScript object that represents attribute values, like the objects that are passed as the second argument to createElement(). Here, for example, is another take on our sidebar() function:\n```js\nfunction Sidebar(props) {\n  return (\n    <div>\n      <h1>{props.title}</h1>\n      { props.drawLine && <hr/> }\n      <p>{props.content}</p>\n    </div>\n  );\n}\n```\nThis new Sidebar() function is a lot like the earlier sidebar() function. But this one has a name that begins with a capital letter and takes a single object argument instead of separate arguments. This makes it a React component and means that it can be used in place of an HTML tag name in JSX expressions:\n```js\nlet sidebar = <Sidebar title=\"Something snappy\" content=\"Something wise\"/>;\nThis <Sidebar/> element compiles like this:\n\nlet sidebar = React.createElement(Sidebar, {\n  title: \"Something snappy\",\n  content: \"Something wise\"\n});\n```\nIt is a simple JSX expression, but when React renders it, it will pass the second argument (the Props object) to the first argument (the Sidebar() function) and will use the JSX expression returned by that function in place of the `<Sidebar>` expression.\n\n## 17.8 Type Checking with Flow\nFlow is a language extension that allows you to annotate your JavaScript code with type information, and a tool for checking your JavaScript code (both annotated and unannotated) for type errors. To use Flow, you start writing code using the Flow language extension to add type annotations. Then you run the Flow tool to analyze your code and report type errors. Once you have fixed the errors and are ready to run the code, you use Babel (perhaps automatically as part of the code-bundling process) to strip the Flow type annotations out of your code. (One of the nice things about the Flow language extension is that there isn’t any new syntax that Flow has to compile or transform. You use the Flow language extension to add annotations to the code, and all Babel has to do is to strip those annotations out to return your code to standard JavaScript.)\n\n#### TYPESCRIPT VERSUS FLOW\nTypeScript is a very popular alternative to Flow. TypeScript is an extension of JavaScript that adds types as well as other language features. The TypeScript compiler “tsc” compiles TypeScript programs into JavaScript programs and in the process analyzes them and reports type errors in much the same the way that Flow does. tsc is not a Babel plugin: it is its own standalone compiler.\n\nSimple type annotations in TypeScript are usually written identically to the same annotations in Flow. For more advanced typing, the syntax of the two extensions diverges, but the intent and value of the two extensions is the same. My goal in this section is to explain the benefits of type annotations and static code analysis. I’ll be doing that with examples based on Flow, but everything demonstrated here can also be achieved with TypeScript with relatively simple syntax changes.\n\nTypeScript was released in 2012, before ES6, when JavaScript did not have a class keyword or a for/of loop or modules or Promises. Flow is a narrow language extension that adds type annotations to JavaScript and nothing else. TypeScript, by contrast, was very much designed as a new language. As its name implies, adding types to JavaScript is the primary purpose of TypeScript, and it is the reason that people use it today. But types are not the only feature that TypeScript adds to JavaScript: the TypeScript language has enum and namespace keywords that simply do not exist in JavaScript. In 2020, TypeScript has better integration with IDEs and code editors (particularly VSCode, which, like TypeScript, is from Microsoft) than Flow does.\n\nUltimately, this is a book about JavaScript, and I’m covering Flow here instead of TypeScript because I don’t want to take the focus off of JavaScript. But everything you learn here about adding types to JavaScript will be helpful to you if you decide to adopt TypeScript for your projects.\n\nUsing Flow requires commitment, but I have found that for medium and large projects, the extra effort is worth it. It takes extra time to add type annotations to your code, to run Flow every time you edit the code, and to fix the type errors it reports. But in return Flow will enforce good coding discipline and will not allow you to cut corners that can lead to bugs. When I have worked on projects that use Flow, I have been impressed by the number of errors it found in my own code. Being able to fix those issues before they became bugs is a great feeling and gives me extra confidence that my code is correct.\n\nWhen I first started using Flow, I found that it was sometimes difficult to understand why it was complaining about my code. With some practice, though, I came to understand its error messages and found that it was usually easy to make minor changes to my code to make it safer and to satisfy Flow.1 I do not recommend using Flow if you still feel like you are learning JavaScript itself. But once you are confident with the language, adding Flow to your JavaScript projects will push you to take your programming skills to the next level. And this, really, is why I’m dedicating the last section of this book to a Flow tutorial: because learning about JavaScript type systems offers a glimpse of another level, or another style, of programming.\n\nThis section is a tutorial, and it does not attempt to cover Flow comprehensively. If you decide to try Flow, you will almost certainly end up spending time reading the documentation at https://flow.org. On the other hand, you do not need to master the Flow type system before you can start making practical use of it in your projects: the simple uses of Flow described here will take you a long way.\n\n### 17.8.1 Installing and Running Flow\nLike the other tools described in this chapter, you can install the Flow type-checking tool using a package manager, with a command like npm install -g flow-bin or npm install --save-dev flow-bin. If you install the tool globally with -g, then you can run it with flow. And if you install it locally in your project with --save-dev, then you can run it with npx flow. Before using Flow to do type checking, the first time run it as flow --init in the root directory of your project to create a .flowconfig configuration file. You may never need to add anything to this file, but Flow needs it to know where your project root is.\n\nWhen you run Flow, it will find all the JavaScript source code in your project, but it will only report type errors for the files that have “opted in” to type checking by adding a // @flow comment at the top of the file. This opt-in behavior is important because it means that you can adopt Flow for existing projects and then begin to convert your code one file at a time, without being bothered by errors and warnings on files that have not yet been converted.\n\nFlow may be able to find errors in your code even if all you do is opt in with a // @flow comment. Even if you do not use the Flow language extension and add no type annotations to your code, the Flow type checker tool can still make inferences about the values in your program and alert you when you use them inconsistently.\n\nConsider the following Flow error message:\n```js\nError ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ variableReassignment.js:6:3\n\nCannot assign 1 to i.r because:\n • property r is missing in number [1].\n\n     2│ let i = { r: 0, i: 1 };    // The complex number 0+1i\n [1] 3│ for(i = 0; i < 10; i++) {  // Oops! The loop variable overwrites i\n     4│     console.log(i);\n     5│ }\n     6│ i.r = 1;                   // Flow detects the error here\n```\nIn this case, we declare the variable i and assign an object to it. Then we use i again as a loop variable, overwriting the object. Flow notices this and flags an error when we try to use i as if it still held an object. (A simple fix would be to write for(let i = 0; making the loop variable local to the loop.)\n\nHere is another error that Flow detects even without type annotations:\n```js\nError ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ size.js:3:14\n\nCannot get x.length because property length is missing in Number [1].\n\n     1│ // @flow\n     2│ function size(x) {\n     3│     return x.length;\n     4│ }\n [1] 5│ let s = size(1000);\n```\nFlow sees that the size() function takes a single argument. It doesn’t know the type of that argument, but it can see that the argument is expected to have a length property. When it sees this size() function being called with a numeric argument, it correctly flags this as an error because numbers do not have length properties.\n\n### 17.8.2 Using Type Annotations\nWhen you declare a JavaScript variable, you can add a Flow type annotation to it by following the variable name with a colon and the type:\n```js\nlet message: string = \"Hello world\";\nlet flag: boolean = false;\nlet n: number = 42;\n```\nFlow would know the types of these variables even if you did not annotate them: it can see what values you assign to each variable, and it keeps track of that. If you add type annotations, however, Flow knows both the type of the variable and that you have expressed the intent that the variable should always be of that type. So if you use the type annotation, Flow will flag an error if you ever assign a value of a different type to that variable. Type annotations for variables are also particularly useful if you tend to declare all your variables up at the top of a function before they are used.\n\nType annotations for function arguments are like annotations for variables: follow the name of the function argument with a colon and the type name. When annotating a function, you typically also add an annotation for the return type of the function. This goes between the close parenthesis and the open curly brace of the function body. Functions that return nothing use the Flow type void.\n\nIn the preceding example we defined a size() function that expected an argument with a length property. Here’s how we could change that function to explicitly specify that it expects a string argument and returns a number. Note, Flow now flags an error if we pass an array to the function, even though the function would work in that case:\n```js\nError ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ size2.js:5:18\n\nCannot call size with array literal bound to s because array literal [1]\nis incompatible with string [2].\n\n [2] 2│ function size(s: string): number {\n     3│     return s.length;\n     4│ }\n [1] 5│ console.log(size([1,2,3]));\n```\nUsing type annotations with arrow functions is also possible, though it can turn this normally succinct syntax into something more verbose:\n```js\nconst size = (s: string): number => s.length;\n```\nAn important thing to understand about Flow is that the JavaScript value null has the Flow type null and the JavaScript value undefined has the Flow type void. But neither of these values is a member of any other type (unless you explicitly add it). If you declare a function parameter to be a string, then it must be a string, and it is an error to pass null or to pass undefined or to omit the argument (which is basically the same thing as passing undefined):\n```js\nError ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ size3.js:3:18\n\nCannot call size with null bound to s because null [1] is incompatible\nwith string [2].\n\n     1│ // @flow\n [2] 2│ const size = (s: string): number => s.length;\n [1] 3│ console.log(size(null));\n```\nIf you want to allow null and undefined as legal values for a variable or function argument, simply prefix the type with a question mark. For example, use ?string or ?number instead of string or number. If we change our size() function to expect an argument of type ?string, then Flow doesn’t complain when we pass null to the function. But it now has something else to complain about:\n```js\nError ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ size4.js:3:14\n\nCannot get s.length because property length is missing in null or\nundefined [1].\n\n     1│ // @flow\n [1] 2│ function size(s: ?string): number {\n     3│     return s.length;\n     4│ }\n     5│ console.log(size(null));\n```\nWhat Flow is telling us here is that it is not safe to write s.length because, at this place in our code, s might be null or undefined, and those values do not have length properties. This is where Flow makes sure we do not cut any corners. If a value might be null, Flow will insist that we check for that case before we do anything that depends on the value not being null.\n\nIn this case, we can fix the issue by changing the body of the function as follows:\n```js\nfunction size(s: ?string): number {\n    // At this point in the code, s could be a string or null or undefined.\n    if (s === null || s === undefined) {\n        // In this block, Flow knows that s is null or undefined.\n        return -1;\n    } else {\n        // And in this block, Flow knows that s is a string.\n        return s.length;\n    }\n}\n```\nWhen the function is first called, the parameter can have more than one type. But by adding type-checking code, we create a block within the code where Flow knows for sure that the parameter is a string. When we use s.length within that block, Flow does not complain. Note that Flow does not require you to write verbose code like this. Flow would also be satisfied if we just replaced the body of the size() function with return s ? s.length : -1;.\n\nFlow syntax allows a question mark before any type specification to indicate that, in addition to the specified type, null and undefined are allowed as well. Question marks can also appear after a parameter name to indicate that the parameter itself is optional. So if we changed the declaration of the parameter s from s: ?string to s? : string, that would mean it is OK to call size() with no arguments (or with the value undefined, which is the same as omitting it), but that if we do call it with a parameter other than undefined, then that parameter must be a string. In this case, null is not a legal value.\n\nSo far, we’ve discussed primitive types string, number, boolean, null, and void and have demonstrated how you can use use them with variable declarations, function parameters, and function return values. The subsections that follow describe some more complex types supported by Flow.\n\n### 17.8.3 Class Types\nIn addition to the primitive types that Flow knows about, it also knows about all of JavaScript’s built-in classes and allows you to use class name as types. The following function, for example, uses type annotations to indicate that it should be invoked with one Date object and one RegExp object:\n```js\n// @flow\n// Return true if the ISO representation of the specified date\n// matches the specified pattern, or false otherwise.\n// E.g: const isTodayChristmas = dateMatches(new Date(), /^\\d{4}-12-25T/);\nexport function dateMatches(d: Date, p: RegExp): boolean {\n    return p.test(d.toISOString());\n}\n```\nIf you define your own classes with the class keyword, those classes automatically become valid Flow types. In order to make this work, however, Flow does require you to use type annotations in the class. In particular, each property of the class must have its type declared. Here is a simple complex number class that demonstrates this:\n```js\n// @flow\nexport default class Complex {\n    // Flow requires an extended class syntax that includes type annotations\n    // for each of the properties used by the class.\n    i: number;\n    r: number;\n    static i: Complex;\n\n    constructor(r: number, i:number) {\n        // Any properties initialized by the constructor must have Flow type\n        // annotations above.\n        this.r = r;\n        this.i = i;\n    }\n\n    add(that: Complex) {\n        return new Complex(this.r + that.r, this.i + that.i);\n    }\n}\n\n// This assignment would not be allowed by Flow if there was not a\n// type annotation for i inside the class.\nComplex.i = new Complex(0,1);\n```\n### 17.8.4 Object Types\nThe Flow type to describe an object looks a lot like an object literal, except that property values are replaced by property types. Here, for example, is a function that expects an object with numeric x and y properties:\n```js\n// @flow\n// Given an object with numeric x and y properties, return the\n// distance from the origin to the point (x,y) as a number.\nexport default function distance(point: {x:number, y:number}): number {\n    return Math.hypot(point.x, point.y);\n}\n```\nIn this code, the text {x:number, y:number} is a Flow type, just like string or Date is. As with any type, you can add a question mark at the front to indicate that null and undefined should also be allowed.\n\nWithin an object type, you can follow any of the property names with a question mark to indicate that that property is optional and may be omitted. For example, you might write the type for an object that represents a 2D or 3D point like this:\n```js\n{x: number, y: number, z?: number}\n```\nIf a property is not marked as optional in an object type, then it is required, and Flow will report an error if an appropriate property is not present in the actual value. Normally, however, Flow tolerates extra properties. If you were to pass an object that had a w property to the distance() function above, Flow would not complain.\n\nIf you want Flow to strictly enforce that an object does not have properties other than those explicitly declared in its type, you can declare an exact object type by adding vertical bars to the curly braces:\n```js\n{| x: number, y: number |}\n```\nJavaScript’s objects are sometimes used as dictionaries or string-to-value maps. When used like this, the property names are not known in advance and cannot be declared in a Flow type. If you use objects this way, you can still use Flow to describe the data structure. Suppose that you have an object where the properties are the names of the world’s major cities and the values of those properties are objects that specify the geographical location of those cities. You might declare this data structure like this:\n```js\n// @flow\nconst cityLocations : {[string]: {longitude:number, latitude:number}} = {\n    \"Seattle\": { longitude: 47.6062, latitude: -122.3321 },\n    // TODO: if there are any other important cities, add them here.\n};\nexport default cityLocations;\n```\n### 17.8.5 Type Aliases\nObjects can have many properties, and the Flow type that describes such an object will be long and difficult to type. And even relatively short object types can be confusing because they look so much like object literals. Once we get beyond simple types like number and ?string, it is often useful to be able to define names for our Flow types. And in fact, Flow uses the type keyword to do exactly that. Follow the type keyword with an identifier, an equals sign, and a Flow type. Once you’ve done that, the identifier will be an alias for the type. Here, for example, is how we could rewrite the distance() function from the previous section with an explicitly defined Point type:\n```js\n// @flow\nexport type Point = {\n    x: number,\n    y: number\n};\n\n// Given a Point object return its distance from the origin\nexport default function distance(point: Point): number {\n    return Math.hypot(point.x, point.y);\n}\n```\nNote that this code exports the distance() function and also exports the Point type. Other modules can use import type Point from './distance.js' if they want to use that type definition. Keep in mind, though, that import type is a Flow language extension and not a real JavaScript import directive. Type imports and exports are used by the Flow type checker, but like all other Flow language extensions, they are stripped out of the code before it ever runs.\n\nFinally, it is worth noting that instead of defining a name for a Flow object type that represents a point, it would probably be simpler and cleaner to just define a Point class and use that class as the type.\n\n### 17.8.6 Array Types\nThe Flow type to describe an array is a compound type that also includes the type of the array elements. Here, for example, is a function that expects an array of numbers, and the error that Flow reports if you try to call the function with an array that has non-numeric elements:\n```js\nError ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ average.js:8:16\n\nCannot call average with array literal bound to data because string [1]\nis incompatible with number [2] in array element.\n\n [2]  2│ function average(data: Array<number>) {\n      3│     let sum = 0;\n      4│     for(let x of data) sum += x;\n      5│     return sum/data.length;\n      6│ }\n      7│\n [1]  8│ average([1, 2, \"three\"]);\n```\nThe Flow type for an array is Array followed by the element type in angle brackets. You can also express an array type by following the element type with open and close square brackets. So in this example we could have written number[] instead of `Array<number>`. I prefer the angle bracket notation because, as we’ll see, there are other Flow types that use this angle-bracket syntax.\n\nThe Array type syntax shown works for arrays with an arbitrary number of elements, all of which have the same type. Flow has a different syntax for describing the type of a tuple: an array with a fixed number of elements, each of which may have a different type. To express the type of a tuple, simply write the type of each of its elements, separate them with commas, and enclose them all in square brackets.\n\nA function that returns an HTTP status code and message might look like this, for example:\n```js\nfunction getStatus():[number, string] {\n    return [getStatusCode(), getStatusMessage()];\n}\n```\nFunctions that return tuples are awkward to work with unless you use destructuring assignment:\n```js\nlet [code, message] = getStatus();\n```\nDestructuring assignment, plus Flow’s type-aliasing capabilities, make tuples easy enough to work with that you might consider them as an alternative to classes for simple datatypes:\n```js\n// @flow\nexport type Color = [number, number, number, number];  // [r, g, b, opacity]\n\nfunction gray(level: number): Color {\n    return [level, level, level, 1];\n}\n\nfunction fade([r,g,b,a]: Color, factor: number): Color {\n    return [r, g, b, a/factor];\n}\n\nlet [r, g, b, a] = fade(gray(75), 3);\n```\nNow that we have a way to express the type of an array, let’s return to the size() function from earlier and modify it to expect an array argument instead of a string argument. We want the function to be able to accept an array of any length, so a tuple type is not appropriate. But we don’t want to restrict our function to working only for arrays where all the elements have the same type. The solution is the type `Array<mixed>`:\n```js\n// @flow\nfunction size(s: Array<mixed>): number {\n    return s.length;\n}\nconsole.log(size([1,true,\"three\"]));\n```\nThe element type mixed indicates that the elements of the array can be of any type. If our function actually indexed the array and attempted to use any of those elements, Flow would insist that we use typeof checks or other tests to determine the type of the element before performing any unsafe operation on it. (If you are willing to give up on type checking, you can also use any instead of mixed: it allows you to do whatever you want with the values of the array without ensuring that the values are of the type you expect.)\n\n### 17.8.7 Other Parameterized Types\nWe’ve seen that when you annotate a value as an Array, Flow requires you to also specify the type of the array elements inside angle brackets. This additional type is known as a type parameter, and Array is not the only JavaScript class that is parameterized.\n\nJavaScript’s Set class is a collection of elements, like an array is, and you can’t use Set as a type by itself, but you have to include a type parameter within angle brackets to specify the type of the values contained in the set. (Though you can use mixed or any if the set may contain values of multiple types.) Here’s an example:\n```js\n// @flow\n// Return a set of numbers with members that are exactly twice those\n// of the input set of numbers.\nfunction double(s: Set<number>): Set<number> {\n    let doubled: Set<number> = new Set();\n    for(let n of s) doubled.add(n * 2);\n    return doubled;\n}\nconsole.log(double(new Set([1,2,3])));  // Prints \"Set {2, 4, 6}\"\n```\nMap is another parameterized type. In this case, there are two type parameters that must be specified; the type of the keys and the types of the values:\n```js\n// @flow\nimport type { Color } from \"./Color.js\";\n\nlet colorNames: Map<string, Color> = new Map([\n    [\"red\", [1, 0, 0, 1]],\n    [\"green\", [0, 1, 0, 1]],\n    [\"blue\", [0, 0, 1, 1]]\n]);\n```\nFlow lets you define type parameters for your own classes as well. The following code defines a Result class but parameterizes that class with an Error type and a Value type. We use placeholders E and V in the code to represent these type parameters. When the user of this class declares a variable of type Result, they will specify the actual types to substitute for E and V. The variable declaration might look like this:\n```js\nlet result: Result<TypeError, Set<string>>;\nAnd here is how the parameterized class is defined:\n\n// @flow\n// This class represents the result of an operation that can either\n// throw an error of type E or a value of type V.\nexport class Result<E, V> {\n    error: ?E;\n    value: ?V;\n\n    constructor(error: ?E, value: ?V) {\n        this.error = error;\n        this.value = value;\n    }\n\n    threw(): ?E { return this.error; }\n    returned(): ?V { return this.value; }\n\n    get():V {\n        if (this.error) {\n            throw this.error;\n        } else if (this.value === null || this.value === undefined) {\n            throw new TypeError(\"Error and value must not both be null\");\n        } else {\n            return this.value;\n        }\n    }\n\n}\n```\nAnd you can even define type parameters for functions:\n```js\n// @flow\n// Combine the elements of two arrays into an array of pairs\nfunction zip<A,B>(a:Array<A>, b:Array<B>): Array<[?A,?B]> {\n    let result:Array<[?A,?B]> = [];\n    let len = Math.max(a.length, b.length);\n    for(let i = 0; i < len; i++) {\n        result.push([a[i], b[i]]);\n    }\n    return result;\n}\n\n// Create the array [[1,'a'], [2,'b'], [3,'c'], [4,undefined]]\nlet pairs: Array<[?number,?string]> = zip([1,2,3,4], ['a','b','c'])\n```\n### 17.8.8 Read-Only Types\nFlow defines some special parameterized “utility types” that have names beginning with $. Most of these types have advanced use cases that we are not going to cover here. But two of them are quite useful in practice. If you have an object type T and want to make a read-only version of that type, just write `$ReadOnly<T>`. Similarly, you can write `$ReadOnlyArray<T>` to describe a read-only array with elements of type T.\n\nThe reason to use these types is not because they can offer any guarantee that an object or array can’t be modified (see Object.freeze() in §14.2 if you want true read-only objects) but because it allows you to catch bugs caused by unintentional modifications. If you write a function that takes an object or array argument and does not change any of the object’s properties or the array’s elements, then you can annotate the function parameter with one of Flow’s read-only types. If you do this, then Flow will report an error if you forget and accidentally modify the input value. Here are two examples:\n```js\n// @flow\ntype Point = {x:number, y:number};\n\n// This function takes a Point object but promises not to modify it\nfunction distance(p: $ReadOnly<Point>): number {\n    return Math.hypot(p.x, p.y);\n}\n\nlet p: Point = {x:3, y:4};\ndistance(p)  // => 5\n\n// This function takes an array of numbers that it will not modify\nfunction average(data: $ReadOnlyArray<number>): number {\n    let sum = 0;\n    for(let i = 0; i < data.length; i++) sum += data[i];\n    return sum/data.length;\n}\n\nlet data: Array<number> = [1,2,3,4,5];\naverage(data) // => 3\n```\n### 17.8.9 Function Types\nWe have seen how to add type annotations to specify the types of a function’s parameters and its return type. But when one of the parameters of a function is itself a function, we need to be able to specify the type of that function parameter.\n\nTo express the type of a function with Flow, write the types of each parameter, separate them with commas, enclose them in parentheses, and then follow that with an arrow and type return type of the function.\n\nHere is an example function that expects to be passed a callback function. Notice how we defined a type alias for the type of the callback function:\n```js\n// @flow\n// The type of the callback function used in fetchText() below\nexport type FetchTextCallback = (?Error, ?number, ?string) => void;\n\nexport default function fetchText(url: string, callback: FetchTextCallback) {\n    let status = null;\n    fetch(url)\n        .then(response => {\n            status = response.status;\n            return response.text()\n        })\n        .then(body => {\n            callback(null, status, body);\n        })\n        .catch(error => {\n            callback(error, status, null);\n        });\n}\n```\n### 17.8.10 Union Types\nLet’s return one more time to the size() function. It doesn’t really make sense to have a function that does nothing other than return the length of an array. Arrays have a perfectly good length property for that. But size() might be useful if it could take any kind of collection object (an array or a Set or a Map) and return the number of elements in the collection. In regular untyped JavaScript it would be easy to write a size() function like that. With Flow, we need a way to express a type that allows arrays, Sets, and Maps, but doesn’t allow values of any other type.\n\nFlow calls types like this Union types and allows you to express them by simply listing the desired types and separating them with vertical bar characters:\n```js\n// @flow\nfunction size(collection: Array<mixed>|Set<mixed>|Map<mixed,mixed>): number {\n    if (Array.isArray(collection)) {\n        return collection.length;\n    } else {\n        return collection.size;\n    }\n}\nsize([1,true,\"three\"]) + size(new Set([true,false])) // => 5\n```\nUnion types can be read using the word “or”—“an array or a Set or a Map”—so the fact that this Flow syntax uses the same vertical bar character as JavaScript’s OR operators is intentional.\n\nWe saw earlier that putting a question mark before a type allows null and undefined values. And now you can see that a ? prefix is simply a shortcut for adding a |null|void suffix to a type.\n\nIn general, when you annotate a value with a Union type, Flow will not allow you to use that value until you’ve done enough tests to figure out what the type of the actual value is. In the size() example we just looked at, we need to explicitly check whether the argument is an array before we try to access the length property of the argument. Note that we do not have to distinguish a Set argument from a Map argument, however: both of those classes define a size property, so the code in the else clause is safe as long as the argument is not an array.\n\n### 17.8.11 Enumerated Types and Discriminated Unions\nFlow allows you to use primitive literals as types that consist of that one single value. If you write let x:3;, then Flow will not allow you to assign any value to that variable other than 3. It is not often useful to define types that have only a single member, but a union of literal types can be useful. You can probably imagine a use for types like these, for example:\n```js\ntype Answer = \"yes\" | \"no\";\ntype Digit = 0|1|2|3|4|5|6|7|8|9;\nIf you use types made up of literals, you need to understand that only literal values are allowed:\n\nlet a: Answer = \"Yes\".toLowerCase(); // Error: can't assign string to Answer\nlet d: Digit = 3+4;                  // Error: can't assign number to Digit\n```\nWhen Flow checks your types, it does not actually do the calculations: it just checks the types of the calculations. Flow knows that toLowerCase() returns a string and that the + operator on numbers returns a number. Even though we know that both of these calculations return values that are within the type, Flow cannot know that and flags errors on both of these lines.\n\nA union type of literal types like Answer and Digit is an example of an enumerated type, or enum. A canonical use case for enum types is to represent the suits of playing cards:\n```js\ntype Suit = \"Clubs\" | \"Diamonds\" | \"Hearts\" | \"Spades\";\nA more relevant example might be HTTP status codes:\n\ntype HTTPStatus =\n    | 200    // OK\n    | 304    // Not Modified\n    | 403    // Forbidden\n    | 404;   // Not Found\n```\nOne of the pieces of advice that new programmers often hear is to avoid using literals in their code and to instead define symbolic constants to represent those values. One practical reason for this is to avoid the problem of typos: if you misspell a string literal like “Diamonds” JavaScript may never complain but your code may not work right. If you mistype an identifier, on the other hand, JavaScript is likely to throw an error that you’ll notice. With Flow, this advice does not always apply. If you annotate a variable with the type Suit, and then try to assign a misspelled suit to it, Flow will alert you to the error.\n\nAnother important use for literal types is the creation of discriminated unions. When you work with union types (made up of actually different types, not of literals), you typically have to write code to discriminate among the possible types. In the previous section, we wrote a function that could take an array or a Set or a Map as its argument and had to write code to discriminate array input from Set or Map input. If you want to create a union of Object types, you can make these types easy to discriminate by using a literal type within each of the individual Object types.\n\nAn example will make this clear. Suppose you’re using a worker thread in Node (§16.11) and are using postMessage() and “message” events for sending object-based messages between the main thread and the worker thread. There are multiple types of messages that the worker might want to send to the main thread, but we’d like to write a Flow Union type that describes all possible messages. Consider this code:\n```js\n// @flow\n// The worker sends a message of this type when it is done\n// reticulating the splines we sent it.\nexport type ResultMessage = {\n    messageType: \"result\",\n    result: Array<ReticulatedSpline>, // Assume this type is defined elsewhere.\n};\n\n// The worker sends a message of this type if its code failed with an exception.\nexport type ErrorMessage = {\n    messageType: \"error\",\n    error: Error,\n};\n\n// The worker sends a message of this type to report usage statistics.\nexport type StatisticsMessage = {\n    messageType: \"stats\",\n    splinesReticulated: number,\n    splinesPerSecond: number\n};\n\n// When we receive a message from the worker it will be a WorkerMessage.\nexport type WorkerMessage = ResultMessage | ErrorMessage | StatisticsMessage;\n\n// The main thread will have an event handler function that is passed\n// a WorkerMessage. But because we've carefully defined each of the\n// message types to have a messageType property with a literal type,\n// the event handler can easily discriminate among the possible messages:\nfunction handleMessageFromReticulator(message: WorkerMessage) {\n    if (message.messageType === \"result\") {\n        // Only ResultMessage has a messageType property with this value\n        // so Flow knows that it is safe to use message.result here.\n        // And Flow will complain if you try to use any other property.\n        console.log(message.result);\n    } else if (message.messageType === \"error\") {\n        // Only ErrorMessage has a messageType property with value \"error\"\n        // so knows that it is safe to use message.error here.\n        throw message.error;\n    } else if (message.messageType === \"stats\") {\n        // Only StatisticsMessage has a messageType property with value \"stats\"\n        // so knows that it is safe to use message.splinesPerSecond here.\n        console.info(message.splinesPerSecond);\n    }\n}\n```\n## 17.9 Summary\nJavaScript is the most-used programming language in the world today. It is a living language—one that continues to evolve and improve—surrounded by a flourishing ecosystem of libraries, tools, and extensions. This chapter introduced some of those tools and extensions, but there are many more to learn about. The JavaScript ecosystem flourishes because the JavaScript developer community is active and vibrant, full of peers who share their knowledge through blog posts, videos, and conference presentations. As you close this book and go forth to join this community, you will find no shortage of information sources to keep you engaged with and learning about JavaScript.\n\nBest wishes, David Flanagan, March 2020\n\n---\n\n1. If you have programmed with Java, you may have experienced something like this the first time you wrote a generic API that used a type parameter. I found the learning process for Flow to be remarkably similar to what I went through in 2004 when generics were added to Java."
  },
  {
    "path": "content/posts/ch2.md",
    "content": "---\ntitle: \"第 2 章 词法结构\"\ndate: 2020-11-02T22:18:41+08:00\n---\n\nThe lexical structure of a programming language is the set of elementary rules that specifies how you write programs in that language. It is the lowest-level syntax of a language: it specifies what variable names look like, the delimiter characters for comments, and how one program statement is separated from the next, for example. This short chapter documents the lexical structure of JavaScript. It covers:\n\n> 编程语言的词法结构是一组基本规则，用于指定如何用该语言编写程序。它是一种语言的最低层次语法：例如，它指定变量名的样子，注释的分隔符字符，以及一个程序语句如何与下一个程序语句分隔。这个简短的章节描述了 JavaScript 的词法结构。它涵盖了：\n\n- Case sensitivity, spaces, and line breaks\n- Comments\n- Literals\n- Identifiers and reserved words\n- Unicode\n- Optional semicolons\n\n## 2.1 The Text of a JavaScript Program\n\nJavaScript is a case-sensitive language. This means that language keywords, variables, function names, and other identifiers must always be typed with a consistent capitalization of letters. The while keyword, for example, must be typed “while,” not “While” or “WHILE.” Similarly, online, Online, OnLine, and ONLINE are four distinct variable names.\n\n> JavaScript 是一种区分大小写的语言。这意味着语言关键字、变量、函数名和其他标识符必须始终以一致的大小写输入。例如，while 关键字必须输入“while”，而不是“While”或“WHILE”。同样，online, Online，OnLine 和 ONLINE 是四个不同的变量名。\n\nJavaScript ignores spaces that appear between tokens in programs. For the most part, JavaScript also ignores line breaks (but see §2.6 for an exception). Because you can use spaces and newlines freely in your programs, you can format and indent your programs in a neat and consistent way that makes the code easy to read and understand.\n\n> JavaScript 忽略程序中标记之间出现的空格。在大多数情况下，JavaScript 也会忽略换行符（但是，关于一个例外，请参阅 §2.6）。由于您可以在程序中自由地使用空格和换行符，因此您可以以一种整洁和一致的方式对程序进行格式化和缩进，从而使代码易于阅读和理解。\n\nIn addition to the regular space character (`\\u0020`), JavaScript also recognizes tabs, assorted ASCII control characters, and various Unicode space characters as whitespace. JavaScript recognizes newlines, carriage returns, and a carriage return/line feed sequence as line terminators.\n\n> 除了常规的空格字符（`\\u0020`）之外，JavaScript 还将制表符、各种 ASCII 控制字符和各种 Unicode 空格字符识别为空格。JavaScript 识别换行符、回车符和回车/换行序列作为行结束符。\n\n## 2.2 Comments\n\nJavaScript supports two styles of comments. Any text between a `//` and the end of a line is treated as a comment and is ignored by JavaScript. Any text between the characters `/*` and `*/` is also treated as a comment; these comments may span multiple lines but may not be nested. The following lines of code are all legal JavaScript comments:\n\n> JavaScript 支持两种风格的注释。在 `//` 和行尾之间的任何文本都被 JavaScript 视为注释并被忽略。字符 `/*` 和 `*/` 之间的任何文本也被视为注释；这些注释可能跨越多行，但可能不是嵌套的。下面几行代码都是合法的 JavaScript 注释：\n\n```js\n// This is a single-line comment.\n\n/* This is also a comment */  // and here is another comment.\n\n/*\n * This is a multi-line comment. The extra * characters at the start of\n * each line are not a required part of the syntax; they just look cool!\n */\n```\n\n## 2.3 Literals\n\nA literal is a data value that appears directly in a program. The following are all literals:\n\n> 字面量是直接出现在程序中的数据值。以下都是字面量：\n\n```js\n12               // The number twelve\n1.2              // The number one point two\n\"hello world\"    // A string of text\n'Hi'             // Another string\ntrue             // A Boolean value\nfalse            // The other Boolean value\nnull             // Absence of an object\n```\n\nComplete details on numeric and string literals appear in Chapter 3.\n\n> 数值和字符串字面量的完整细节出现在第 3 章。\n\n## 2.4 Identifiers and Reserved Words\n\nAn identifier is simply a name. In JavaScript, identifiers are used to name constants, variables, properties, functions, and classes and to provide labels for certain loops in JavaScript code. A JavaScript identifier must begin with a letter, an underscore (`_`), or a dollar sign (`$`). Subsequent characters can be letters, digits, underscores, or dollar signs. (Digits are not allowed as the first character so that JavaScript can easily distinguish identifiers from numbers.) These are all legal identifiers:\n\n> 标识符只是一个名称。在 JavaScript 中，标识符用于命名常量、变量、属性、函数和类，并为 JavaScript 代码中的某些循环提供标签。JavaScript 标识符必须以字母、下划线（`_`）或美元符号（`$`）开头。后面的字符可以是字母、数字、下划线或美元符号。（数字不允许作为第一个字符，这样 JavaScript 可以很容易地将标识符与数字区分开来。）这些都是合法的标识符：\n\n```\ni\nmy_variable_name\nv13\n_dummy\n$str\n```\n\nLike any language, JavaScript reserves certain identifiers for use by the language itself. These “reserved words” cannot be used as regular identifiers. They are listed in the next section.\n\n> 与任何语言一样，JavaScript 保留某些标识符供语言本身使用。这些“保留字”不能用作常规标识符。它们将在下一节中列出。\n\n### 2.4.1 Reserved Words\n\nThe following words are part of the JavaScript language. Many of these (such as if, while, and for) are reserved keywords that must not be used as the names of constants, variables, functions, or classes (though they can all be used as the names of properties within an object). Others (such as from, of, get, and set) are used in limited contexts with no syntactic ambiguity and are perfectly legal as identifiers. Still other keywords (such as let) can’t be fully reserved in order to retain backward compatibility with older programs, and so there are complex rules that govern when they can be used as identifiers and when they cannot. (let can be used as a variable name if declared with var outside of a class, for example, but not if declared inside a class or with const.) The simplest course is to avoid using any of these words as identifiers, except for from, set, and target, which are safe to use and are already in common use.\n\n> 下面的单词是 JavaScript 语言的一部分。其中许多（如 if、while 和 for）是保留的关键字，不能用作常量、变量、函数或类的名称（尽管它们都可以用作对象中的属性名称）。其他的（如 from、of、get 和 set）则在有限的上下文中使用，没有语法歧义，作为标识符完全合法。还有一些关键字（比如let）不能被完全保留，以保持与旧程序的向后兼容性，因此有复杂的规则来管理何时可以将它们用作标识符，何时不能。（例如，如果在类外部用 var 声明，let 就可以用作变量名，但如果在类内部或用 const 声明，let 就不能用作变量名。）最简单的方法是避免使用这些词作为标识符，除了 from、set 和 target 之外，这些词使用起来很安全，而且已经很常用了。\n\n```\nas      const      export     get         null     target   void\nasync   continue   extends    if          of       this     while\nawait   debugger   false      import      return   throw    with\nbreak   default    finally    in          set      true     yield\ncase    delete     for        instanceof  static   try\ncatch   do         from       let         super    typeof\nclass   else       function   new         switch   var\n```\n\nJavaScript also reserves or restricts the use of certain keywords that are not currently used by the language but that might be used in future versions:\n\n> JavaScript 还保留或限制使用某些关键字，目前没有使用的语言，但可能会在未来的版本：\n\n```\nenum  implements  interface  package  private  protected  public\n```\n\nFor historical reasons, arguments and eval are not allowed as identifiers in certain circumstances and are best avoided entirely.\n\n> 由于历史原因，参数和 eval 在某些情况下不允许作为标识符，最好完全避免使用。\n\n## 2.5 Unicode\n\nJavaScript programs are written using the Unicode character set, and you can use any Unicode characters in strings and comments. For portability and ease of editing, it is common to use only ASCII letters and digits in identifiers. But this is a programming convention only, and the language allows Unicode letters, digits, and ideographs (but not emojis) in identifiers. This means that programmers can use mathematical symbols and words from non-English languages as constants and variables:\n\n> JavaScript 程序是使用 Unicode 字符集编写的，您可以在字符串和注释中使用任何 Unicode 字符。为了便于移植和编辑，通常在标识符中只使用 ASCII 字母和数字。但这只是一种编程约定，该语言允许在标识符中使用 Unicode 字母、数字和表意文字（但不允许使用表情符号）。这意味着程序员可以使用来自非英语语言的数学符号和单词作为常量和变量：\n\n```js\nconst π = 3.14;\nconst sí = true;\n```\n\n### 2.5.1 Unicode Escape Sequences\n\nSome computer hardware and software cannot display, input, or correctly process the full set of Unicode characters. To support programmers and systems using older technology, JavaScript defines escape sequences that allow us to write Unicode characters using only ASCII characters. These Unicode escapes begin with the characters `\\u` and are either followed by exactly four hexadecimal digits (using uppercase or lowercase letters A–F) or by one to six hexadecimal digits enclosed within curly braces. These Unicode escapes may appear in JavaScript string literals, regular expression literals, and identifiers (but not in language keywords). The Unicode escape for the character “`é,`” for example, is `\\u00E9`; here are three different ways to write a variable name that includes this character:\n\n> 一些计算机硬件和软件不能显示、输入或正确处理完整的 Unicode 字符集。为了支持使用较旧技术的程序员和系统，JavaScript 定义了转义序列，允许我们仅使用 ASCII 字符编写 Unicode 字符。这些 Unicode 转义以字符 `\\u` 开始，后跟四个十六进制数字（使用大写或小写字母A-F），或者用大括号括起来的一到六个十六进制数字。这些 Unicode 转义可能以 JavaScript 字符串、正则表达式和标识符的形式出现（但不会以语言关键字的形式出现）。例如，Unicode 字符 “`é,`” 的转义是`\\u00E9`；这里有三种不同的方式来写一个变量名，包括这个字符：\n\n```js\nlet café = 1; // Define a variable using a Unicode character\ncaf\\u00e9     // => 1; access the variable using an escape sequence\ncaf\\u{E9}     // => 1; another form of the same escape sequence\n```\n\nEarly versions of JavaScript only supported the four-digit escape sequence. The version with curly braces was introduced in ES6 to better support Unicode codepoints that require more than 16 bits, such as emoji:\n\n> 早期版本的 JavaScript 只支持四位数转义序列。带花括号的版本是在 ES6 中引入的，目的是为了更好地支持需要超过16位元的 Unicode 码点，比如表情符号:\n\n```js\nconsole.log(\"\\u{1F600}\");  // Prints a smiley face emoji\n```\n\nUnicode escapes may also appear in comments, but since comments are ignored, they are simply treated as ASCII characters in that context and not interpreted as Unicode.\n\n> Unicode 转义也可能出现在注释中，但是由于注释被忽略，因此它们在该上下文中只是作为 ASCII 字符处理，而不是解释为 Unicode。\n\n### 2.5.2 Unicode Normalization\n\nIf you use non-ASCII characters in your JavaScript programs, you must be aware that Unicode allows more than one way of encoding the same character. The string “`é,`” for example, can be encoded as the single Unicode character `\\u00E9` or as a regular ASCII “`e`” followed by the acute accent combining mark `\\u0301`. These two encodings typically look exactly the same when displayed by a text editor, but they have different binary encodings, meaning that they are considered different by JavaScript, which can lead to very confusing programs:\n\n> 如果在 JavaScript 程序中使用非 ASCII 字符，则必须知道 Unicode 允许对同一字符进行多种编码方式。例如，字符串 “`é,`” 可以编码为单个Unicode字符 `\\u00E9`，或编码为后面跟着急性重音组合标记 `\\u0301` 的正则ASCII “`e`”。这两种编码在文本编辑器中显示时看起来是完全一样的，但是它们有不同的二进制编码，这意味着它们被 JavaScript  认为是不同的，这可能会导致非常混乱的程序:\n\n```js\nconst café = 1;  // This constant is named \"caf\\u{e9}\"\nconst café = 2;  // This constant is different: \"cafe\\u{301}\"\ncafé  // => 1: this constant has one value\ncafé  // => 2: this indistinguishable constant has a different value\n```\n\nThe Unicode standard defines the preferred encoding for all characters and specifies a normalization procedure to convert text to a canonical form suitable for comparisons. JavaScript assumes that the source code it is interpreting has already been normalized and does not do any normalization on its own. If you plan to use Unicode characters in your JavaScript programs, you should ensure that your editor or some other tool performs Unicode normalization of your source code to prevent you from ending up with different but visually indistinguishable identifiers.\n\n> Unicode 标准定义了所有字符的首选编码，并指定了将文本转换为适合于比较的规范形式的标准化过程。JavaScript 假设它要解释的源代码已经被规范化了，它自己不做任何规范化。如果您计划在 JavaScript 程序中使用 Unicode 字符，那么应该确保您的编辑器或其他工具对源代码执行 Unicode 规范化，以防止出现不同但视觉上无法区分的标识符。\n\n## 2.6 Optional Semicolons\n\nLike many programming languages, JavaScript uses the semicolon (;) to separate statements (see Chapter 5) from one another. This is important for making the meaning of your code clear: without a separator, the end of one statement might appear to be the beginning of the next, or vice versa. In JavaScript, you can usually omit the semicolon between two statements if those statements are written on separate lines. (You can also omit a semicolon at the end of a program or if the next token in the program is a closing curly brace: }.) Many JavaScript programmers (and the code in this book) use semicolons to explicitly mark the ends of statements, even where they are not required. Another style is to omit semicolons whenever possible, using them only in the few situations that require them. Whichever style you choose, there are a few details you should understand about optional semicolons in JavaScript.\n\n> 像许多编程语言一样，JavaScript 使用分号（;）来分隔语句（参见第 5 章）。这对于明确代码的含义非常重要：如果没有分隔符，一条语句的结尾可能会是下一条语句的开始，反之亦然。在 JavaScript 中，如果两个语句写在不同的行上，通常可以省略这两个语句之间的分号。（如果程序的下一个标记是右花括号 }，也可以省略在程序末尾的分号。）许多 JavaScript 程序员（以及本书中的代码）使用分号来显式地标记语句的结束，即使在不需要分号的地方也是如此。另一种风格是尽可能省略分号，只在少数需要分号的情况下使用。无论您选择哪种样式，关于 JavaScript 中的可选分号，您都应该了解一些细节。\n\nConsider the following code. Since the two statements appear on separate lines, the first semicolon could be omitted:\n\n> 考虑下面的代码。由于这两个语句出现在不同的行中，第一个分号可以省略：\n\n```js\na = 3;\nb = 4;\n```\n\nWritten as follows, however, the first semicolon is required:\n\n> 但是，如写如下，第一个分号是必需的:\n\n```js\na = 3; b = 4;\n```\n\nNote that JavaScript does not treat every line break as a semicolon: it usually treats line breaks as semicolons only if it can’t parse the code without adding an implicit semicolon. More formally (and with three exceptions described a bit later), JavaScript treats a line break as a semicolon if the next nonspace character cannot be interpreted as a continuation of the current statement. Consider the following code:\n\n> 请注意，JavaScript 并不把每个断行符都当作分号来处理：它通常只在不添加隐式分号而无法解析代码时才把断行符当作分号来处理。更正式的说法是，如果下一个非空格字符不能解释为当前语句的延续，JavaScript将换行符视为分号。考虑以下代码:\n\n```js\nlet a\na\n=\n3\nconsole.log(a)\n```\n\nJavaScript interprets this code like this:\n\n> JavaScript 这样解释这段代码：\n\n```js\nlet a; a = 3; console.log(a);\n```\n\nJavaScript does treat the first line break as a semicolon because it cannot parse the code let a a without a semicolon. The second a could stand alone as the statement a;, but JavaScript does not treat the second line break as a semicolon because it can continue parsing the longer statement a = 3;.\n\n> JavaScript 确实把第一个换行符当作分号，因为它不能解析没有分号的代码。第二个 a 可以单独作为语句 a;，但 JavaScript 不会将第二个换行符作为分号处理，因为它可以继续解析较长的语句 a = 3;。\n\nThese statement termination rules lead to some surprising cases. This code looks like two separate statements separated with a newline:\n\n> 这些语句终止规则会导致一些奇怪的情况。这段代码看起来像用换行符分隔的两个单独的语句：\n\n```js\nlet y = x + f\n(a+b).toString()\n```\n\nBut the parentheses on the second line of code can be interpreted as a function invocation of f from the first line, and JavaScript interprets the code like this:\n\n> 但是第二行括号可以解释为第一行 f 的函数调用，JavaScript 是这样解释的：\n\n```js\nlet y = x + f(a+b).toString();\n```\n\nMore likely than not, this is not the interpretation intended by the author of the code. In order to work as two separate statements, an explicit semicolon is required in this case.\n\n> 很可能，这不是代码作者想要的解释。为了作为两个独立的语句工作，在这种情况下需要显式的分号。\n\nIn general, if a statement begins with (, [, /, +, or -, there is a chance that it could be interpreted as a continuation of the statement before. Statements beginning with /, +, and - are quite rare in practice, but statements beginning with ( and [ are not uncommon at all, at least in some styles of JavaScript programming. Some programmers like to put a defensive semicolon at the beginning of any such statement so that it will continue to work correctly even if the statement before it is modified and a previously terminating semicolon removed:\n\n> 通常，如果一个语句以 (、[、/、+ 或 - 开头，那么它有可能被解释为之前语句的延续。在实践中，以 /、+ 和 - 开头的语句非常少见，但是以 ( 和 [ 开头的语句并不少见，至少在某些 JavaScript 编程风格中是这样的。一些程序员喜欢在任何这样的语句的开头放一个防御分号，这样即使在修改之前的语句和删除之前的终止分号时，它也能继续正确工作:\n\n```js\nlet x = 0                         // Semicolon omitted here\n;[x,x+1,x+2].forEach(console.log) // Defensive ; keeps this statement separate\n```\n\nThere are three exceptions to the general rule that JavaScript interprets line breaks as semicolons when it cannot parse the second line as a continuation of the statement on the first line. The first exception involves the return, throw, yield, break, and continue statements (see Chapter 5). These statements often stand alone, but they are sometimes followed by an identifier or expression. If a line break appears after any of these words (before any other tokens), JavaScript will always interpret that line break as a semicolon. For example, if you write:\n\n> 当 JavaScript 不能将第二行解析为第一行语句的延续时，它会将换行符解释为分号。第一个异常涉及到 return、throw、yield、break 和 continue 语句（参见第 5 章），这些语句通常是独立的，但有时会后跟一个标识符或表达式。如果一个换行符出现在这些单词之后（在任何其他标记之前），JavaScript 总是将该换行符解释为分号。例如，如果你写：\n\n```js\nreturn\ntrue;\n```\n\nJavaScript assumes you meant:\n\n> JavaScript 假设您的意思是：\n\n```js\nreturn; true;\n```\n\nHowever, you probably meant:\n\n> 不过，你的意思可能是：\n\n```js\nreturn true;\n```\n\nThis means that you must not insert a line break between return, break, or continue and the expression that follows the keyword. If you do insert a line break, your code is likely to fail in a nonobvious way that is difficult to debug.\n\n> 这意味着不能在 return、break 或 continue 和关键字后面的表达式之间插入换行符。如果您确实插入了一个换行符，那么您的代码很可能会以一种难以调试的不明显的方式失败。\n\nThe second exception involves the ++ and −− operators (§4.8). These operators can be prefix operators that appear before an expression or postfix operators that appear after an expression. If you want to use either of these operators as postfix operators, they must appear on the same line as the expression they apply to. The third exception involves functions defined using concise “arrow” syntax: the => arrow itself must appear on the same line as the parameter list.\n\n> 第二个例外涉及到 ++ 和 -- 运算符（§4.8）。这些运算符可以是出现在表达式之前的前缀运算符，也可以是出现在表达式之后的后缀运算符。如果要使用这些运算符中的任何一个作为后缀运算符，则它们必须与应用它们的表达式出现在同一行。第三个异常涉及使用简洁的“箭头”语法定义的函数：=> 箭头本身必须与参数列表出现在同一行。\n\n## 2.7 Summary\n\nThis chapter has shown how JavaScript programs are written at the lowest level. The next chapter takes us one step higher and introduces the primitive types and values (numbers, strings, and so on) that serve as the basic units of computation for JavaScript programs.\n\n> 本章展示了 JavaScript 程序是如何在底层编写的。下一章将进一步介绍作为 JavaScript 程序基本计算单元的基本类型和值（数字、字符串等）。"
  },
  {
    "path": "content/posts/ch3.md",
    "content": "---\ntitle: \"第 3 章 类型、值和变量\"\ndate: 2020-11-02T22:18:40+08:00\n---\n\nComputer programs work by manipulating values, such as the number 3.14 or the text “Hello World.” The kinds of values that can be represented and manipulated in a programming language are known as types, and one of the most fundamental characteristics of a programming language is the set of types it supports. When a program needs to retain a value for future use, it assigns the value to (or “stores” the value in) a variable. Variables have names, and they allow use of those names in our programs to refer to values. The way that variables work is another fundamental characteristic of any programming language. This chapter explains types, values, and variables in JavaScript. It begins with an overview and some definitions.\n\n> 计算机程序通过处理数值来工作，比如数字 3.14 或文本“Hello World”。在编程语言中可以表示和操作的值的种类称为类型，而编程语言最基本的特征之一就是它所支持的类型集。当程序需要保留一个值以供将来使用时，它将该值赋给（或将该值“存储”在）一个变量。变量有名称，它们允许在程序中使用这些名称来引用值。变量工作的方式是任何编程语言的另一个基本特征。本章解释了 JavaScript 中的类型、值和变量。它以概述和一些定义开始。\n\n## 3.1 Overview and Definitions\n\nJavaScript types can be divided into two categories: primitive types and object types. JavaScript’s primitive types include numbers, strings of text (known as strings), and Boolean truth values (known as booleans). A significant portion of this chapter is dedicated to a detailed explanation of the numeric (§3.2) and string (§3.3) types in JavaScript. Booleans are covered in §3.4.\n\n> JavaScript 类型可以分为两类：基本类型和对象类型。JavaScript 的基本类型包括数字、文本字符串（称为字符串）和布尔真实性值（称为布尔值）。这一章的重要部分是专门详细解释 JavaScript 中的数字（§3.2）和字符串（§3.3）类型。在 §3.4 中介绍了布尔值。\n\nThe special JavaScript values null and undefined are primitive values, but they are not numbers, strings, or booleans. Each value is typically considered to be the sole member of its own special type. §3.5 has more about null and undefined. ES6 adds a new special-purpose type, known as Symbol, that enables the definition of language extensions without harming backward compatibility. Symbols are covered briefly in §3.6.\n\n> 特殊的 JavaScript 值 null 和 undefined 是基本值，但它们不是数字、字符串或布尔值。每个值通常被认为是它自己特殊类型的唯一成员。§3.5 有更多关于 null 和 undefined 的内容。ES6 添加了一种新的特殊用途类型，称为 Symbol，它支持在不损害向后兼容性的情况下定义语言扩展。符号在§3.6中有简要介绍。\n\nAny JavaScript value that is not a number, a string, a boolean, a symbol, null, or undefined is an object. An object (that is, a member of the type object) is a collection of properties where each property has a name and a value (either a primitive value or another object). One very special object, the global object, is covered in §3.7, but more general and more detailed coverage of objects is in Chapter 6.\n\n> 任何不是数字、字符串、布尔值、Symbol、null 或 undefined 的 JavaScript 值都是对象。对象（即类型对象的成员）是属性的集合，其中每个属性都有一个名称和一个值（原始值或另一个对象）。一个非常特殊的对象，全局对象，在 §3.7 中介绍过了，但是在第 6 章中对对象进行了更广泛、更详细的介绍。\n\nAn ordinary JavaScript object is an unordered collection of named values. The language also defines a special kind of object, known as an array, that represents an ordered collection of numbered values. The JavaScript language includes special syntax for working with arrays, and arrays have some special behavior that distinguishes them from ordinary objects. Arrays are the subject of Chapter 7.\n\n> 普通的 JavaScript 对象是命名值的无序集合。该语言还定义了一种特殊类型的对象，称为数组，它表示编号值的有序集合。JavaScript 语言包含处理数组的特殊语法，数组有一些特殊的行为将它们与普通对象区分开来。数组是第 7 章的主题。\n\nIn addition to basic objects and arrays, JavaScript defines a number of other useful object types. A Set object represents a set of values. A Map object represents a mapping from keys to values. Various “typed array” types facilitate operations on arrays of bytes and other binary data. The RegExp type represents textual patterns and enables sophisticated matching, searching, and replacing operations on strings. The Date type represents dates and times and supports rudimentary date arithmetic. Error and its subtypes represent errors that can arise when executing JavaScript code. All of these types are covered in Chapter 11.\n\n> 除了基本对象和数组之外，JavaScript 还定义了许多其他有用的对象类型。Set 对象表示一组值。Map 对象表示从键到值的映射。各种“类型化数组”类型促进了对字节数组和其他二进制数据的操作。RegExp 类型表示文本模式，支持对字符串进行复杂的匹配、搜索和替换操作。Date 类型表示日期和时间，并支持基本的日期运算。Error 及其子类型表示在执行 JavaScript 代码时可能出现的错误。所有这些类型都在第 11 章中介绍。\n\nJavaScript differs from more static languages in that functions and classes are not just part of the language syntax: they are themselves values that can be manipulated by JavaScript programs. Like any JavaScript value that is not a primitive value, functions and classes are a specialized kind of object. They are covered in detail in Chapters 8 and 9.\n\n> JavaScript 与更静态的语言的不同之处在于，函数和类不仅仅是语言语法的一部分：它们本身是可由 JavaScript 程序操作的值。像任何不是基本值的 JavaScript 值一样，函数和类是一种特殊的对象。它们将在第 8 章和第 9 章中详细介绍。\n\nThe JavaScript interpreter performs automatic garbage collection for memory management. This means that a JavaScript programmer generally does not need to worry about destruction or deallocation of objects or other values. When a value is no longer reachable—when a program no longer has any way to refer to it—the interpreter knows it can never be used again and automatically reclaims the memory it was occupying. (JavaScript programmers do sometimes need to take care to ensure that values do not inadvertently remain reachable—and therefore nonreclaimable—longer than necessary.)\n\n> JavaScript 解释器为内存管理执行自动垃圾收集。这意味着 JavaScript 程序员通常不需要担心对象或其他值的销毁或释放。当一个值不再可获得时——当程序不再有方法引用它时——解释器知道它再也不能被使用，并自动收回它所占用的内存。（JavaScript 程序员有时确实需要注意确保值不会意外地超时保持可用——导致不可收回。）\n\nJavaScript supports an object-oriented programming style. Loosely, this means that rather than having globally defined functions to operate on values of various types, the types themselves define methods for working with values. To sort the elements of an array a, for example, we don’t pass a to a sort() function. Instead, we invoke the sort() method of a:\n\n> JavaScript 支持面向对象的编程风格。不严格地说，这意味着不是用全局定义的函数来操作各种类型的值，而是由类型本身定义处理值的方法。例如，要对数组 a 的元素进行排序，我们不需要将 a 传递给 sort() 函数。相反，我们调用 sort() 方法：\n\n```js\na.sort();       // The object-oriented version of sort(a).\n```\n\nMethod definition is covered in Chapter 9. Technically, it is only JavaScript objects that have methods. But numbers, strings, boolean, and symbol values behave as if they have methods. In JavaScript, null and undefined are the only values that methods cannot be invoked on.\n\n> 方法定义将在第 9 章中介绍。从技术上讲，只有 JavaScript 对象才有方法。但是数字、字符串、布尔值和符号值的行为就好像它们有方法一样。在 JavaScript 中，只有 null 和 undefined 值不能调用方法。\n\nJavaScript’s object types are mutable and its primitive types are immutable. A value of a mutable type can change: a JavaScript program can change the values of object properties and array elements. Numbers, booleans, symbols, null, and undefined are immutable—it doesn’t even make sense to talk about changing the value of a number, for example. Strings can be thought of as arrays of characters, and you might expect them to be mutable. In JavaScript, however, strings are immutable: you can access the text at any index of a string, but JavaScript provides no way to alter the text of an existing string. The differences between mutable and immutable values are explored further in §3.8.\n\n> JavaScript 的对象类型是可变的，它的基本类型是不可变的。可变类型的值可以更改：JavaScript 程序可以更改对象属性和数组元素的值。数字、布尔值、符号、null 和 undefined 都是不可更改的——例如，讨论更改某个数字的值甚至都没有意义。字符串可以看作是字符数组，您可能希望它们是可变的。然而，在 JavaScript 中，字符串是不可变的：您可以在字符串的任何索引处访问文本，但是 JavaScript 没有提供改变现有字符串文本的方法。可变值和不可变值之间的区别在 §3.8 中有进一步的探讨。\n\nJavaScript liberally converts values from one type to another. If a program expects a string, for example, and you give it a number, it will automatically convert the number to a string for you. And if you use a non-boolean value where a boolean is expected, JavaScript will convert accordingly. The rules for value conversion are explained in §3.9. JavaScript’s liberal value conversion rules affect its definition of equality, and the == equality operator performs type conversions as described in §3.9.1. (In practice, however, the == equality operator is deprecated in favor of the strict equality operator ===, which does no type conversions. See §4.9.1 for more about both operators.)\n\n> JavaScript 自由地将值从一种类型转换为另一种类型。例如，如果程序需要一个字符串，而您给了它一个数字，它会自动将数字转换为字符串。如果在需要布尔值的地方使用了非布尔值，JavaScript 将相应地进行转换。值转换的规则在 §3.9 中解释。JavaScript 的自由值转换规则会影响相等的定义，`==`相等运算符会执行 §3.9.1 中描述的类型转换。（然而，在实践中，== 相等运算符已弃用，而使用严格的相等运算符`===`，它不进行类型转换。关于这两个运算符的更多信息请参见 §4.9.1。）\n\nConstants and variables allow you to use names to refer to values in your programs. Constants are declared with const and variables are declared with let (or with var in older JavaScript code). JavaScript constants and variables are untyped: declarations do not specify what kind of values will be assigned. Variable declaration and assignment are covered in §3.10.\n\n> 常量和变量允许您在程序中使用名称来引用值。常量是用 const 声明的，变量是用 let 声明的（在旧的JavaScript代码中是用var声明的）。JavaScript 常量和变量都是无类型的：声明不指定要赋给哪种类型的值。变量声明和赋值见 §3.10。\n\nAs you can see from this long introduction, this is a wide-ranging chapter that explains many fundamental details about how data is represented and manipulated in JavaScript. We’ll begin by diving right in to the details of JavaScript numbers and text.\n\n> 从这篇冗长的介绍中可以看出，这一章内容广泛，解释了关于如何在 JavaScript 中表示和操作数据的许多基本细节。首先，我们将深入了解 JavaScript 数字和文本的细节。\n\n## 3.2 Number\nJavaScript’s primary numeric type, Number, is used to represent integers and to approximate real numbers. JavaScript represents numbers using the 64-bit floating-point format defined by the IEEE 754 standard,1 which means it can represent numbers as large as ±1.7976931348623157 × 10308 and as small as ±5 × 10−324.\n\n> JavaScript 的主要数字类型 Number 用于表示整数和近似实数。JavaScript 使用 IEEE 754 标准定义的64位浮点格式表示数字1，这意味着它可以表示大到±1.7976931348623157×10308，小到±5×10−324的数字。\n\nThe JavaScript number format allows you to exactly represent all integers between −9,007,199,254,740,992 (−253) and 9,007,199,254,740,992 (253), inclusive. If you use integer values larger than this, you may lose precision in the trailing digits. Note, however, that certain operations in JavaScript (such as array indexing and the bitwise operators described in Chapter 4) are performed with 32-bit integers. If you need to exactly represent larger integers, see §3.2.5.\n\n> JavaScript 数字格式允许你精确地表示 9,007,199,254,740,992(−253) 和 9,007,199,254,740,992(253) 之间的所有整数。如果使用大于这个值的整数值，则可能会丢失末尾数字的精度。然而，请注意，JavaScript 中的某些操作（如数组索引和第 4 章中描述的位运算符）是用 32 位整数执行的。如果你需要精确地表示更大的整数，请参阅 §3.2.5。\n\nWhen a number appears directly in a JavaScript program, it’s called a numeric literal. JavaScript supports numeric literals in several formats, as described in the following sections. Note that any numeric literal can be preceded by a minus sign (-) to make the number negative.\n\n> 当一个数字直接出现在 JavaScript 程序中时，它被称为数字文字。JavaScript 支持几种格式的数字文字，如下面的章节所述。注意，任何数字文字前面都可以加一个减号(-)，以使数字为负。\n\n### 3.2.1 Integer Literals\nIn a JavaScript program, a base-10 integer is written as a sequence of digits. For example:\n\n> 在 JavaScript 程序中，以 10 为基数的整数被写成数字序列。例如：\n\n```js\n0\n3\n10000000\n```\n\nIn addition to base-10 integer literals, JavaScript recognizes hexadecimal (base-16) values. A hexadecimal literal begins with 0x or 0X, followed by a string of hexadecimal digits. A hexadecimal digit is one of the digits 0 through 9 or the letters a (or A) through f (or F), which represent values 10 through 15. Here are examples of hexadecimal integer literals:\n\n> 除了以 10 为基数的整数字面量之外，JavaScript 还可以识别十六进制（以 16 为基数）的值。以 0x 或 0x 开头的十六进制文字，后面跟着一串十六进制数字。16进制数字是数字 0 到 9 或字母 a（或A）到 f（或F）中的一个，表示值 10 到 15。以下是十六进制整数字面量的例子:\n\n```js\n0xff       // => 255: (15*16 + 15)\n0xBADCAFE  // => 195939070\n```\n\nIn ES6 and later, you can also express integers in binary (base 2) or octal (base 8) using the prefixes 0b and 0o (or 0B and 0O) instead of 0x:\n\n> 在 ES6 及后续版本中，还可以使用前缀 0b 和 0o（或0B和0O）来表示二进制（以 2 为基数）或八进制（以 8 为基数）的整数，而不是 0x：\n\n```js\n0b10101  // => 21:  (1*16 + 0*8 + 1*4 + 0*2 + 1*1)\n0o377    // => 255: (3*64 + 7*8 + 7*1)\n```\n### 3.2.2 Floating-Point Literals\nFloating-point literals can have a decimal point; they use the traditional syntax for real numbers. A real value is represented as the integral part of the number, followed by a decimal point and the fractional part of the number.\n\n> 浮点字面值可以有小数点；他们使用传统的实数语法。实数表示为数字的整数部分，然后是小数点和小数部分。\n\nFloating-point literals may also be represented using exponential notation: a real number followed by the letter e (or E), followed by an optional plus or minus sign, followed by an integer exponent. This notation represents the real number multiplied by 10 to the power of the exponent.\n\n> 浮点字量也可以用指数表示法表示：一个实数后面跟着字母 e（或 E），后面跟着一个可选的加号或减号，然后是一个整数指数。这种记数方法表示的数值，是由前面的实数乘以10的指数次幂。\n\nMore succinctly, the syntax is:\n\n> 更简洁地说，语法是：\n\n```js\n[digits][.digits][(E|e)[(+|-)]digits]\n```\nFor example:\n\n> 例如:\n\n```js\n3.14\n2345.6789\n.333333333333333333\n6.02e23        // 6.02 × 10²³\n1.4738223E-32  // 1.4738223 × 10⁻³²\n```\nSEPARATORS IN NUMERIC LITERALS\n\nYou can use underscores within numeric literals to break long literals up into chunks that are easier to read:\n\n> 你可以在数字字面值中使用下划线将长字面值分解成更容易阅读的块：\n```js\nlet billion = 1_000_000_000;   // Underscore as a thousands separator.\nlet bytes = 0x89_AB_CD_EF;     // As a bytes separator.\nlet bits = 0b0001_1101_0111;   // As a nibble separator.\nlet fraction = 0.123_456_789;  // Works in the fractional part, too.\n```\n\nAt the time of this writing in early 2020, underscores in numeric literals are not yet formally standardized as part of JavaScript. But they are in the advanced stages of the standardization process and are implemented by all major browsers and by Node.\n\n> 在 2020 年初撰写本文时，数字字面值中的下划线还没有作为 JavaScript 的一部分正式标准化。但是它们处于标准化过程的高级阶段，所有主流浏览器和 Node 都实现了它们。\n\n### 3.2.3 Arithmetic in JavaScript\n\nJavaScript programs work with numbers using the arithmetic operators . that the language provides. These include + for addition, - for subtraction, * for multiplication, / for division, and % for modulo (remainder after division). ES2016 adds ** for exponentiation. Full details on these and other operators can be found in Chapter 4.\n\n> JavaScript 程序使用算术运算符处理数字。这是语言提供的。这些参数包括 + 表示加法，- 表示减法，* 表示乘法，/ 表示除法，% 表示取模（除后的余数）。ES2016 为取幂添加了 **。关于这些运算符和其他运算符的详细信息可以在第 4 章中找到。\n\nIn addition to these basic arithmetic operators, JavaScript supports more complex mathematical operations through a set of functions and constants defined as properties of the Math object:\n\n> 除了这些基本的算术运算符，JavaScript 还支持更加复杂的算术运算，这些复杂运算通过作为 Math 对象的属性定义的函数和常量来实现：\n\n```js\nMath.pow(2,53)           // => 9007199254740992: 2 to the power 53\nMath.round(.6)           // => 1.0: round to the nearest integer\nMath.ceil(.6)            // => 1.0: round up to an integer\nMath.floor(.6)           // => 0.0: round down to an integer\nMath.abs(-5)             // => 5: absolute value\nMath.max(x,y,z)          // Return the largest argument\nMath.min(x,y,z)          // Return the smallest argument\nMath.random()            // Pseudo-random number x where 0 <= x < 1.0\nMath.PI                  // π: circumference of a circle / diameter\nMath.E                   // e: The base of the natural logarithm\nMath.sqrt(3)             // => 3**0.5: the square root of 3\nMath.pow(3, 1/3)         // => 3**(1/3): the cube root of 3\nMath.sin(0)              // Trigonometry: also Math.cos, Math.atan, etc.\nMath.log(10)             // Natural logarithm of 10\nMath.log(100)/Math.LN10  // Base 10 logarithm of 100\nMath.log(512)/Math.LN2   // Base 2 logarithm of 512\nMath.exp(3)              // Math.E cubed\n```\n\nES6 defines more functions on the Math object:\n\n> ES6 在 Math 对象上定义了更多的函数:\n\n```js\nMath.cbrt(27)    // => 3: cube root\nMath.hypot(3, 4) // => 5: square root of sum of squares of all arguments\nMath.log10(100)  // => 2: Base-10 logarithm\nMath.log2(1024)  // => 10: Base-2 logarithm\nMath.log1p(x)    // Natural log of (1+x); accurate for very small x\nMath.expm1(x)    // Math.exp(x)-1; the inverse of Math.log1p()\nMath.sign(x)     // -1, 0, or 1 for arguments <, ==, or > 0\nMath.imul(2,3)   // => 6: optimized multiplication of 32-bit integers\nMath.clz32(0xf)  // => 28: number of leading zero bits in a 32-bit integer\nMath.trunc(3.9)  // => 3: convert to an integer by truncating fractional part\nMath.fround(x)   // Round to nearest 32-bit float number\nMath.sinh(x)     // Hyperbolic sine. Also Math.cosh(), Math.tanh()\nMath.asinh(x)    // Hyperbolic arcsine. Also Math.acosh(), Math.atanh()\n```\n\nArithmetic in JavaScript does not raise errors in cases of overflow, underflow, or division by zero. When the result of a numeric operation is larger than the largest representable number (overflow), the result is a special infinity value, Infinity. Similarly, when the absolute value of a negative value becomes larger than the absolute value of the largest representable negative number, the result is negative infinity, -Infinity. The infinite values behave as you would expect: adding, subtracting, multiplying, or dividing them by anything results in an infinite value (possibly with the sign reversed).\n\n> JavaScript 中的算术在溢出、下溢或除以 0 的情况下不会引发错误。当数值操作的结果大于最大可表示数（溢出）时，结果是一个特殊的无穷值，即 Infinity。同样，当一个负数的绝对值大于最大可表示负数的绝对值时，结果是负无穷，-Infinity。无穷值的行为与您预期的一样：加、减、乘或除任何值都会产生无穷值（可能符号颠倒）。\n\nUnderflow occurs when the result of a numeric operation is closer to zero than the smallest representable number. In this case, JavaScript returns 0. If underflow occurs from a negative number, JavaScript returns a special value known as “negative zero.” This value is almost completely indistinguishable from regular zero and JavaScript programmers rarely need to detect it.\n\n> 当数值操作的结果比最小的可表示数字更接近于零时，就会发生下溢。在这种情况下，JavaScript 返回 0。如果下溢发生在负数，JavaScript 将返回一个特殊的值，称为“负零”。这个值与普通的 0 几乎没有区别，JavaScript 程序员几乎不需要检测它。\n\nDivision by zero is not an error in JavaScript: it simply returns infinity or negative infinity. There is one exception, however: zero divided by zero does not have a well-defined value, and the result of this operation is the special not-a-number value, NaN. NaN also arises if you attempt to divide infinity by infinity, take the square root of a negative number, or use arithmetic operators with non-numeric operands that cannot be converted to numbers.\n\n> 在 JavaScript 中除以 0 不是错误：它只是返回无穷大或负无穷大。然而，有一个例外：0 除以 0 没有一个定义良好的值，这个操作的结果是一个特殊的非数字值 NaN。如果您试图将无穷除以无穷，取负数的平方根，或使用带有不能转换为数字的非数字操作数的算术运算符，则还会出现 NaN。\n\nJavaScript predefines global constants Infinity and NaN to hold the positive infinity and not-a-number value, and these values are also available as properties of the Number object:\n\n> JavaScript 预先定义了全局常量 Infinity 和 NaN 来保存正的无穷大和非数值，这些值也可以作为 Number 对象的属性：\n\n```js\nInfinity                    // A positive number too big to represent\nNumber.POSITIVE_INFINITY    // Same value\n1/0                         // => Infinity\nNumber.MAX_VALUE * 2        // => Infinity; overflow\n\n-Infinity                   // A negative number too big to represent\nNumber.NEGATIVE_INFINITY    // The same value\n-1/0                        // => -Infinity\n-Number.MAX_VALUE * 2       // => -Infinity\n\nNaN                         // The not-a-number value\nNumber.NaN                  // The same value, written another way\n0/0                         // => NaN\nInfinity/Infinity           // => NaN\n\nNumber.MIN_VALUE/2          // => 0: underflow\n-Number.MIN_VALUE/2         // => -0: negative zero\n-1/Infinity                 // -> -0: also negative 0\n-0\n\n// The following Number properties are defined in ES6\nNumber.parseInt()       // Same as the global parseInt() function\nNumber.parseFloat()     // Same as the global parseFloat() function\nNumber.isNaN(x)         // Is x the NaN value?\nNumber.isFinite(x)      // Is x a number and finite?\nNumber.isInteger(x)     // Is x an integer?\nNumber.isSafeInteger(x) // Is x an integer -(2**53) < x < 2**53?\nNumber.MIN_SAFE_INTEGER // => -(2**53 - 1)\nNumber.MAX_SAFE_INTEGER // => 2**53 - 1\nNumber.EPSILON          // => 2**-52: smallest difference between numbers\n```\n\nThe not-a-number value has one unusual feature in JavaScript: it does not compare equal to any other value, including itself. This means that you can’t write x === NaN to determine whether the value of a variable x is NaN. Instead, you must write x != x or Number.isNaN(x). Those expressions will be true if, and only if, x has the same value as the global constant NaN.\n\n> 非数值在 JavaScript 中有一个不同寻常的特性：它不等于任何其他值，包括它自己。这意味着您不能编写`x === NaN`来确定变量 x 的值是否为 NaN。相反，你必须写`x != x`或`Number.isNaN(x)`。当且仅当 x 具有与全局常量 NaN 相同的值时，这些表达式将为真。\n\nThe global function isNaN() is similar to Number.isNaN(). It returns true if its argument is NaN, or if that argument is a non-numeric value that cannot be converted to a number. The related function Number.isFinite() returns true if its argument is a number other than NaN, Infinity, or -Infinity. The global isFinite() function returns true if its argument is, or can be converted to, a finite number.\n\n> 全局函数 isNaN() 类似于 Number.isNaN()。如果它的实参是 NaN，或者该实参是一个不能转换为数字的非数字值，则返回 true。相关函数 Number.isFinite() 如果实参不是 NaN、Infinity 或 -Infinity，则返回 true。全局的 isFinite() 函数如果它的实参是一个有限数，或者可以转换为一个有限数，则返回 true。\n\nThe negative zero value is also somewhat unusual. It compares equal (even using JavaScript’s strict equality test) to positive zero, which means that the two values are almost indistinguishable, except when used as a divisor:\n\n> 负零值也有些不寻常。它和正零值是相等的（甚至使用 JavaScript 的严格相等测试来判断）。这意味着这两个值几乎一模一样，除了作为除数之外：\n\n```js\nlet zero = 0;         // Regular zero\nlet negz = -0;        // Negative zero\nzero === negz         // => true: zero and negative zero are equal\n1/zero === 1/negz     // => false: Infinity and -Infinity are not equal\n```\n### 3.2.4 Binary Floating-Point and Rounding Errors\n\nThere are infinitely many real numbers, but only a finite number of them (18,437,736,874,454,810,627, to be exact) can be represented exactly by the JavaScript floating-point format. This means that when you’re working with real numbers in JavaScript, the representation of the number will often be an approximation of the actual number.\n\n> 有无穷多的实数，但只有有限的数（确切地说，18,437,736,874,454,810,627）可以用 JavaScript 浮点格式精确地表示。这意味着在 JavaScript 中处理实数时，数字的表示通常是实际数字的近似值。\n\nThe IEEE-754 floating-point representation used by JavaScript (and just about every other modern programming language) is a binary representation, which can exactly represent fractions like 1/2, 1/8, and 1/1024. Unfortunately, the fractions we use most commonly (especially when performing financial calculations) are decimal fractions: 1/10, 1/100, and so on. Binary floating-point representations cannot exactly represent numbers as simple as 0.1.\n\n> JavaScript（以及几乎所有其他现代编程语言）使用的 IEEE-754 浮点表示法是二进制表示法，它可以精确地表示像 1/2、1/8 和 1/1024 这样的分数。不幸的是，我们最常用的分数（特别是在执行财务计算时）是十进制分数：1/10、1/100，等等。二进制浮点表示法不能精确地表示像 0.1 这样简单的数字。\n\nJavaScript numbers have plenty of precision and can approximate 0.1 very closely. But the fact that this number cannot be represented exactly can lead to problems. Consider this code:\n\n> JavaScript 数字有足够的精度，可以非常接近 0.1。但这个数字不能准确地表示，这可能会导致问题。考虑这段代码：\n\n```js\nlet x = .3 - .2;    // thirty cents minus 20 cents\nlet y = .2 - .1;    // twenty cents minus 10 cents\nx === y             // => false: the two values are not the same!\nx === .1            // => false: .3-.2 is not equal to .1\ny === .1            // => true: .2-.1 is equal to .1\n```\n\nBecause of rounding error, the difference between the approximations of .3 and .2 is not exactly the same as the difference between the approximations of .2 and .1. It is important to understand that this problem is not specific to JavaScript: it affects any programming language that uses binary floating-point numbers. Also, note that the values x and y in the code shown here are very close to each other and to the correct value. The computed values are adequate for almost any purpose; the problem only arises when we attempt to compare values for equality.\n\n> 由于舍入误差，.3 和 .2 的近似值与 .2 和 .1 的近似值之间的差并不完全相同。需要注意的是，这个问题并不是 JavaScript 特有的：它会影响任何使用二进制浮点数的编程语言。另外，请注意，这里显示的代码中的值 x 和 y 非常接近，也非常接近正确的值。计算的值几乎适用于任何目的；只有当我们试图比较平等的价值时，问题才会出现。\n\nIf these floating-point approximations are problematic for your programs, consider using scaled integers. For example, you might manipulate monetary values as integer cents rather than fractional dollars.\n\n> 如果这些浮点逼使的程序出现问题时，请考虑使用缩放整数。例如，您可以将货币值转为整数美分，而不是小数美元。\n\n### 3.2.5 Arbitrary Precision Integers with BigInt\n\nOne of the newest features of JavaScript, defined in ES2020, is a new numeric type known as BigInt. As of early 2020, it has been implemented in Chrome, Firefox, Edge, and Node, and there is an implementation in progress in Safari. As the name implies, BigInt is a numeric type whose values are integers. The type was added to JavaScript mainly to allow the representation of 64-bit integers, which are required for compatibility with many other programming languages and APIs. But BigInt values can have thousands or even millions of digits, should you have need to work with numbers that large. (Note, however, that BigInt implementations are not suitable for cryptography because they do not attempt to prevent timing attacks.)\n\n> 在 ES2020 中定义的 JavaScript 最新特性之一是一种名为 BigInt 的新数字类型。到 2020 年初，它已经在 Chrome、Firefox、Edge 和 Node 中实现，Safari 也正在实现中。顾名思义，BigInt 是一种数值类型，其值为整数。该类型添加到 JavaScript 主要是为了支持 64 位整数的表示，这是与许多其他编程语言和 api 兼容所必需的。但是，如果需要处理这么大的数字，BigInt 值可以有数千甚至数百万位数字。（但是，请注意，BigInt 实现不适合密码学，因为它们不试图阻止计时攻击。）\n\nBigInt literals are written as a string of digits followed by a lowercase letter n. By default, the are in base 10, but you can use the 0b, 0o, and 0x prefixes for binary, octal, and hexadecimal BigInts:\n\n> Bigint 字面量以数字字符串编写，后跟小写字母 n。默认情况下，它是 10 进制的，但您可以将 0b、0o 和 0x 前缀用于二进制、八进制和十六进制 BigInts：\n\n```js\n1234n                // A not-so-big BigInt literal\n0b111111n            // A binary BigInt\n0o7777n              // An octal BigInt\n0x8000000000000000n  // => 2n**63n: A 64-bit integer\n```\nYou can use BigInt() as a function for converting regular JavaScript numbers or strings to BigInt values:\n\n> 你可以使用 BigInt() 作为函数，将普通的 JavaScript 数字或字符串转换为 BigInt 值:\n\n```js\nBigInt(Number.MAX_SAFE_INTEGER)     // => 9007199254740991n\nlet string = \"1\" + \"0\".repeat(100); // 1 followed by 100 zeros.\nBigInt(string)                      // => 10n**100n: one googol\n```\n\nArithmetic with BigInt values works like arithmetic with regular JavaScript numbers, except that division drops any remainder and rounds down (toward zero):\n\n> 使用 BigInt 值的算术运算与使用常规 JavaScript 数字的算术运算类似，只不过除法会去掉任何余数（归 0）:\n\n```js\n1000n + 2000n  // => 3000n\n3000n - 2000n  // => 1000n\n2000n * 3000n  // => 6000000n\n3000n / 997n   // => 3n: the quotient is 3\n3000n % 997n   // => 9n: and the remainder is 9\n(2n ** 131071n) - 1n  // A Mersenne prime with 39457 decimal digits\n```\n\nAlthough the standard +, -, *, /, %, and ** operators work with BigInt, it is important to understand that you may not mix operands of type BigInt with regular number operands. This may seem confusing at first, but there is a good reason for it. If one numeric type was more general than the other, it would be easy to define arithmetic on mixed operands to simply return a value of the more general type. But neither type is more general than the other: BigInt can represent extraordinarily large values, making it more general than regular numbers. But BigInt can only represent integers, making the regular JavaScript number type more general. There is no way around this problem, so JavaScript sidesteps it by simply not allowing mixed operands to the arithmetic operators.\n\n> 尽管标准的 +、-、*、/、% 和 ** 运算符可用于BigInt，但重要的是要理解不能将 BigInt 类型的操作数与常规的数字操作数混合使用。乍一看，这可能令人困惑，但这是有充分理由的。如果一种数字类型比另一种类型更通用，那么很容易在混合操作数上定义算术，只返回更通用类型的值。但是这两种类型都比另一种更通用：BigInt可以表示非常大的值，这使得它比普通数字更通用。但是 BigInt 只能表示整数，这使得常规的 JavaScript 数字类型更加通用。没有办法绕过这个问题，所以 JavaScript 只是通过不允许算术运算符使用混合操作数来绕过这个问题。\n\nComparison operators, by contrast, do work with mixed numeric types (but see §3.9.1 for more about the difference between == and ===):\n\n> 相比之下，比较运算符适用于混合数值类型（关于 == 和 === 的区别，请参阅 §3.9.1）：\n\n```js\n1 < 2n     // => true\n2 > 1n     // => true\n0 == 0n    // => true\n0 === 0n   // => false: the === checks for type equality as well\n```\n\nThe bitwise operators (described in §4.8.3) generally work with BigInt operands. None of the functions of the Math object accept BigInt operands, however.\n\n> 按位运算符（在 §4.8.3 中描述）通常用于 BigInt 操作数。但是，Math 对象的函数都不接受 BigInt 操作数。\n\n### 3.2.6 Dates and Times\nJavaScript defines a simple Date class for representing and manipulating the numbers that represent dates and times. JavaScript Dates are objects, but they also have a numeric representation as a timestamp that specifies the number of elapsed milliseconds since January 1, 1970:\n\n> JavaScript 定义了一个简单的 Date 类，用于表示和操作表示日期和时间的数字。JavaScript 的日期是对象，但它们也有一个数字表示形式的时间戳，指定了自1970年1月1日以来经过的毫秒数：\n\n```js\nlet timestamp = Date.now();  // The current time as a timestamp (a number).\nlet now = new Date();        // The current time as a Date object.\nlet ms = now.getTime();      // Convert to a millisecond timestamp.\nlet iso = now.toISOString(); // Convert to a string in standard format.\n```\nThe Date class and its methods are covered in detail in §11.4. But we will see Date objects again in §3.9.3 when we examine the details of JavaScript type conversions.\n\n> Date 类及其方法在 §11.4 中有详细介绍。但在 §3.9.3 节中，我们会在讨论 JavaScript 类型转换的细节时再次看到 Date 对象。\n\n## 3.3 Text\n\nThe JavaScript type for representing text is the string. A string is an immutable ordered sequence of 16-bit values, each of which typically represents a Unicode character. The length of a string is the number of 16-bit values it contains. JavaScript’s strings (and its arrays) use zero-based indexing: the first 16-bit value is at position 0, the second at position 1, and so on. The empty string is the string of length 0. JavaScript does not have a special type that represents a single element of a string. To represent a single 16-bit value, simply use a string that has a length of 1.\n\n> 表示文本的 JavaScript 类型是字符串。字符串是由 16 位值组成的不可变有序序列，每个值通常代表一个 Unicode 字符。字符串的长度是它包含的 16 位值的个数。JavaScript 的字符串（及其数组）使用从零开始的索引：第一个 16 位的值位于位置 0，第二个位于位置 1，以此类推。空字符串是长度为 0 的字符串。JavaScript 没有特殊的类型来表示字符串的单个元素。要表示单个 16 位值，只需使用长度为 1 的字符串。\n\nCHARACTERS, CODEPOINTS, AND JAVASCRIPT STRINGS\n\n> **字符集，内码和JavaScript字符串**\n\nJavaScript uses the UTF-16 encoding of the Unicode character set, and JavaScript strings are sequences of unsigned 16-bit values. The most commonly used Unicode characters (those from the “basic multilingual plane”) have codepoints that fit in 16 bits and can be represented by one element of a string. Unicode characters whose codepoints do not fit in 16 bits are encoded using the rules of UTF-16 as a sequence (known as a “surrogate pair”) of two 16-bit values. This means that a JavaScript string of length 2 (two 16-bit values) might represent only a single Unicode character:\n\n> JavaScript 使用 Unicode 字符集的 UTF-16 编码，JavaScript 字符串是无符号的 16 位值序列。最常用的 Unicode 字符（来自“基本多语种平面”的字符）都是通过 16 位的内码表示，并代表字符串中的单个字符。那些不能表示为16位的 Unicode 字符则遵循 UTF-16 编码规则——用两个 16 位值组成的一个序列（亦称做“代理项对”）表示。这意味着一个长度为 2 的 JavaScript 字符串（两个 16 位值）有可能表 一个 Unicode 字符：\n\n```js\nlet euro = \"€\";\nlet love = \"❤\";\neuro.length   // => 1: this character has one 16-bit element\nlove.length   // => 2: UTF-16 encoding of ❤ is \"\\ud83d\\udc99\"\n```\n\nMost string-manipulation methods defined by JavaScript operate on 16-bit values, not characters. They do not treat surrogate pairs specially, they perform no normalization of the string, and don’t even ensure that a string is well-formed UTF-16.\n\n> JavaScript 定义的大多数字符串操作方法操作的是 16 位值，而不是字符。它们没有特别对待代理对，它们没有对字符串执行规范化，甚至不确保字符串是格式良好的 UTF-16。\n\nIn ES6, however, strings are iterable, and if you use the for/of loop or ... operator with a string, it will iterate the actual characters of the string, not the 16-bit values.\n\n> 然而，在 ES6 中，字符串是可迭代的，如果你使用 for/of 循环或 … 运算符，它将迭代字符串的实际字符，而不是 16 位值。\n\n### 3.3.1 String Literals\n\nTo include a string in a JavaScript program, simply enclose the characters of the string within a matched pair of single or double quotes or backticks (' or \" or `). Double-quote characters and backticks may be contained within strings delimited by single-quote characters, and similarly for strings delimited by double quotes and backticks. Here are examples of string literals:\n\n> 要在 JavaScript 程序中包含字符串，只需将字符串的字符包含在匹配的单引号或双引号或反引号（' or \" or `）中。双引号字符和反引号可以包含在由单引号字符分隔的字符串中，由双引号和反引号分隔的字符串也是如此。以下是字符串字面量的例子:\n\n```js\n\"\"  // The empty string: it has zero characters\n'testing'\n\"3.14\"\n'name=\"myform\"'\n\"Wouldn't you prefer O'Reilly's book?\"\n\"τ is the ratio of a circle's circumference to its radius\"\n`\"She said 'hi'\", he said.`\n```\n\nStrings delimited with backticks are a feature of ES6, and allow JavaScript expressions to be embedded within (or interpolated into) the string literal. This expression interpolation syntax is covered in §3.3.4.\n\n> 用反引号包含的字符串是 ES6 的一个特性，它允许 JavaScript 表达式嵌入（或插入）字符串字面量。这种表达式插值语法在 §3.3.4 中介绍。\n\nThe original versions of JavaScript required string literals to be written on a single line, and it is common to see JavaScript code that creates long strings by concatenating single-line strings with the + operator. As of ES5, however, you can break a string literal across multiple lines by ending each line but the last with a backslash `(\\)`. Neither the backslash nor the line terminator that follow it are part of the string literal. If you need to include a newline character in a single-quoted or double-quoted string literal, use the character sequence \\n (documented in the next section). The ES6 backtick syntax allows strings to be broken across multiple lines, and in this case, the line terminators are part of the string literal:\n\n> 最初的 JavaScript 版本要求将字符串文字写在单行上，通常会看到 JavaScript 代码通过使用 + 运算符连接单行字符串来创建长字符串。但是，在 ES5 中，您可以通过在每行末尾使用反斜杠（`\\`）来将字符串分隔成多行。反斜杠及其后面的行结束符都不是字符串字面值的一部分。如果需要在单引号或双引号的字符串文字中包含换行字符，请使用字符序列 \\n（在下一节中介绍）。ES6 反引号语法允许字符串跨多行分割，在这种情况下，行结束符是字符串字面值的一部分：\n\n```js\n// A string representing 2 lines written on one line:\n'two\\nlines'\n\n// A one-line string written on 3 lines:\n\"one\\\n long\\\n line\"\n\n// A two-line string written on two lines:\n`the newline character at the end of this line\nis included literally in this string`\n```\n\nNote that when you use single quotes to delimit your strings, you must be careful with English contractions and possessives, such as can’t and O’Reilly’s. Since the apostrophe is the same as the single-quote character, you must use the backslash character (`\\`) to “escape” any apostrophes that appear in single-quoted strings (escapes are explained in the next section).\n\n> 注意，当您使用单引号来分隔字符串时，必须小心使用英语缩写和所有格，如 can't 和 O'Reilly's。由于撇号与单引号字符相同，必须使用反斜杠字符（`\\`）来“转义”出现在单引号字符串中的任何撇号（转义将在下一节中解释）。\n\nIn client-side JavaScript programming, JavaScript code may contain strings of HTML code, and HTML code may contain strings of JavaScript code. Like JavaScript, HTML uses either single or double quotes to delimit its strings. Thus, when combining JavaScript and HTML, it is a good idea to use one style of quotes for JavaScript and the other style for HTML. In the following example, the string “Thank you” is single-quoted within a JavaScript expression, which is then double-quoted within an HTML event-handler attribute:\n\n> 在客户端 JavaScript 编程中，JavaScript 代码可能包含 HTML 代码字符串，HTML 代码可能包含 JavaScript 代码字符串。和 JavaScript 一样，HTML 也使用单引号或双引号来分隔字符串。因此，在组合 JavaScript 和 HTML 时，最好使用一种风格的引号用于 JavaScript，另一种风格的引号用于 HTML。在下面的例子中，字符串“Thank you”在 JavaScript 表达式中被单引号引用，然后在 HTML 事件处理程序属性中被双引号引用:\n\n```js\n<button onclick=\"alert('Thank you')\">Click Me</button>\n```\n### 3.3.2 Escape Sequences in String Literals\n\nThe backslash character `(\\)` has a special purpose in JavaScript strings. Combined with the character that follows it, it represents a character that is not otherwise representable within the string. For example, \\n is an escape sequence that represents a newline character.\n\n> 反斜杠字符（`\\`）在 JavaScript 字符串中有特殊用途。与它后面的字符相结合，它表示字符串中不能以其他方式表示的字符。例如，\\n 是表示换行符的转义序列。\n\nAnother example, mentioned earlier, is the \\' escape, which represents the single quote (or apostrophe) character. This escape sequence is useful when you need to include an apostrophe in a string literal that is contained within single quotes. You can see why these are called escape sequences: the backslash allows you to escape from the usual interpretation of the single-quote character. Instead of using it to mark the end of the string, you use it as an apostrophe:\n\n> 前面提到的另一个例子是 \\' 转义符，它表示单引号（或撇号）字符。当您需要在包含在单引号中的字符串文本中包含撇号时，这个转义序列非常有用。您可以看到为什么它们被称为转义序列：反斜杠允许您转义单引号字符的通常解释。不是用它来标记字符串的结束，而是用它作为撇号：\n\n```js\n'You\\'re right, it can\\'t be a quote'\n```\nTable 3-1 lists the JavaScript escape sequences and the characters they represent. Three escape sequences are generic and can be used to represent any character by specifying its Unicode character code as a hexadecimal number. For example, the sequence \\xA9 represents the copyright symbol, which has the Unicode encoding given by the hexadecimal number A9. Similarly, the \\u escape represents an arbitrary Unicode character specified by four hexadecimal digits or one to five digits when the digits are enclosed in curly braces: \\u03c0 represents the character π, for example, and \\u{1f600} represents the “grinning face” emoji.\n\n> JavaScript 转义序列及其代表的字符如表 3-1 所示。三个转义序列是通用的，可以通过将 Unicode 字符代码指定为十六进制数来表示任何字符。例如，序列 \\xA9 表示版权符号，它具有由十六进制数字 A9 给出的 Unicode 编码。同样，\\u 转义表示由四个十六进制数字或用花括号括起来的 1 ~ 5 个数字指定的任意 Unicode 字符：例如，\\u03c0 表示字符 π，\\u{1f600} 表示“龇牙笑”表情。\n\nTable 3-1. JavaScript escape sequences\n\n| Sequence | Character represented                                                                                                   |\n| -------- | ----------------------------------------------------------------------------------------------------------------------- |\n| \\0       | The NUL character (\\u0000)                                                                                              |\n| \\b       | Backspace (\\u0008)                                                                                                      |\n| \\t       | Horizontal tab (\\u0009)                                                                                                 |\n| \\n       | Newline (\\u000A)                                                                                                        |\n| \\v       | Vertical tab (\\u000B)                                                                                                   |\n| \\f       | Form feed (\\u000C)                                                                                                      |\n| \\r       | Carriage return (\\u000D)                                                                                                |\n| \\\"       | Double quote (\\u0022)                                                                                                   |\n| \\'       | Apostrophe or single quote (\\u0027)                                                                                     |\n| `\\\\`     | Backslash (\\u005C)                                                                                                      |\n| \\xnn     | The Unicode character specified by the two hexadecimal digits nn                                                        |\n| \\unnnn   | The Unicode character specified by the four hexadecimal digits nnnn                                                     |\n| \\u{n}    | The Unicode character specified by the codepoint n, where n is one to six hexadecimal digits between 0 and 10FFFF (ES6) |\n\nIf the \\ character precedes any character other than those shown in Table 3-1, the backslash is simply ignored (although future versions of the language may, of course, define new escape sequences). For example, `\\#` is the same as #. Finally, as noted earlier, ES5 allows a backslash before a line break to break a string literal across multiple lines.\n\n> 如果 \\ 字符在表 3-1 中以外的任何字符之前，反斜杠将被忽略（当然，未来版本的语言可能会定义新的转义序列）。例如，`\\#` 和 # 是一样的。最后，如前所述，ES5 允许在换行之前使用反斜杠来跨多行分割字符串字面量。\n\n### 3.3.3 Working with Strings\n\nOne of the built-in features of JavaScript is the ability to concatenate strings. If you use the + operator with numbers, it adds them. But if you use this operator on strings, it joins them by appending the second to the first. For example:\n\n> JavaScript 的内置特性之一是连接字符串的能力。如果您对数字使用 + 运算符，它将它们相加。但是如果你在字符串上使用这个运算符，它通过将第二个运算符附加到第一个运算符上来连接字符串。例如：\n\n```js\nlet msg = \"Hello, \" + \"world\";   // Produces the string \"Hello, world\"\nlet greeting = \"Welcome to my blog,\" + \" \" + name;\n```\nStrings can be compared with the standard === equality and !== inequality operators: two strings are equal if and only if they consist of exactly the same sequence of 16-bit values. Strings can also be compared with the <, <=, >, and >= operators. String comparison is done simply by comparing the 16-bit values. (For more robust locale-aware string comparison and sorting, see §11.7.3.)\n\n> 字符串可以与标准的 === 相等和 !== 不等运算符进行比较：当且仅当两个字符串由完全相同的 16 位值序列组成时，它们才相等。字符串还可以与 <、<=、> 和 >= 运算符进行比较。字符串比较简单地通过比较 16 位值来完成。（更健壮的语言环境感知字符串比较和排序，见 §11.7.3。）\n\nTo determine the length of a string—the number of 16-bit values it contains—use the length property of the string:\n\n> 要确定一个字符串的长度——它包含的 16 位值的数量——使用字符串的 length 属性：\n\n```js\ns.length\n```\n\nIn addition to this length property, JavaScript provides a rich API for working with strings:\n\n> 除了这个 length 属性，JavaScript 还提供了一个丰富的 API 来处理字符串：\n\n```js\nlet s = \"Hello, world\"; // Start with some text.\n\n// Obtaining portions of a string\ns.substring(1,4)        // => \"ell\": the 2nd, 3rd, and 4th characters.\ns.slice(1,4)            // => \"ell\": same thing\ns.slice(-3)             // => \"rld\": last 3 characters\ns.split(\", \")           // => [\"Hello\", \"world\"]: split at delimiter string\n\n// Searching a string\ns.indexOf(\"l\")          // => 2: position of first letter l\ns.indexOf(\"l\", 3)       // => 3: position of first \"l\" at or after 3\ns.indexOf(\"zz\")         // => -1: s does not include the substring \"zz\"\ns.lastIndexOf(\"l\")      // => 10: position of last letter l\n\n// Boolean searching functions in ES6 and later\ns.startsWith(\"Hell\")    // => true: the string starts with these\ns.endsWith(\"!\")         // => false: s does not end with that\ns.includes(\"or\")        // => true: s includes substring \"or\"\n\n// Creating modified versions of a string\ns.replace(\"llo\", \"ya\")  // => \"Heya, world\"\ns.toLowerCase()         // => \"hello, world\"\ns.toUpperCase()         // => \"HELLO, WORLD\"\ns.normalize()           // Unicode NFC normalization: ES6\ns.normalize(\"NFD\")      // NFD normalization. Also \"NFKC\", \"NFKD\"\n\n// Inspecting individual (16-bit) characters of a string\ns.charAt(0)             // => \"H\": the first character\ns.charAt(s.length-1)    // => \"d\": the last character\ns.charCodeAt(0)         // => 72: 16-bit number at the specified position\ns.codePointAt(0)        // => 72: ES6, works for codepoints > 16 bits\n\n// String padding functions in ES2017\n\"x\".padStart(3)         // => \"  x\": add spaces on the left to a length of 3\n\"x\".padEnd(3)           // => \"x  \": add spaces on the right to a length of 3\n\"x\".padStart(3, \"*\")    // => \"**x\": add stars on the left to a length of 3\n\"x\".padEnd(3, \"-\")      // => \"x--\": add dashes on the right to a length of 3\n\n// Space trimming functions. trim() is ES5; others ES2019\n\" test \".trim()         // => \"test\": remove spaces at start and end\n\" test \".trimStart()    // => \"test \": remove spaces on left. Also trimLeft\n\" test \".trimEnd()      // => \" test\": remove spaces at right. Also trimRight\n\n// Miscellaneous string methods\ns.concat(\"!\")           // => \"Hello, world!\": just use + operator instead\n\"<>\".repeat(5)          // => \"<><><><><>\": concatenate n copies. ES6\n```\n\nRemember that strings are immutable in JavaScript. Methods like replace() and toUpperCase() return new strings: they do not modify the string on which they are invoked.\n\n> 记住，字符串在 JavaScript 中是不可变的。像 replace() 和 toUpperCase() 这样的方法返回新的字符串：它们不会修改调用它们的字符串。\n\nStrings can also be treated like read-only arrays, and you can access individual characters (16-bit values) from a string using square brackets instead of the charAt() method:\n\n> 字符串也可以像只读数组一样处理，你可以使用方括号而不是 charAt() 方法从字符串中访问单个字符（16 位值）:\n\n```js\nlet s = \"hello, world\";\ns[0]                  // => \"h\"\ns[s.length-1]         // => \"d\"\n```\n### 3.3.4 Template Literals\n\nIn ES6 and later, string literals can be delimited with backticks:\n\n> 在 ES6 及以后的版本中，字符串文字可以用反引号分隔：\n```js\nlet s = `hello world`;\n```\nThis is more than just another string literal syntax, however, because these template literals can include arbitrary JavaScript expressions. The final value of a string literal in backticks is computed by evaluating any included expressions, converting the values of those expressions to strings and combining those computed strings with the literal characters within the backticks:\n\n> 然而，这不仅仅是另一种字符串字面量语法，因为这些模板字面量可以包含任意的 JavaScript 表达式。反引号中的字符串字面量的原始值是通过计算包含的表达式来计算的，将这些表达式的值转换为字符串，并将这些计算出来的字符串与反引号中的字面量字符组合起来:\n\n```js\nlet name = \"Bill\";\nlet greeting = `Hello ${ name }.`;  // greeting == \"Hello Bill.\"\n```\n\nEverything between the ${ and the matching } is interpreted as a JavaScript expression. Everything outside the curly braces is normal string literal text. The expression inside the braces is evaluated and then converted to a string and inserted into the template, replacing the dollar sign, the curly braces, and everything in between them.\n\n> ${ 和匹配 } 之间的所有内容都被解释为 JavaScript 表达式。大括号之外的都是普通的字符串文本。括号内的表达式被求值，然后转换为字符串并插入到模板中，替换美元符号、花括号以及它们之间的所有内容。\n\nA template literal may include any number of expressions. It can use any of the escape characters that normal strings can, and it can span any number of lines, with no special escaping required. The following template literal includes four JavaScript expressions, a Unicode escape sequence, and at least four newlines (the expression values may include newlines as well):\n\n> 模板字面量可以包含任意数量的表达式。它可以使用普通字符串可以使用的任何转义字符，并且可以跨任意数量的行，不需要特殊转义。下面的模板文字包含四个 JavaScript 表达式，一个 Unicode 转义序列，以及至少四个换行符（表达式值也可以包含换行符）：\n\n```js\nlet errorMessage = `\\\n\\u2718 Test failure at ${filename}:${linenumber}:\n${exception.message}\nStack trace:\n${exception.stack}\n`;\n```\n\nThe backslash at the end of the first line here escapes the initial newline so that the resulting string begins with the Unicode ✘ character (\\u2718) rather than a newline.\n\n> 第一行末尾的反斜杠转义了最初的换行符，这样产生的字符串以 Unicode 符合英语习惯的字符（\\u2718）开始，而不是换行符。\n\nTAGGED TEMPLATE LITERALS\n\n> **标签模版字面量**\n\nA powerful but less commonly used feature of template literals is that, if a function name (or “tag”) comes right before the opening backtick, then the text and the values of the expressions within the template literal are passed to the function. The value of this “tagged template literal” is the return value of the function. This could be used, for example, to apply HTML or SQL escaping to the values before substituting them into the text.\n\n> 模板字面值的一个强大但不太常用的特性是，如果一个函数名（或“标签”）恰好出现在开始的反勾之前，那么模板字面值中的文本和表达式的值就会传递给该函数。这个“标记模板字面量”的值是函数的返回值。例如，这可以用于在将值替换到文本之前对值应用 HTML 或 SQL 转义。\n\nES6 has one built-in tag function: String.raw(). It returns the text within backticks without any processing of backslash escapes:\n\n> ES6 有一个内置的标记函数：String.raw()。它在不处理任何反斜杠转义的情况下返回带有反引号的文本:\n\n```js\n`\\n`.length            // => 1: the string has a single newline character\nString.raw`\\n`.length  // => 2: a backslash character and the letter n\n```\n\nNote that even though the tag portion of a tagged template literal is a function, there are no parentheses used in its invocation. In this very specific case, the backtick characters replace the open and close parentheses.\n\n> 请注意，尽管标记模板文字的标记部分是一个函数，但在调用它时没有使用圆括号。在这个非常特定的例子中，反勾字符替换了开括号和闭括号。\n\nThe ability to define your own template tag functions is a powerful feature of JavaScript. These functions do not need to return strings, and they can be used like constructors, as if defining a new literal syntax for the language. We’ll see an example in §14.5.\n\n> 定义自己的模板标记函数的能力是 JavaScript 的一个强大特性。这些函数不需要返回字符串，它们可以像构造函数一样使用，就像为语言定义新的文字语法一样。我们将在 §14.5 中看到一个例子。\n\n### 3.3.5 Pattern Matching\n\nJavaScript defines a datatype known as a regular expression (or RegExp) for describing and matching patterns in strings of text. RegExps are not one of the fundamental datatypes in JavaScript, but they have a literal syntax like numbers and strings do, so they sometimes seem like they are fundamental. The grammar of regular expression literals is complex and the API they define is nontrivial. They are documented in detail in §11.3. Because RegExps are powerful and commonly used for text processing, however, this section provides a brief overview.\n\n> JavaScript 定义了一种称为正则表达式（或RegExp）的数据类型，用于描述和匹配文本字符串中的模式。regexp 并不是 JavaScript 中的基本数据类型之一，但它们像数字和字符串一样有字面语法，所以有时看起来它们似乎是基本类型。正则表达式字面量的语法很复杂，它们定义的 API 也很重要。它们在 §11.3 中有详细的记录。但是，由于 regexp 功能强大，通常用于文本处理，因此本节提供一个简要概述。\n\nText between a pair of slashes constitutes a regular expression literal. The second slash in the pair can also be followed by one or more letters, which modify the meaning of the pattern. For example:\n\n> 一对斜杠之间的文本构成了正则表达式字面量。对中的第二个斜杠还可以后跟一个或多个字母，它们修改了模式的含义。例如：\n\n```js\n/^HTML/;             // Match the letters H T M L at the start of a string\n/[1-9][0-9]*/;       // Match a nonzero digit, followed by any # of digits\n/\\bjavascript\\b/i;   // Match \"javascript\" as a word, case-insensitive\n```\n\nRegExp objects define a number of useful methods, and strings also have methods that accept RegExp arguments. For example:\n\n> RegExp 对象定义了许多有用的方法，字符串也有接受 RegExp 实参的方法。例如：\n\n```js\nlet text = \"testing: 1, 2, 3\";   // Sample text\nlet pattern = /\\d+/g;            // Matches all instances of one or more digits\npattern.test(text)               // => true: a match exists\ntext.search(pattern)             // => 9: position of first match\ntext.match(pattern)              // => [\"1\", \"2\", \"3\"]: array of all matches\ntext.replace(pattern, \"#\")       // => \"testing: #, #, #\"\ntext.split(/\\D+/)                // => [\"\",\"1\",\"2\",\"3\"]: split on nondigits\n```\n## 3.4 Boolean Values\n\nA boolean value represents truth or falsehood, on or off, yes or no. There are only two possible values of this type. The reserved words true and false evaluate to these two values.\n\n> 布尔值表示真或假、开或关、是或否。这种类型只有两个可能的值。保留字 true 和 false 计算这两个值。\n\nBoolean values are generally the result of comparisons you make in your JavaScript programs. For example:\n\n> 布尔值通常是你在 JavaScript 程序中进行比较的结果。例如：\n\n```js\na === 4\n```\n\nThis code tests to see whether the value of the variable a is equal to the number 4. If it is, the result of this comparison is the boolean value true. If a is not equal to 4, the result of the comparison is false.\n\n> 这段代码测试变量 a 的值是否等于数字 4。如果是，则此比较的结果为布尔值 true。如果 a 不等于 4，则比较的结果为 false。\n\nBoolean values are commonly used in JavaScript control structures. For example, the if/else statement in JavaScript performs one action if a boolean value is true and another action if the value is false. You usually combine a comparison that creates a boolean value directly with a statement that uses it. The result looks like this:\n\n> 布尔值通常用于 JavaScript 的控制结构中。例如，JavaScript 中的 if/else 语句在布尔值为 true 时执行一个动作，在值为 false 时执行另一个动作。通常将直接创建布尔值的比较与使用它的语句组合在一起。结果是这样的：\n\n```js\nif (a === 4) {\n    b = b + 1;\n} else {\n    a = a + 1;\n}\n```\n\nThis code checks whether a equals 4. If so, it adds 1 to b; otherwise, it adds 1 to a.\n\n> 这段代码检查 a 是否等于 4。如果是，则给 b 加 1；否则，它给 a 加 1。\n\nAs we’ll discuss in §3.9, any JavaScript value can be converted to a boolean value. The following values convert to, and therefore work like, false:\n\n> 正如我们将在 §3.9 中讨论的，任何 JavaScript 值都可以转换为布尔值。下面的值会转换为 false，因此工作方式类似于 false：\n\n```js\nundefined\nnull\n0\n-0\nNaN\n\"\"  // the empty string\n```\n\nAll other values, including all objects (and arrays) convert to, and work like, true. false, and the six values that convert to it, are sometimes called falsy values, and all other values are called truthy. Any time JavaScript expects a boolean value, a falsy value works like false and a truthy value works like true.\n\n> 所有其他值，包括所有对象（和数组）都转换为 true，并像 true 一样工作。假值，以及转换为假值的六个值，有时被称为假值，其他所有值都被称为真值。任何时候 JavaScript 期望一个布尔值，都可以将假值作为 false 使用，真值作为 true。\n\nAs an example, suppose that the variable o either holds an object or the value null. You can test explicitly to see if o is non-null with an if statement like this:\n\n> 例如，假设变量 o 保存一个对象或值为 null。你可以使用如下的 if 语句来显式测试 o 是否非空：\n\n```js\nif (o !== null) ...\n```\n\nThe not-equal operator !== compares o to null and evaluates to either true or false. But you can omit the comparison and instead rely on the fact that null is falsy and objects are truthy:\n\n> 不相等运算符 !== 将 o 与 null 进行比较，然后求值为 true 或 false。但是你可以忽略比较，而是依赖 null 是假的，对象是真的：\n\n```js\nif (o) ...\n```\n\nIn the first case, the body of the if will be executed only if o is not null. The second case is less strict: it will execute the body of the if only if o is not false or any falsy value (such as null or undefined). Which if statement is appropriate for your program really depends on what values you expect to be assigned to o. If you need to distinguish null from 0 and \"\", then you should use an explicit comparison.\n\n> 在第一种情况下，if 函数体只有在 o 不为空时才会执行。第二种情况不那么严格：它只在 o 不为假值或任何假值（如 null 或 undefined）时执行 if 语句体。哪个 if 语句适合你的程序实际上取决于你希望给 o 赋什么值。如果你需要区分 null 和 0 和 \"\"，那么你应该使用显式比较。\n\nBoolean values have a toString() method that you can use to convert them to the strings “true” or “false”, but they do not have any other useful methods. Despite the trivial API, there are three important boolean operators.\n\n> 布尔值有一个 toString() 方法，可以用来将它们转换为字符串“true”或“false”，但它们没有任何其他有用的方法。尽管 API 很简单，但是有三个重要的布尔运算符。\n\nThe && operator performs the Boolean AND operation. It evaluates to a truthy value if and only if both of its operands are truthy; it evaluates to a falsy value otherwise. The || operator is the Boolean OR operation: it evaluates to a truthy value if either one (or both) of its operands is truthy and evaluates to a falsy value if both operands are falsy. Finally, the unary ! operator performs the Boolean NOT operation: it evaluates to true if its operand is falsy and evaluates to false if its operand is truthy. For example:\n\n> && 运算符执行布尔和运算。当且仅当它的两个操作数都为真时，它的计算结果为真值；否则它将计算为假值。|| 运算符是布尔或运算符：如果其中一个操作数（或两个操作数）为真，则计算为真值；如果两个操作数都为假，则计算为假值。最后，一元 ! 运算符执行布尔的 NOT 操作：如果操作数为假，则计算为真；如果操作数为真，则计算为假。例如：\n\n```js\nif ((x === 0 && y === 0) || !(z === 0)) {\n    // x and y are both zero or z is non-zero\n}\n```\nFull details on these operators are in §4.10.\n\n> 关于这些运算符的全部细节见 §4.10。\n\n## 3.5 null and undefined\n\nnull is a language keyword that evaluates to a special value that is usually used to indicate the absence of a value. Using the typeof operator on null returns the string “object”, indicating that null can be thought of as a special object value that indicates “no object”. In practice, however, null is typically regarded as the sole member of its own type, and it can be used to indicate “no value” for numbers and strings as well as objects. Most programming languages have an equivalent to JavaScript’s null: you may be familiar with it as NULL, nil, or None.\n\n> null 是一个语言关键字，其计算结果为一个特殊值，该值通常用于指示没有值。对 null 使用 typeof 运算符返回字符串“object”，表示 null 可以被认为是一个特殊的对象值，表示“没有对象”。然而，在实践中，null 通常被视为它自己类型的唯一成员，它可以用来表示数字、字符串和对象的“无值”。大多数编程语言都有一个等价于 JavaScript 的 null：您可能熟悉它为 NULL、nil 或 None。\n\nJavaScript also has a second value that indicates absence of value. The undefined value represents a deeper kind of absence. It is the value of variables that have not been initialized and the value you get when you query the value of an object property or array element that does not exist. The undefined value is also the return value of functions that do not explicitly return a value and the value of function parameters for which no argument is passed. undefined is a predefined global constant (not a language keyword like null, though this is not an important distinction in practice) that is initialized to the undefined value. If you apply the typeof operator to the undefined value, it returns “undefined”, indicating that this value is the sole member of a special type.\n\n> JavaScript 还有第二个值，表示没有值。undefined 值代表一种更深层次的缺席。它是尚未初始化的变量的值，以及在查询不存在的对象属性或数组元素的值时获得的值。未定义的值也是没有显式返回值的函数的返回值，以及没有传递实参的函数形参的值。undefined 是一个预定义的全局常量（不是像 null 这样的语言关键字，虽然这在实践中不是一个重要的区别），它被初始化为 undefined 值。如果对未定义的值应用 typeof 运算符，它将返回“undefined”，表明该值是特殊类型的唯一成员。\n\nDespite these differences, null and undefined both indicate an absence of value and can often be used interchangeably. The equality operator == considers them to be equal. (Use the strict equality operator === to distinguish them.) Both are falsy values: they behave like false when a boolean value is required. Neither null nor undefined have any properties or methods. In fact, using . or [] to access a property or method of these values causes a TypeError.\n\n> 尽管存在这些差异，null 和 undefined 都表示没有值，并且经常可以互换使用。相等运算符 == 认为它们相等。（使用严格的相等运算符 === 来区分它们。）两者都是假值：当需要布尔值时，它们的行为类似于假值。null 和 undefined 都没有任何属性或方法。事实上，使用。或 [] 访问这些值的属性或方法会导致 TypeError。\n\nI consider undefined to represent a system-level, unexpected, or error-like absence of value and null to represent a program-level, normal, or expected absence of value. I avoid using null and undefined when I can, but if I need to assign one of these values to a variable or property or pass or return one of these values to or from a function, I usually use null. Some programmers strive to avoid null entirely and use undefined in its place wherever they can.\n\n> 我认为 undefined 表示系统级的、意外的或类似错误的值缺失，null 表示程序级的、正常的或预期的值缺失。我尽可能避免使用 null 和 undefined，但如果我需要分配这些值给一个变量或属性，或函数传递或返回一个这些值，我通常使用 null。一些程序员避免使用 null，而使用 undefined。\n\n## 3.6 Symbols\n\nSymbols were introduced in ES6 to serve as non-string property names. To understand Symbols, you need to know that JavaScript’s fundamental Object type is an unordered collection of properties, where each property has a name and a value. Property names are typically (and until ES6, were exclusively) strings. But in ES6 and later, Symbols can also serve this purpose:\n\n> Symbol 在 ES6 中被引入，用作非字符串属性名。要理解 Symbol，您需要知道 JavaScript 的基本对象类型是属性的无序集合，其中每个属性都有一个名称和一个值。属性名通常是字符串（在 ES6 之前专有的）。但是在 ES6 和以后的版本中，Symbol 也可以达到这个目的：\n\n```js\nlet strname = \"string name\";      // A string to use as a property name\nlet symname = Symbol(\"propname\"); // A Symbol to use as a property name\ntypeof strname                    // => \"string\": strname is a string\ntypeof symname                    // => \"symbol\": symname is a symbol\nlet o = {};                       // Create a new object\no[strname] = 1;                   // Define a property with a string name\no[symname] = 2;                   // Define a property with a Symbol name\no[strname]                        // => 1: access the string-named property\no[symname]                        // => 2: access the symbol-named property\n```\n\nThe Symbol type does not have a literal syntax. To obtain a Symbol value, you call the Symbol() function. This function never returns the same value twice, even when called with the same argument. This means that if you call Symbol() to obtain a Symbol value, you can safely use that value as a property name to add a new property to an object and do not need to worry that you might be overwriting an existing property with the same name. Similarly, if you use symbolic property names and do not share those symbols, you can be confident that other modules of code in your program will not accidentally overwrite your properties.\n\n> Symbol 类型没有文字语法。要获取符号值，可以调用 Symbol() 函数。这个函数不会两次返回相同的值，即使调用时带有相同的实参。这意味着，如果您调用 Symbol() 来获得一个 Symbol 值，您可以安全地使用该值作为属性名，向对象添加一个新属性，而不需要担心可能会用相同的名称覆盖现有的属性。同样，如果您使用 Symbol 属性名而不共享这些符号，您可以确信程序中的其他模块不会意外地覆盖您的属性。\n\nIn practice, Symbols serve as a language extension mechanism. When ES6 introduced the for/of loop (§5.4.4) and iterable objects (Chapter 12), it needed to define standard method that classes could implement to make themselves iterable. But standardizing any particular string name for this iterator method would have broken existing code, so a symbolic name was used instead. As we’ll see in Chapter 12, Symbol.iterator is a Symbol value that can be used as a method name to make an object iterable.\n\n> 实际上，Symbol 是一种语言扩展机制。当 ES6 引入 for/of 循环（ §5.4.4 ）和可迭代对象（第 12 章）时，它需要定义标准方法使类可以迭代。但用任何特定的字符串标准化命名这个迭代器方法都会破坏现有的代码，所以使用了一个符号名称代替。我们将在第 12 章 中看到。Symbol.iterator 是一个 Symbol 值，可以用作方法名，使对象可迭代。\n\nThe Symbol() function takes an optional string argument and returns a unique Symbol value. If you supply a string argument, that string will be included in the output of the Symbol’s toString() method. Note, however, that calling Symbol() twice with the same string produces two completely different Symbol values.\n\n> Symbol() 函数接受一个可选的字符串实参，并返回一个唯一的符号值。如果提供一个字符串实参，该字符串将包含在符号的 toString() 方法的输出中。但是请注意，对同一个字符串调用 Symbol() 两次会产生两个完全不同的符号值。\n\n```js\nlet s = Symbol(\"sym_x\");\ns.toString()             // => \"Symbol(sym_x)\"\n```\ntoString() is the only interesting method of Symbol instances. There are two other Symbol-related functions you should know about, however. Sometimes when using Symbols, you want to keep them private to your own code so you have a guarantee that your properties will never conflict with properties used by other code. Other times, however, you might want to define a Symbol value and share it widely with other code. This would be the case, for example, if you were defining some kind of extension that you wanted other code to be able to participate in, as with the Symbol.iterator mechanism described earlier.\n\n> toString() 是 Symbol 实例中唯一有趣的方法。但是，您还应该了解另外两个与符号相关的函数。有时在使用符号时，您希望将它们保留为自己的代码的私有属性，这样就可以保证属性不会与其他代码使用的属性发生冲突。然而，在其他时候，可能想要定义一个 Symbol 值并与其他代码广泛共享它。例如，如果您正在定义某种希望其他代码能够参与的扩展，就像前面描述的 Symbol.iterator 机制。\n\nTo serve this latter use case, JavaScript defines a global Symbol registry. The Symbol.for() function takes a string argument and returns a Symbol value that is associated with the string you pass. If no Symbol is already associated with that string, then a new one is created and returned; otherwise, the already existing Symbol is returned. That is, the Symbol.for() function is completely different than the Symbol() function: Symbol() never returns the same value twice, but Symbol.for() always returns the same value when called with the same string. The string passed to Symbol.for() appears in the output of toString() for the returned Symbol, and it can also be retrieved by calling Symbol.keyFor() on the returned Symbol.\n\n> 为了满足后一个用例，JavaScript 定义了一个全局 Symbol 注册表。Symbol.for() 函数接受一个字符串实参，并返回与所传递的字符串相关联的 Symbol 值。如果没有与该字符串相关联的符号，则创建并返回一个新的符号；否则，返回已经存在的符号。也就是说，Symbol.for() 函数与 Symbol() 函数完全不同：Symbol() 不会两次返回相同的值，但 Symbol.for() 在使用相同的字符串调用时总是返回相同的值。传递给 Symbol.for() 的字符串出现在返回符号的 toString() 的输出中，也可以通过调用返回符号的 Symbol.keyfor() 来检索它。\n\n```js\nlet s = Symbol.for(\"shared\");\nlet t = Symbol.for(\"shared\");\ns === t          // => true\ns.toString()     // => \"Symbol(shared)\"\nSymbol.keyFor(t) // => \"shared\"\n```\n## 3.7 The Global Object\nThe preceding sections have explained JavaScript’s primitive types and values. Object types—objects, arrays, and functions—are covered in chapters of their own later in this book. But there is one very important object value that we must cover now. The global object is a regular JavaScript object that serves a very important purpose: the properties of this object are the globally defined identifiers that are available to a JavaScript program. When the JavaScript interpreter starts (or whenever a web browser loads a new page), it creates a new global object and gives it an initial set of properties that define:\n\n> 前面的章节已经说明了 JavaScript 的基本类型和值。对象类型——对象、数组和函数——将在本书后面的章节中介绍。但是有一个非常重要的对象值，我们现在必须讲一下。全局对象是一个常规的 JavaScript 对象，它有一个非常重要的用途：该对象的属性是 JavaScript 程序可用的全局定义的标识符。当 JavaScript 解释器启动时(或者每当 web 浏览器加载一个新页面时)，它会创建一个新的全局对象，并为其提供一组初始属性，这些属性定义：\n\n- Global constants like undefined, Infinity, and NaN\n- Global functions like isNaN(), parseInt() (§3.9.2), and eval() (§4.12)\n- Constructor functions like Date(), RegExp(), String(), Object(), and Array() (§3.9.2)\n- Global objects like Math and JSON (§6.8)\n\n---\n\n> - 全局常量，如 undefined、Infinity 和 NaN\n> - 全局函数，如 isNaN()、 parseInt()（§3.9.2）和 eval()（§4.12）\n> - 构造函数，如 Date()、 RegExp()、 String()、 Object() 和 Array()（§3.9.2）\n> - 全局对象，如 Math 和 JSON（§6.8）\n\nThe initial properties of the global object are not reserved words, but they deserve to be treated as if they are. This chapter has already described some of these global properties. Most of the others will be covered elsewhere in this book.\n\n> 全局对象的初始属性不是保留字，但是它们应该被当作保留字来对待。本章已经描述了其中一些全局属性。其余的大部分将在本书的其他地方讨论。\n\nIn Node, the global object has a property named global whose value is the global object itself, so you can always refer to the global object by the name global in Node programs.\n\n> 在 Node 中，全局对象有一个名为 global 的属性，该属性的值就是全局对象本身，因此在 Node 程序中始终可以通过名称 global 来引用全局对象。\n\nIn web browsers, the Window object serves as the global object for all JavaScript code contained in the browser window it represents. This global Window object has a self-referential window property that can be used to refer to the global object. The Window object defines the core global properties, but it also defines quite a few other globals that are specific to web browsers and client-side JavaScript. Web worker threads (§15.13) have a different global object than the Window with which they are associated. Code in a worker can refer to its global object as self.\n\n> 在 web 浏览器中，Window 对象充当它所代表的浏览器窗口中包含的所有 JavaScript 代码的全局对象。这个全局窗口对象有一个自引用窗口属性，可以用来引用全局对象。Window 对象定义了核心的全局属性，但是它也定义了一些其他的全局属性，这些全局属性是特定于 web 浏览器和客户端 JavaScript 的。Web worker 线程（§15.13）有一个与它们相关联的窗口不同的全局对象。worker 中的代码可以将其全局对象引用为 self。\n\nES2020 finally defines globalThis as the standard way to refer to the global object in any context. As of early 2020, this feature has been implemented by all modern browsers and by Node.\n\n> ES2020 最终定义了 globalThis 作为在任何上下文中引用全局对象的标准方式。到 2020 年初，所有现代浏览器和 Node 都实现了该特性。\n\n## 3.8 Immutable Primitive Values and Mutable Object References\n\nThere is a fundamental difference in JavaScript between primitive values (undefined, null, booleans, numbers, and strings) and objects (including arrays and functions). Primitives are immutable: there is no way to change (or “mutate”) a primitive value. This is obvious for numbers and booleans—it doesn’t even make sense to change the value of a number. It is not so obvious for strings, however. Since strings are like arrays of characters, you might expect to be able to alter the character at any specified index. In fact, JavaScript does not allow this, and all string methods that appear to return a modified string are, in fact, returning a new string value. For example:\n\n> JavaScript 中的原始值（undefined、null、布尔值、数字和字符串）与对象（包 括数组和函数）有着根本区别。原始值是不可更改的：任何方法都无法更改（或“突变”）一个原始值。对数字和布尔值来说显然如此——改变数字的值本身就说不通，而对字符串来说就不那么明显了，因为字符串看起来像由字符组成的数组，我们期望可以通过指定索引来修改字符串中的字符。实际上，JavaScript 是禁止这样做的。字符串中所有的方法看上去返回了一个修改后的字符串，实际上返回的 是一个新的字符串值。例如：\n\n```js\nlet s = \"hello\";   // Start with some lowercase text\ns.toUpperCase();   // Returns \"HELLO\", but doesn't alter s\ns                  // => \"hello\": the original string has not changed\n```\n\nPrimitives are also compared by value: two values are the same only if they have the same value. This sounds circular for numbers, booleans, null, and undefined: there is no other way that they could be compared. Again, however, it is not so obvious for strings. If two distinct string values are compared, JavaScript treats them as equal if, and only if, they have the same length and if the character at each index is the same.\n\n> 原始值的比较是值的比较：只有在它们的值相等时它们才相等。这对数字、布尔值、null 和 undefined 来说听起来有点儿难懂，并没有其他办法来比较它们。同样，对于字符串来说则并不明显：如果比较两个单独的字符串，当且仅当它们的长度相等且每个索引的字符都相等时，JavaScript 才认为它们相等。\n\nObjects are different than primitives. First, they are mutable—their values can change:\n\n> 对象和原始值不同，首先，它们是可变的——它们的值是可修改的：\n\n```js\nlet o = { x: 1 };  // Start with an object\no.x = 2;           // Mutate it by changing the value of a property\no.y = 3;           // Mutate it again by adding a new property\n\nlet a = [1,2,3];   // Arrays are also mutable\na[0] = 0;          // Change the value of an array element\na[3] = 4;          // Add a new array element\n```\n\nObjects are not compared by value: two distinct objects are not equal even if they have the same properties and values. And two distinct arrays are not equal even if they have the same elements in the same order:\n\n> 对象的比较并非值的比较：即使两个对象包含同样的属性及相同的值，它们也是不相等的。各个索引元素完全相等的两个数组也不相等。\n\n```js\nlet o = {x: 1}, p = {x: 1};  // Two objects with the same properties\no === p                      // => false: distinct objects are never equal\nlet a = [], b = [];          // Two distinct, empty arrays\na === b                      // => false: distinct arrays are never equal\n```\n\nObjects are sometimes called reference types to distinguish them from JavaScript’s primitive types. Using this terminology, object values are references, and we say that objects are compared by reference: two object values are the same if and only if they refer to the same underlying object.\n\n> 我们有时将对象称为引用类型（reference type），以此来和 JavaScript 的基本类型区分开来。依照术语的叫法，对象值都是引用（reference），对象的比较均是引用的比较：当且仅当它们引用同一个基对象时，它们才相等。\n\n```js\nlet a = [];   // The variable a refers to an empty array.\nlet b = a;    // Now b refers to the same array.\nb[0] = 1;     // Mutate the array referred to by variable b.\na[0]          // => 1: the change is also visible through variable a.\na === b       // => true: a and b refer to the same object, so they are equal.\n```\n\nAs you can see from this code, assigning an object (or array) to a variable simply assigns the reference: it does not create a new copy of the object. If you want to make a new copy of an object or array, you must explicitly copy the properties of the object or the elements of the array. This example demonstrates using a for loop (§5.4.3):\n\n> 就像你刚看到的如上代码，将对象（或数组）赋值给一个变量，仅仅是赋值的引用值：对象本身并没有复制一次。如果你想得到一个对象或数组的副本，则必须显式复制对象的每个属性或数组的每个元素。下面这个例子则是通过循环来完成数组复制（§5.4.3）：\n\n```js\nlet a = [\"a\",\"b\",\"c\"];              // An array we want to copy\nlet b = [];                         // A distinct array we'll copy into\nfor(let i = 0; i < a.length; i++) { // For each index of a[]\n    b[i] = a[i];                    // Copy an element of a into b\n}\nlet c = Array.from(b);              // In ES6, copy arrays with Array.from()\n```\n\nSimilarly, if we want to compare two distinct objects or arrays, we must compare their properties or elements. This code defines a function to compare two arrays:\n\n> 同样的，如果我们想比较两个单独的对象或者数组，则必须比较它们的属性或元素。下面这段代码定义了一个比较两个数组的函数：\n\n```js\nfunction equalArrays(a, b) {\n    if (a === b) return true;                // Identical arrays are equal\n    if (a.length !== b.length) return false; // Different-size arrays not equal\n    for(let i = 0; i < a.length; i++) {      // Loop through all elements\n        if (a[i] !== b[i]) return false;     // If any differ, arrays not equal\n    }\n    return true;                             // Otherwise they are equal\n}\n```\n## 3.9 Type Conversions\n\nJavaScript is very flexible about the types of values it requires. We’ve seen this for booleans: when JavaScript expects a boolean value, you may supply a value of any type, and JavaScript will convert it as needed. Some values (“truthy” values) convert to true and others (“falsy” values) convert to false. The same is true for other types: if JavaScript wants a string, it will convert whatever value you give it to a string. If JavaScript wants a number, it will try to convert the value you give it to a number (or to NaN if it cannot perform a meaningful conversion).\n\n> JavaScript 中取值所需类型非常灵活。从布尔值上就能看出这一点：当 JavaScript 需要一个布尔值时，你可以提供任何类型的值，JavaScript 会根据需要进行转换。一些值（真值）转换为 true，其他值（假值）转换为 false。这在 其他类型中同样适用：如果 JavaScript 期望使用一个字符串，它把给定的值将转换为字符串。如果 JavaScript 期望使用一个数字，它把给定的值将转换为数字（如果转换结果无意义的话将返回NaN）。\n\nSome examples:\n\n> 一些例子如下:\n\n```js\n10 + \" objects\"     // => \"10 objects\":  Number 10 converts to a string\n\"7\" * \"4\"           // => 28: both strings convert to numbers\nlet n = 1 - \"x\";    // n == NaN; string \"x\" can't convert to a number\nn + \" objects\"      // => \"NaN objects\": NaN converts to string \"NaN\"\n```\n\nTable 3-2 summarizes how values convert from one type to another in JavaScript. Bold entries in the table highlight conversions that you may find surprising. Empty cells indicate that no conversion is necessary and none is performed.\n\n> 表 3-2 简要说明了在 JavaScript 中如何进行类型转换。表 3-2 中的粗体部分突出显示了那些让你倍感意外的类型转换。空单元格表示不必要也没有执行转换。\n\nTable 3-2. JavaScript type conversions\n\n> 表 3-2 JavaScript类型转换\n\n| Value                         | to String         | to Number  | to Boolean |\n| ----------------------------- | ----------------- | ---------- | ---------- |\n| undefined                     | \"undefined\"       | NaN        | false      |\n| null                          | \"null\"            | **0**      | false      |\n| true                          | \"true\"            | **1**      |            |\n| false                         | \"false\"           | **0**      |            |\n| \"\" (empty string)             |                   | **0**      | **false**  |\n| \"1.2\" (nonempty, numeric)     |                   | 1.2        | true       |\n| \"one\" (nonempty, non-numeric) |                   | NaN        | true       |\n| 0                             | \"0\"               |            | **false**  |\n| -0                            | \"0\"               |            | **false**  |\n| 1 (finite, non-zero)          | \"1\"               |            | true       |\n| Infinity                      | \"Infinity\"        |            | true       |\n| -Infinity                     | \"-Infinity\"       |            | true       |\n| NaN                           | \"NaN\"             |            | **false**  |\n| {} (any object)               | see §3.9.3        | see §3.9.3 | true       |\n| [] (empty array)              | \"\"                | **0**      | true       |\n| [9] (one numeric element)     | \"9\"               | **9**      | true       |\n| ['a'] (any other array)       | use join() method | NaN        | true       |\n| function(){} (any function)   | see §3.9.3        | NaN        | true       |\n\nThe primitive-to-primitive conversions shown in the table are relatively straightforward. Conversion to boolean was already discussed in §3.4. Conversion to strings is well defined for all primitive values. Conversion to numbers is just a little trickier. Strings that can be parsed as numbers convert to those numbers. Leading and trailing spaces are allowed, but any leading or trailing nonspace characters that are not part of a numeric literal cause the string-to-number conversion to produce NaN. Some numeric conversions may seem surprising: true converts to 1, and false and the empty string convert to 0.\n\n> 表中提到的原始值到原始值的转换相对简单，我们已经在 §3.4 讨论过转换为布尔值的情况了。所有原始值转换为字符串的情形也已经明确定义。转换为数字的情形比较微妙。那些以数字表示的字符串可以直接转换为数字，也允许在开始和结尾处带有空格。但在开始和结尾处的任意非空格字符都不会被当成数字直接量的一部分，进而造成字符串转换为数字的结果为 NaN。有一些数字转换看起来让人奇怪：true 转换为 1，false、空字符串 ”” 转换为 0。\n\nObject-to-primitive conversion is somewhat more complicated, and it is the subject of §3.9.3.\n\n> 对象到原始值的转换要复杂一些，它是 §3.9.3 的主题。\n\n### 3.9.1 Conversions and Equality\n\nJavaScript has two operators that test whether two values are equal. The “strict equality operator,” ===, does not consider its operands to be equal if they are not of the same type, and this is almost always the right operator to use when coding. But because JavaScript is so flexible with type conversions, it also defines the == operator with a flexible definition of equality. All of the following comparisons are true, for example:\n\n> JavaScript 有两个运算符来测试两个值是否相等。“严格的相等运算符”=== 的操作数不是同一类型则不认为它们是相等的，在编码时，这几乎总是正确的运算符。但是，由于 JavaScript 在类型转换方面非常灵活，所以它还定义了灵活相等运算符 ==。下面所有的比较都是正确的，例如：\n\n```js\nnull == undefined // => true: These two values are treated as equal.\n\"0\" == 0          // => true: String converts to a number before comparing.\n0 == false        // => true: Boolean converts to number before comparing.\n\"0\" == false      // => true: Both operands convert to 0 before comparing!\n```\n\n§4.9.1 explains exactly what conversions are performed by the == operator in order to determine whether two values should be considered equal.\n\n> §4.9.1 详细说明了 == 运算符执行什么转换来确定两个值是否应该被认为相等。\n\nKeep in mind that convertibility of one value to another does not imply equality of those two values. If undefined is used where a boolean value is expected, for example, it will convert to false. But this does not mean that undefined == false. JavaScript operators and statements expect values of various types and perform conversions to those types. The if statement converts undefined to false, but the == operator never attempts to convert its operands to booleans.\n\n> 需要特别注意的是，一个值转换为另一个值并不意味着两个值相等。比如，如果在期望使用布尔值的地方使用了 undefined，它将会转换为 false，但这并不表明 undefined == false。JavaScript 运算符和语句期望使用多样化的数据类型，并可以相互转换。if 语句将 undefined 转换为 false，但“==”运算符从不试图将其操作数转换为布尔值。\n\n### 3.9.2 Explicit Conversions\n\nAlthough JavaScript performs many type conversions automatically, you may sometimes need to perform an explicit conversion, or you may prefer to make the conversions explicit to keep your code clearer.\n\n> 尽管 JavaScript 可以自动做许多类型转换，但有时仍需要做显式转换，或者为了使代码变得清晰易读而做显式转换。\n\nThe simplest way to perform an explicit type conversion is to use the Boolean(), Number(), and String() functions:\n\n> 执行显式类型转换的最简单方法是使用 Boolean()、Number() 和 String() 函数:\n\n```js\nNumber(\"3\")    // => 3\nString(false)  // => \"false\":  Or use false.toString()\nBoolean([])    // => true\n```\n\nAny value other than null or undefined has a toString() method, and the result of this method is usually the same as that returned by the String() function.\n\n> 除null或undefined之外的任何值都有toString()方法，该方法的结果通常与String()函数返回的结果相同。\n\nAs an aside, note that the Boolean(), Number(), and String() functions can also be invoked—with new—as constructor. If you use them this way, you’ll get a “wrapper” object that behaves just like a primitive boolean, number, or string value. These wrapper objects are a historical leftover from the earliest days of JavaScript, and there is never really any good reason to use them.\n\n> 顺便说一下，Boolean()、Number() 和 String() 函数可以作为构造函数调用（用 new 关键字）。如果以这种方式调用它们，就会得到一个行为类似原始布尔值、数字或字符串值的“包装器”对象。这些包装器对象是 JavaScript 早期遗留下来的，并且从来没有一个好的场景来使用他们。\n\nCertain JavaScript operators perform implicit type conversions and are sometimes used explicitly for the purpose of type conversion. If one operand of the + operator is a string, it converts the other one to a string. The unary + operator converts its operand to a number. And the unary ! operator converts its operand to a boolean and negates it. These facts lead to the following type conversion idioms that you may see in some code:\n\n> JavaScript 中的某些运算符会做隐式的类型转换，有时用于类型转换。如果“+”运算符的一个操作数是字符串，它将会把另外一个操作数转换为字符串。一元“+”运算符将其操作数转换为数字。同样，一元“！”运算符将其操作数转换为布尔值并取反。在代码中会经常见到这种类型转换的惯用法：\n\n```js\nx + \"\"   // => String(x)\n+x       // => Number(x)\nx-0      // => Number(x)\n!!x      // => Boolean(x): Note double !\n```\n\nFormatting and parsing numbers are common tasks in computer programs, and JavaScript has specialized functions and methods that provide more precise control over number-to-string and string-to-number conversions.\n\n> 在计算机程序中数字的解析和格式化是非常普通的工作，JavaScript 中提供了专门的函数和方法用来做更加精确的数字到字符串和字符串到数字的转换。\n\nThe toString() method defined by the Number class accepts an optional argument that specifies a radix, or base, for the conversion. If you do not specify the argument, the conversion is done in base 10. However, you can also convert numbers in other bases (between 2 and 36). For example:\n\n> Number 类定义的 toString() 方法可以接收表示转换基数的可选实参，如果不指定此实参，转换规则将是基于十进制。同样，亦可以将数字转换为其他进制数（范围在2～36之间），例如：\n\n```js\nlet n = 17;\nlet binary = \"0b\" + n.toString(2);  // binary == \"0b10001\"\nlet octal = \"0o\" + n.toString(8);   // octal == \"0o21\"\nlet hex = \"0x\" + n.toString(16);    // hex == \"0x11\"\n```\n\nWhen working with financial or scientific data, you may want to convert numbers to strings in ways that give you control over the number of decimal places or the number of significant digits in the output, or you may want to control whether exponential notation is used. The Number class defines three methods for these kinds of number-to-string conversions. toFixed() converts a number to a string with a specified number of digits after the decimal point. It never uses exponential notation. toExponential() converts a number to a string using exponential notation, with one digit before the decimal point and a specified number of digits after the decimal point (which means that the number of significant digits is one larger than the value you specify). toPrecision() converts a number to a string with the number of significant digits you specify. It uses exponential notation if the number of significant digits is not large enough to display the entire integer portion of the number. Note that all three methods round the trailing digits or pad with zeros as appropriate. Consider the following examples:\n\n> 当处理财务或科学数据的时候，在做数字到字符串的转换过程中，你期望自己控制输出中小数点位置和有效数字位数，或者决定是否需要指数记数法。Number 类为这种数字到字符串的类型转换场景定义了三个方法。toFixed() 根据小数点后的指定位数将数字转换为字符串，它从不使用指数记数法。toExponential() 使用指数记数法将数字转换为指数形式的字符串，其中小数点前只有一位，小数点后的位数则由参数指定（也就是说有效数字位数比指定的位数要多一位），toPrecision() 根据指定的有效数字位数将数字转换成字符串。如果有效数字的位数少于数字整数部分的位数，则转换成指数形式。我们注意到，所有三个方法都会适当地进行四舍五入或填充 0。看一下下面几个例子：\n\n```js\nlet n = 123456.789;\nn.toFixed(0)         // => \"123457\"\nn.toFixed(2)         // => \"123456.79\"\nn.toFixed(5)         // => \"123456.78900\"\nn.toExponential(1)   // => \"1.2e+5\"\nn.toExponential(3)   // => \"1.235e+5\"\nn.toPrecision(4)     // => \"1.235e+5\"\nn.toPrecision(7)     // => \"123456.8\"\nn.toPrecision(10)    // => \"123456.7890\"\n```\n\nIn addition to the number-formatting methods shown here, the Intl.NumberFormat class defines a more general, internationalized number-formatting method. See §11.7.1 for details.\n\n> 除了这里显示的数字格式方法外，Intl.NumberFormat 类定义了一个更通用的、国际化的数字格式化方法。详情见 §11.7.1。\n\nIf you pass a string to the Number() conversion function, it attempts to parse that string as an integer or floating-point literal. That function only works for base-10 integers and does not allow trailing characters that are not part of the literal. The parseInt() and parseFloat() functions (these are global functions, not methods of any class) are more flexible. parseInt() parses only integers, while parseFloat() parses both integers and floating-point numbers. If a string begins with “0x” or “0X”, parseInt() interprets it as a hexadecimal number. Both parseInt() and parseFloat() skip leading whitespace, parse as many numeric characters as they can, and ignore anything that follows. If the first nonspace character is not part of a valid numeric literal, they return NaN:\n\n> 如果通过 Number() 转换函数传入一个字符串，它会试图将其转换为一个整数或浮点数直接量，这个方法只能基于十进制数进行转换，并且不能出现非法的尾随字符。parseInt() 函数和 parseFloat() 函数（它们是全局函数，不从属于任何类的方法）更加灵活。parseInt() 只解析整数，而 parseFloat() 则可以解析整数和浮点数。如果字符串前缀是“0x”或者“0X”，parseInt() 将其解释为十六进制数，parseInt() 和 parseFloat() 都会跳过任意数量的前导空格，尽可能解析更多数值字符，并忽略后面的内容。如果第一个非空格字符是非法的数字直接量，将最终返回 NaN：\n\n```js\nparseInt(\"3 blind mice\")     // => 3\nparseFloat(\" 3.14 meters\")   // => 3.14\nparseInt(\"-12.34\")           // => -12\nparseInt(\"0xFF\")             // => 255\nparseInt(\"0xff\")             // => 255\nparseInt(\"-0XFF\")            // => -255\nparseFloat(\".1\")             // => 0.1\nparseInt(\"0.1\")              // => 0\nparseInt(\".1\")               // => NaN: integers can't start with \".\"\nparseFloat(\"$72.47\")         // => NaN: numbers can't start with \"$\"\n```\n\nparseInt() accepts an optional second argument specifying the radix (base) of the number to be parsed. Legal values are between 2 and 36. For example:\n\n> parseInt() 接受第二个可选实参，指定数字转换的基数。合法值在2到36之间。例如:\n\n```js\nparseInt(\"11\", 2)     // => 3: (1*2 + 1)\nparseInt(\"ff\", 16)    // => 255: (15*16 + 15)\nparseInt(\"zz\", 36)    // => 1295: (35*36 + 35)\nparseInt(\"077\", 8)    // => 63: (7*8 + 7)\nparseInt(\"077\", 10)   // => 77: (7*10 + 7)\n```\n### 3.9.3 Object to Primitive Conversions\n\nThe previous sections have explained how you can explicitly convert values of one type to another type and have explained JavaScript’s implicit conversions of values from one primitive type to another primitive type. This section covers the complicated rules that JavaScript uses to convert objects to primitive values. It is long and obscure, and if this is your first reading of this chapter, you should feel free to skip ahead to §3.10.\n\n> 前几节说明了如何显式地将一种类型的值转换为另一种类型，并说明了 JavaScript 如何将值从一种原始类型隐式转换为另一种原始类型。本节介绍 JavaScript 用于将对象转换为原始值的复杂规则。它很长，晦涩难懂，如果这是你第一次阅读这一章，你可以直接跳到 §3.10。\n\nOne reason for the complexity of JavaScript’s object-to-primitive conversions is that some types of objects have more than one primitive representation. Date objects, for example, can be represented as strings or as numeric timestamps. The JavaScript specification defines three fundamental algorithms for converting objects to primitive values:\n\n> JavaScript 的对象到原始值转换复杂的一个原因是某些类型的对象有不止一个原始表现。例如，日期对象可以表示为字符串或数字时间戳。JavaScript 规范定义了将对象转换为基本值的三种基本算法：\n\nprefer-string\nThis algorithm returns a primitive value, preferring a string value, if a conversion to string is possible.\n\n> 偏好字符串 prefer-string\n> 如果可以转换成字符串，这个算法会返回一个原始值，提供一个字符串。\n\nprefer-number\nThis algorithm returns a primitive value, preferring a number, if such a conversion is possible.\n\n> 偏好数字 prefer-number\n> 如果可以转换，这个算法会返回一个原始值，提供一个数字。\n\nno-preference\nThis algorithm expresses no preference about what type of primitive value is desired, and classes can define their own conversions. Of the built-in JavaScript types, all except Date implement this algorithm as prefer-number. The Date class implements this algorithm as prefer-string.\n\n> 无偏好 no-preference\n> 此算法表示不对任何类型原始值有偏好，并且可以定义自己的转换。在内置的 JavaScript 类型中，除 Date 之外，其他所有类型都将此算法实现为偏好数字。Date 类将偏好字符串作为无偏好转换算法。\n\nThe implementation of these object-to-primitive conversion algorithms is explained at the end of this section. First, however, we explain how the algorithms are used in JavaScript.\n\n> 这些对象到原语转换算法的实现将在本节的末尾进行解释。但是，我们首先解释如何在JavaScript中使用这些算法。\n\nOBJECT-TO-BOOLEAN CONVERSIONS\n\nObject-to-boolean conversions are trivial: all objects convert to true. Notice that this conversion does not require the use of the object-to-primitive algorithms described, and that it literally applies to all objects, including empty arrays and even the wrapper object new Boolean(false).\n\n> 对象到布尔的转换很简单：所有对象都转换为true。注意，这种转换不需要使用前面所描述的对象到原始值算法，它实际上适用于所有对象，包括空数组，甚至包装器对象 new Boolean(false)。\n\nOBJECT-TO-STRING CONVERSIONS\n\nWhen an object needs to be converted to a string, JavaScript first converts it to a primitive using the prefer-string algorithm, then converts the resulting primitive value to a string, if necessary, following the rules in Table 3-2.\n\n> 当一个对象需要转换为字符串时，JavaScript 首先使用 prefer-string 算法将其转换为原始值，然后根据表 3-2 的规则将得到的原语值转换为字符串。\n\nThis kind of conversion happens, for example, if you pass an object to a built-in function that expects a string argument, if you call String() as a conversion function, and when you interpolate objects into template literals (§3.3.4).\n\n> 例如，如果你将一个对象传递给一个需要 string 实参的内置函数，如果你调用 String() 作为一个转换函数，以及当你将对象插入到模板字面量（§3.3.4）时，这种转换就会发生。\n\nOBJECT-TO-NUMBER CONVERSIONS\n\nWhen an object needs to be converted to a number, JavaScript first converts it to a primitive value using the prefer-number algorithm, then converts the resulting primitive value to a number, if necessary, following the rules in Table 3-2.\n\n> 当一个对象需要转换为数字时，JavaScript首先使用prefer-number算法将其转换为一个原始值，然后根据表3-2的规则将结果原始值转换为数字。\n\nBuilt-in JavaScript functions and methods that expect numeric arguments convert object arguments to numbers in this way, and most (see the exceptions that follow) JavaScript operators that expect numeric operands convert objects to numbers in this way as well.\n\n> 期望数字实参的内置JavaScript函数和方法以这种方式将对象实参转换为数字，大多数期望数字操作数的JavaScript运算符(参见后面的异常)也以这种方式将对象转换为数字。\n\nSPECIAL CASE OPERATOR CONVERSIONS\n\nOperators are covered in detail in Chapter 4. Here, we explain the special case operators that do not use the basic object-to-string and object-to-number conversions described earlier.\n\n> 运算符将在第 4 章中详细介绍。这里，我们解释不使用前面描述的基本对象-字符串和对象-数字转换的特殊情况运算符。\n\nThe + operator in JavaScript performs numeric addition and string concatenation. If either of its operands is an object, JavaScript converts them to primitive values using the no-preference algorithm. Once it has two primitive values, it checks their types. If either argument is a string, it converts the other to a string and concatenates the strings. Otherwise, it converts both arguments to numbers and adds them.\n\n> JavaScript中的+运算符执行数字相加和字符串连接。如果它的任意一个操作数是对象，JavaScript会使用无优先级算法将它们转换为原始值。一旦它有了两个原始值，它就检查它们的类型。如果其中一个实参是字符串，它将另一个实参转换为字符串并连接字符串。否则，它将两个实参都转换为数字并相加。\n\nThe == and != operators perform equality and inequality testing in a loose way that allows type conversions. If one operand is an object and the other is a primitive value, these operators convert the object to primitive using the no-preference algorithm and then compare the two primitive values.\n\n> ==和!=运算符以一种允许类型转换的不严格方式执行相等和不相等的测试。如果一个操作数是对象，另一个是基元值，这些运算符使用无优先级算法将对象转换为基元值，然后比较两个基元值。\n\nFinally, the relational operators <, <=, >, and >= compare the order of their operands and can be used to compare both numbers and strings. If either operand is an object, it is converted to a primitive value using the prefer-number algorithm. Note, however, that unlike the object-to-number conversion, the primitive values returned by the prefer-number conversion are not then converted to numbers.\n\n> 最后，关系运算符<、<=、>和>=比较其操作数的顺序，可用于比较数字和字符串。如果其中一个操作数是对象，则使用prefer-number算法将其转换为原始值。但是，请注意，与对象到数字的转换不同，preferred -number转换返回的原始值随后不会转换为数字。\n\nNote that the numeric representation of Date objects is meaningfully comparable with < and >, but the string representation is not. For Date objects, the no-preference algorithm converts to a string, so the fact that JavaScript uses the prefer-number algorithm for these operators means that we can use them to compare the order of two Date objects.\n\n> 请注意，Date对象的数字表示可以与<和>进行有意义的比较，但字符串表示不能。对于Date对象，no-preference算法会转换为字符串，因此JavaScript对这些运算符使用prefer-number算法的事实意味着我们可以使用它们来比较两个Date对象的顺序。\n\nTHE TOSTRING() AND VALUEOF() METHODS\n\nAll objects inherit two conversion methods that are used by object-to-primitive conversions, and before we can explain the prefer-string, prefer-number, and no-preference conversion algorithms, we have to explain these two methods.\n\n> 所有对象都继承了两种用于对象到原语转换的转换方法，在解释prefer-string、prefer-number和no-preference转换算法之前，我们必须解释这两种方法。\n\nThe first method is toString(), and its job is to return a string representation of the object. The default toString() method does not return a very interesting value (though we’ll find it useful in §14.4.3):\n\n> 第一个方法是toString()，它的任务是返回对象的字符串表示。默认的toString()方法不会返回一个非常有趣的值(尽管我们会在14.4.3节中发现它很有用):\n\n```js\n({x: 1, y: 2}).toString()    // => \"[object Object]\"\n```\n\nMany classes define more specific versions of the toString() method. The toString() method of the Array class, for example, converts each array element to a string and joins the resulting strings together with commas in between. The toString() method of the Function class converts user-defined functions to strings of JavaScript source code. The Date class defines a toString() method that returns a human-readable (and JavaScript-parsable) date and time string. The RegExp class defines a toString() method that converts RegExp objects to a string that looks like a RegExp literal:\n\n> 许多类定义了toString()方法的更特定版本。例如，Array类的toString()方法将每个数组元素转换为字符串，并使用逗号将结果字符串连接在一起。函数类的toString()方法将用户定义的函数转换为JavaScript源代码的字符串。Date类定义了一个toString()方法，该方法返回一个人类可读(javascript可解析)的日期和时间字符串。RegExp类定义了一个toString()方法，该方法将RegExp对象转换为看起来像RegExp字面量的字符串:\n\n```js\n[1,2,3].toString()                  // => \"1,2,3\"\n(function(x) { f(x); }).toString()  // => \"function(x) { f(x); }\"\n/\\d+/g.toString()                   // => \"/\\\\d+/g\"\nlet d = new Date(2020,0,1);\nd.toString()  // => \"Wed Jan 01 2020 00:00:00 GMT-0800 (Pacific Standard Time)\"\n```\n\nThe other object conversion function is called valueOf(). The job of this method is less well defined: it is supposed to convert an object to a primitive value that represents the object, if any such primitive value exists. Objects are compound values, and most objects cannot really be represented by a single primitive value, so the default valueOf() method simply returns the object itself rather than returning a primitive. Wrapper classes such as String, Number, and Boolean define valueOf() methods that simply return the wrapped primitive value. Arrays, functions, and regular expressions simply inherit the default method. Calling valueOf() for instances of these types simply returns the object itself. The Date class defines a valueOf() method that returns the date in its internal representation: the number of milliseconds since January 1, 1970:\n\n> 另一个对象转换函数称为valueOf()。这个方法的工作没有很好地定义:它被假定将一个对象转换为表示该对象的原始值(如果存在这样的原始值的话)。对象是复合值，大多数对象不能真正用单个原始值表示，因此默认的valueOf()方法只是返回对象本身，而不是返回一个原始值。包装类，如String、Number和布尔值define valueOf()方法，它们只是返回包装的原语值。数组、函数和正则表达式只是继承默认方法。对这些类型的实例调用valueOf()只会返回对象本身。Date类定义了一个valueOf()方法，以其内部表示形式返回日期:\n\n```js\nlet d = new Date(2010, 0, 1);   // January 1, 2010, (Pacific time)\nd.valueOf()                     // => 1262332800000\n```\n\nOBJECT-TO-PRIMITIVE CONVERSION ALGORITHMS\n\nWith the toString() and valueOf() methods explained, we can now explain approximately how the three object-to-primitive algorithms work (the complete details are deferred until §14.4.7):\n\n> 在说明了toString()和valueOf()方法之后，我们现在可以大致解释这三种从对象到原语的算法是如何工作的(完整的细节将在§14.4.7中给出):\n\n- The prefer-string algorithm first tries the toString() method. If the method is defined and returns a primitive value, then JavaScript uses that primitive value (even if it is not a string!). If toString() does not exist or if it returns an object, then JavaScript tries the valueOf() method. If that method exists and returns a primitive value, then JavaScript uses that value. Otherwise, the conversion fails with a TypeError.\n- The prefer-number algorithm works like the prefer-string algorithm, except that it tries valueOf() first and toString() second.\n- The no-preference algorithm depends on the class of the object being converted. If the object is a Date object, then JavaScript uses the prefer-string algorithm. For any other object, JavaScript uses the prefer-number algorithm.\n\n---\n\n> - prefer-string算法首先尝试toString()方法。如果该方法被定义并返回一个原语值，那么JavaScript将使用该原语值(即使它不是字符串!)如果toString()不存在或者它返回一个对象，那么JavaScript会尝试valueOf()方法。如果该方法存在并返回一个原始值，那么JavaScript将使用该值。否则，转换将失败，并出现TypeError。\n> - prefer-number算法的工作原理与prefer-string算法相似，不同之处是它首先尝试valueOf()，然后尝试toString()。\n> - 无优先级算法取决于被转换对象的类。如果对象是Date对象，那么JavaScript使用prefer-string算法。对于任何其他对象，JavaScript都使用prefer-number算法。\n\nThe rules described here are true for all built-in JavaScript types and are the default rules for any classes you define yourself. §14.4.7 explains how you can define your own object-to-primitive conversion algorithms for the classes you define.\n\n> 这里描述的规则适用于所有内置JavaScript类型，并且是您自己定义的任何类的默认规则。§14.4.7解释了如何为你定义的类定义自己的对象到原语的转换算法。\n\nBefore we leave this topic, it is worth noting that the details of the prefer-number conversion explain why empty arrays convert to the number 0 and single-element arrays can also convert to numbers:\n\n> 在我们结束这个主题之前，值得注意的是，prefer-number转换的细节解释了为什么空数组可以转换为数字0，而单元素数组也可以转换为数字:\n\n```js\nNumber([])    // => 0: this is unexpected!\nNumber([99])  // => 99: really?\n```\n\nThe object-to-number conversion first converts the object to a primitive using the prefer-number algorithm, then converts the resulting primitive value to a number. The prefer-number algorithm tries valueOf() first and then falls back on toString(). But the Array class inherits the default valueOf() method, which does not return a primitive value. So when we try to convert an array to a number, we end up invoking the toString() method of the array. Empty arrays convert to the empty string. And the empty string converts to the number 0. An array with a single element converts to the same string that that one element does. If an array contains a single number, that number is converted to a string, and then back to a number.\n\n> 对象到数字的转换首先使用prefer-number算法将对象转换为原语，然后将结果原语值转换为数字。prefer-number算法首先尝试valueOf()，然后返回到toString()。但是Array类继承了默认的valueOf()方法，该方法不返回原语值。因此，当我们尝试将数组转换为数字时，我们最终会调用数组的toString()方法。空数组转换为空字符串。空字符串转换为数字0。具有单个元素的数组将转换为与该元素相同的字符串。如果数组包含单个数字，则该数字将被转换为字符串，然后再转换回数字。\n\n## 3.10 Variable Declaration and Assignment\n\nOne of the most fundamental techniques of computer programming is the use of names—or identifiers—to represent values. Binding a name to a value gives us a way to refer to that value and use it in the programs we write. When we do this, we typically say that we are assigning a value to a variable. The term “variable” implies that new values can be assigned: that the value associated with the variable may vary as our program runs. If we permanently assign a value to a name, then we call that name a constant instead of a variable.\n\n> 计算机编程最基本的技术之一是使用名称或标识符来表示值。将名称绑定到一个值提供了一种引用该值并在编写的程序中使用它的方法。当我们这样做的时候，我们通常说我们给一个变量赋值。术语“变量”意味着可以分配新值:与变量相关的值可能随着程序运行而变化。如果我们将一个值永久地赋给一个名称，那么我们将该名称称为常量而不是变量。\n\nBefore you can use a variable or constant in a JavaScript program, you must declare it. In ES6 and later, this is done with the let and const keywords, which we explain next. Prior to ES6, variables were declared with var, which is more idiosyncratic and is explained later on in this section.\n\n> 在JavaScript程序中使用变量或常量之前，必须声明它。在ES6及以后的版本中，这是通过let和const关键字来完成的，我们将在下文中解释。在ES6之前，变量是用var声明的，var更特殊，在本节后面解释。\n\n### 3.10.1 Declarations with let and const\n\nIn modern JavaScript (ES6 and later), variables are declared with the let keyword, like this:\n\n> 在现代JavaScript (ES6及以后的版本)中，变量是用let关键字声明的，就像这样:\n\n```js\nlet i;\nlet sum;\n```\n\nYou can also declare multiple variables in a single let statement:\n\n> 你也可以在一个let语句中声明多个变量:\n\n```js\nlet i, sum;\n```\n\nIt is a good programming practice to assign an initial value to your variables when you declare them, when this is possible:\n\n> 在声明变量时，给它们赋一个初始值是一个很好的编程实践，这是可能的:\n\n```js\nlet message = \"hello\";\nlet i = 0, j = 0, k = 0;\nlet x = 2, y = x*x; // Initializers can use previously declared variables\n```\n\nIf you don’t specify an initial value for a variable with the let statement, the variable is declared, but its value is undefined until your code assigns a value to it.\n\n> 如果没有使用let语句为变量指定初始值，则声明变量，但在代码为其赋值之前，变量的值是未定义的。\n\nTo declare a constant instead of a variable, use const instead of let. const works just like let except that you must initialize the constant when you declare it:\n\n> 要声明常量而不是变量，请使用const而不是let。const的工作方式与let类似，只是在声明常量时必须初始化它:\n\n```js\nconst H0 = 74;         // Hubble constant (km/s/Mpc)\nconst C = 299792.458;  // Speed of light in a vacuum (km/s)\nconst AU = 1.496E8;    // Astronomical Unit: distance to the sun (km)\n```\n\nAs the name implies, constants cannot have their values changed, and any attempt to do so causes a TypeError to be thrown.\n\n> 顾名思义，常量不能改变它们的值，任何这样做的尝试都会引发TypeError。\n\nIt is a common (but not universal) convention to declare constants using names with all capital letters such as H0 or HTTP_NOT_FOUND as a way to distinguish them from variables.\n\n> 通常(但不是通用的)约定是使用全大写字母(如H0或HTTP_NOT_FOUND)的名称来声明常量，以将它们与变量区分开来。\n\nWHEN TO USE CONST\n\nThere are two schools of thought about the use of the const keyword. One approach is to use const only for values that are fundamentally unchanging, like the physical constants shown, or program version numbers, or byte sequences used to identify file types, for example. Another approach recognizes that many of the so-called variables in our program don’t actually ever change as our program runs. In this approach, we declare everything with const, and then if we find that we do actually want to allow the value to vary, we switch the declaration to let. This may help prevent bugs by ruling out accidental changes to variables that we did not intend.\n\n> 关于const关键字的使用有两种思想流派。一种方法是仅对基本不变的值使用const，例如所示的物理常量、程序版本号或用于标识文件类型的字节序列。另一种方法认为，程序中许多所谓的变量实际上不会随着程序的运行而改变。在这种方法中，我们用const声明所有内容，然后如果发现我们确实希望允许值变化，我们将声明切换为let。这可以通过排除我们无意中对变量的意外更改来防止错误。\n\nIn one approach, we use const only for values that must not change. In the other, we use const for any value that does not happen to change. I prefer the former approach in my own code.\n\n> 在一种方法中，我们只对不能改变的值使用const。在另一种情况下，对于任何不会发生变化的值都使用const。在我自己的代码中，我更喜欢前一种方法。\n\nIn Chapter 5, we’ll learn about the for, for/in, and for/of loop statements in JavaScript. Each of these loops includes a loop variable that gets a new value assigned to it on each iteration of the loop. JavaScript allows us to declare the loop variable as part of the loop syntax itself, and this is another common way to use let:\n\n> 在第五章，我们将学习JavaScript中的for、for/ In和for/of循环语句。每个循环都包含一个循环变量，该变量在循环的每次迭代中都赋给它一个新值。JavaScript允许我们将循环变量声明为循环语法本身的一部分，这是使用let的另一种常见方式:\n\n```js\nfor(let i = 0, len = data.length; i < len; i++) console.log(data[i]);\nfor(let datum of data) console.log(datum);\nfor(let property in object) console.log(property);\n```\n\nIt may seem surprising, but you can also use const to declare the loop “variables” for for/in and for/of loops, as long as the body of the loop does not reassign a new value. In this case, the const declaration is just saying that the value is constant for the duration of one loop iteration:\n\n> 你可能会觉得奇怪，但是你也可以使用const来声明for/in和for/of循环的循环“变量”，只要循环体不重新赋值。在这种情况下，const声明只是表示该值在循环迭代的持续时间内为常量:\n\n```js\nfor(const datum of data) console.log(datum);\nfor(const property in object) console.log(property);\n```\n\nVARIABLE AND CONSTANT SCOPE\n\nThe scope of a variable is the region of your program source code in which it is defined. Variables and constants declared with let and const are block scoped. This means that they are only defined within the block of code in which the let or const statement appears. JavaScript class and function definitions are blocks, and so are the bodies of if/else statements, while loops, for loops, and so on. Roughly speaking, if a variable or constant is declared within a set of curly braces, then those curly braces delimit the region of code in which the variable or constant is defined (though of course it is not legal to reference a variable or constant from lines of code that execute before the let or const statement that declares the variable). Variables and constants declared as part of a for, for/in, or for/of loop have the loop body as their scope, even though they technically appear outside of the curly braces.\n\n> 变量的范围是定义变量的程序源代码的区域。用let和const声明的变量和常量是块作用域的。这意味着它们只在出现let或const语句的代码块中定义。JavaScript类和函数定义都是块，if/else语句体、while循环体、for循环体等等也是块。粗略地说,如果一个变量或常量声明一组花括号内,那么这些花括号划定的地区代码定义的变量或常数(当然这不是法律引用一个变量或常量的代码执行前让声明变量或常量声明)。作为for、for/in或for/of循环的一部分声明的变量和常量都有循环体作为它们的作用域，即使它们在技术上出现在花括号之外。\n\nWhen a declaration appears at the top level, outside of any code blocks, we say it is a global variable or constant and has global scope. In Node and in client-side JavaScript modules (see Chapter 10), the scope of a global variable is the file that it is defined in. In traditional client-side JavaScript, however, the scope of a global variable is the HTML document in which it is defined. That is: if one `<script>` declares a global variable or constant, that variable or constant is defined in all of the `<script>` elements in that document (or at least all of the scripts that execute after the let or const statement executes).\n\n> 当声明出现在顶层，在任何代码块之外时，我们说它是一个全局变量或常量，具有全局作用域。在Node和客户端JavaScript模块中(见第 10 章)，全局变量的作用域是定义它的文件。然而，在传统的客户端JavaScript中，全局变量的作用域是定义它的HTML文档。也就是说:如果一个 `<script>` 声明一个全局变量或常量，该变量或常量定义在该文档中的所有 `<script>` 该文档中的元素(或至少在let或const语句之后执行的所有脚本)。\n\nREPEATED DECLARATIONS\n\nIt is a syntax error to use the same name with more than one let or const declaration in the same scope. It is legal (though a practice best avoided) to declare a new variable with the same name in a nested scope:\n\n> 在同一个作用域中对多个let或const声明使用相同的名称是一个语法错\n\n```js\nconst x = 1;        // Declare x as a global constant\nif (x === 1) {\n    let x = 2;      // Inside a block x can refer to a different value\n    console.log(x); // Prints 2\n}\nconsole.log(x);     // Prints 1: we're back in the global scope now\nlet x = 3;          // ERROR! Syntax error trying to re-declare x\n```\nDECLARATIONS AND TYPES\n\nIf you’re used to statically typed languages such as C or Java, you may think that the primary purpose of variable declarations is to specify the type of values that may be assigned to a variable. But, as you have seen, there is no type associated with JavaScript’s variable declarations.2 A JavaScript variable can hold a value of any type. For example, it is perfectly legal (but generally poor programming style) in JavaScript to assign a number to a variable and then later assign a string to that variable:\n\n> 如果您习惯了静态类型语言，比如C或Java，那么您可能认为变量声明的主要目的是指定可以赋给变量的值的类型。但是，如您所见，没有与JavaScript的变量声明相关联的类型。一个JavaScript变量可以保存任何类型的值。例如，在JavaScript中，先给变量赋一个数字，然后再给该变量赋一个字符串，这是完全合法的(但通常是糟糕的编程风格):\n\n```js\nlet i = 10;\ni = \"ten\";\n```\n### 3.10.2 Variable Declarations with var\n\nIn versions of JavaScript before ES6, the only way to declare a variable is with the var keyword, and there is no way to declare constants. The syntax of var is just like the syntax of let:\n\n> 在ES6之前的JavaScript版本中，声明变量的唯一方法是使用var关键字，而没有方法声明常量。var的语法就像let的语法一样:\n\n```js\nvar x;\nvar data = [], count = data.length;\nfor(var i = 0; i < count; i++) console.log(data[i]);\n```\n\nAlthough var and let have the same syntax, there are important differences in the way they work:\n\n> 虽然var和let有相同的语法，但它们的工作方式有重要的不同:\n\n- Variables declared with var do not have block scope. Instead, they are scoped to the body of the containing function no matter how deeply nested they are inside that function.\n- If you use var outside of a function body, it declares a global variable. But global variables declared with var differ from globals declared with let in an important way. Globals declared with var are implemented as properties of the global object (§3.7). The global object can be referenced as globalThis. So if you write var x = 2; outside of a function, it is like you wrote globalThis.x = 2;. Note however, that the analogy is not perfect: the properties created with global var declarations cannot be deleted with the delete operator (§4.13.4). Global variables and constants declared with let and const are not properties of the global object.\n- Unlike variables declared with let, it is legal to declare the same variable multiple times with var. And because var variables have function scope instead of block scope, it is actually common to do this kind of redeclaration. The variable i is frequently used for integer values, and especially as the index variable of for loops. In a function with multiple for loops, it is typical for each one to begin for(var i = 0; .... Because var does not scope these variables to the loop body, each of these loops is (harmlessly) re-declaring and re-initializing the same variable.\n- One of the most unusual features of var declarations is known as hoisting. When a variable is declared with var, the declaration is lifted up (or “hoisted”) to the top of the enclosing function. The initialization of the variable remains where you wrote it, but the definition of the variable moves to the top of the function. So variables declared with var can be used, without error, anywhere in the enclosing function. If the initialization code has not run yet, then the value of the variable may be undefined, but you won’t get an error if you use the variable before it is initialized. (This can be a source of bugs and is one of the important misfeatures that let corrects: if you declare a variable with let but attempt to use it before the let statement runs, you will get an actual error instead of just seeing an undefined value.)\n\n---\n\n> - 用var声明的变量没有块作用域。相反，它们的作用域是包含函数的主体，无论它们嵌套在函数内部有多深。\n> - 如果你在函数体之外使用var，它会声明一个全局变量。但是用var声明的全局变量与用let声明的全局变量在一个重要的方面不同。用var声明的全局变量是作为全局对象的属性实现的(§3.7)。全局对象可以引用为globalThis。如果你写var x = 2;在函数之外，它就像你写的globalThis。x = 2,。但是注意，这个类比并不完美:用全局var声明创建的属性不能用delete运算符删除(§4.13.4)。用let和const声明的全局变量和常量不是全局对象的属性。\n> - 与用let声明的变量不同，用var多次声明同一个变量是合法的。而且因为var变量具有函数作用域而不是块作用域，所以这种类型的重声明实际上是很常见的。变量i经常被用来表示整数值，特别是作为for循环的索引变量。在有多个for循环的函数中，每个循环都以for(var i = 0;…因为var没有将这些变量作用域限定在循环体中，所以每个循环都(无害地)重新声明并初始化同一个变量。\n> - var声明最不寻常的特性之一就是吊装。当用var声明一个变量时，这个声明会被提升(或“悬挂”)到外围函数的顶部。变量的初始化保持在编写它的地方，但变量的定义移到函数的顶部。因此，用var声明的变量可以在外围函数的任何位置使用，不会出错。如果初始化代码还没有运行，那么变量的值可能是未定义的，但如果在变量初始化之前使用它，则不会出现错误。(这可能是bug的来源，也是let纠正的重要错误特性之一:如果你用let声明了一个变量，但试图在let语句运行之前使用它，你将得到一个实际的错误，而不仅仅是看到一个未定义的值。)\n\nUSING UNDECLARED VARIABLES\n\nIn strict mode (§5.6.3), if you attempt to use an undeclared variable, you’ll get a reference error when you run your code. Outside of strict mode, however, if you assign a value to a name that has not been declared with let, const, or var, you’ll end up creating a new global variable. It will be a global no matter now deeply nested within functions and blocks your code is, which is almost certainly not what you want, is bug-prone, and is one of the best reasons for using strict mode!\n\n> 在严格模式(§5.6.3)，如果你试图使用一个未声明的变量，你会得到一个引用错误，当你运行你的代码。但是，在严格模式之外，如果您将一个值赋给一个没有使用let、const或var声明的名称，那么您最终将创建一个新的全局变量。它将是全局的，无论你的代码是嵌套在函数和块中，这几乎肯定不是你想要的，它是容易出错的，这是使用严格模式的最佳原因之一!\n\nGlobal variables created in this accidental way are like global variables declared with var: they define properties of the global object. But unlike the properties defined by proper var declarations, these properties can be deleted with the delete operator (§4.13.4).\n\n> 以这种偶然方式创建的全局变量与用var声明的全局变量类似:它们定义全局对象的属性。但与正确的var声明定义的属性不同，这些属性可以通过delete运算符删除(§4.13.4)。\n\n### 3.10.3 Destructuring Assignment\n\nES6 implements a kind of compound declaration and assignment syntax known as destructuring assignment. In a destructuring assignment, the value on the righthand side of the equals sign is an array or object (a “structured” value), and the lefthand side specifies one or more variable names using a syntax that mimics array and object literal syntax. When a destructuring assignment occurs, one or more values are extracted (“destructured”) from the value on the right and stored into the variables named on the left. Destructuring assignment is perhaps most commonly used to initialize variables as part of a const, let, or var declaration statement, but it can also be done in regular assignment expressions (with variables that have already been declared). And, as we’ll see in §8.3.5, destructuring can also be used when defining the parameters to a function.\n\n> ES6 实现了一种复合声明和赋值语法，称为解构赋值。在解构赋值中，等号右边的值是一个数组或对象（“结构化的”值），左边使用模仿数组和对象字面量语法的语法指定一个或多个变量名。当发生解构赋值时，将从右边的值中提取一个或多个值（“解构”），并存储到左边命名的变量中。解构赋值可能常用来初始化 const、let 或 var 的声明，但它也可以用在正则赋值表达式中（使用已经声明的变量）。而且，我们将在 §8.3.5 中看到的，解构也可以用于定义函数的形参。\n\nHere are simple destructuring assignments using arrays of values:\n\n> 下面是使用数组值的简单解构赋值：\n\n```js\nlet [x,y] = [1,2];  // Same as let x=1, y=2\n[x,y] = [x+1,y+1];  // Same as x = x + 1, y = y + 1\n[x,y] = [y,x];      // Swap the value of the two variables\n[x,y]               // => [3,2]: the incremented and swapped values\n```\n\nNotice how destructuring assignment makes it easy to work with functions that return arrays of values:\n\n> 请注意，解构赋值使返回数组值的函数变得很容易：\n\n```js\n// Convert [x,y] coordinates to [r,theta] polar coordinates\nfunction toPolar(x, y) {\n    return [Math.sqrt(x*x+y*y), Math.atan2(y,x)];\n}\n\n// Convert polar to Cartesian coordinates\nfunction toCartesian(r, theta) {\n    return [r*Math.cos(theta), r*Math.sin(theta)];\n}\n\nlet [r,theta] = toPolar(1.0, 1.0);  // r == Math.sqrt(2); theta == Math.PI/4\nlet [x,y] = toCartesian(r,theta);   // [x, y] == [1.0, 1,0]\n```\n\nWe saw that variables and constants can be declared as part of JavaScript’s various for loops. It is possible to use variable destructuring in this context as well. Here is a code that loops over the name/value pairs of all properties of an object and uses destructuring assignment to convert those pairs from two-element arrays into individual variables:\n\n> 我们看到变量和常量可以声明为 JavaScript 的各种 for 循环的一部分。在这个上下文中也可以使用变量解构。下面的代码循环遍历对象的所有属性的名称/值对，并使用解构赋值将这些对从两个元素数组转换为单独的变量：\n\n```js\nlet o = { x: 1, y: 2 }; // The object we'll loop over\nfor(const [name, value] of Object.entries(o)) {\n    console.log(name, value); // Prints \"x 1\" and \"y 2\"\n}\n```\n\nThe number of variables on the left of a destructuring assignment does not have to match the number of array elements on the right. Extra variables on the left are set to undefined, and extra values on the right are ignored. The list of variables on the left can include extra commas to skip certain values on the right:\n\n> 解构赋值左边的变量数量不必与右边的数组元素数量相匹配。左边的额外变量被设置为 undefined，右边的额外值被忽略。左边的变量列表可以包含额外的逗号，以跳过右边的某些值：\n\n```js\nlet [x,y] = [1];     // x == 1; y == undefined\n[x,y] = [1,2,3];     // x == 1; y == 2\n[,x,,y] = [1,2,3,4]; // x == 2; y == 4\n```\n\nIf you want to collect all unused or remaining values into a single variable when destructuring an array, use three dots (...) before the last variable name on the left-hand side:\n\n> 如果你想在解构数组时将所有未使用的或剩余的值收集到一个变量中，在左边最后一个变量名之前使用三个点（…）：\n\n```js\nlet [x, ...y] = [1,2,3,4];  // y == [2,3,4]\n```\n\nWe’ll see three dots used this way again in §8.3.2, where they are used to indicate that all remaining function arguments should be collected into a single array.\n\n> 在 §8.3.2 中，我们将再次看到以这种方式使用的三个点，它们用于指示所有剩余的函数实参收集到一个数组中。\n\nDestructuring assignment can be used with nested arrays. In this case, the lefthand side of the assignment should look like a nested array literal:\n\n> 解构赋值可用于嵌套数组。在这种情况下，赋值的左边应该看起来像一个嵌套的数组字面量：\n\n```js\nlet [a, [b, c]] = [1, [2,2.5], 3]; // a == 1; b == 2; c == 2.5\n```\n\nA powerful feature of array destructuring is that it does not actually require an array! You can use any iterable object (Chapter 12) on the righthand side of the assignment; any object that can be used with a for/of loop (§5.4.4) can also be destructured:\n\n> 数组解构的一个强大特性是它实际上不必须是个数组！你可以在赋值语句的右侧使用任何可迭代对象（第 12 章）;任何可以与 for/of 循环一起使用的对象（§5.4.4）也可以被解构:\n\n```js\nlet [first, ...rest] = \"Hello\"; // first == \"H\"; rest == [\"e\",\"l\",\"l\",\"o\"]\n```\n\nDestructuring assignment can also be performed when the righthand side is an object value. In this case, the lefthand side of the assignment looks something like an object literal: a comma-separated list of variable names within curly braces:\n\n> 当右边是对象值时，也可以执行解构赋值。在这种情况下，赋值的左边看起来像一个对象字面量：花括号包含用逗号分隔的变量名列表：\n\n```js\nlet transparent = {r: 0.0, g: 0.0, b: 0.0, a: 1.0}; // A RGBA color\nlet {r, g, b} = transparent;  // r == 0.0; g == 0.0; b == 0.0\n```\n\nThe next example copies global functions of the Math object into variables, which might simplify code that does a lot of trigonometry:\n\n> 下一个示例将 Math 对象的全局函数复制到变量中，这可能会简化执行大量三角函数的代码：\n\n```js\n// Same as const sin=Math.sin, cos=Math.cos, tan=Math.tan\nconst {sin, cos, tan} = Math;\n```\n\nNotice in the code here that the Math object has many properties other than the three that are destructured into individual variables. Those that are not named are simply ignored. If the lefthand side of this assignment had included a variable whose name was not a property of Math, that variable would simply be assigned undefined.\n\n> 请注意，在这里的代码中，Math 对象有许多属性，而不是被解构为单个变量的三个属性。那些没有被命名的就被忽略了。如果这个赋值的左边包含了一个名称不是 Math 属性的变量，那么这个变量的赋值将是 undefined。\n\nIn each of these object destructuring examples, we have chosen variable names that match the property names of the object we’re destructuring. This keeps the syntax simple and easy to understand, but it is not required. Each of the identifiers on the lefthand side of an object destructuring assignment can also be a colon-separated pair of identifiers, where the first is the name of the property whose value is to be assigned and the second is the name of the variable to assign it to:\n\n> 在每个对象解构示例中，我们都选择了与要解构的对象的属性名匹配的变量名。这使语法保持简单和易于理解，但这不是必需的。每个对象解构赋值也可以用冒号分割的一对标识符，冒号左边是属性的名字，冒号右边是被赋值的变量名：\n\n```js\n// Same as const cosine = Math.cos, tangent = Math.tan;\nconst { cos: cosine, tan: tangent } = Math;\n```\n\nI find that object destructuring syntax becomes too complicated to be useful when the variable names and property names are not the same, and I tend to avoid the shorthand in this case. If you choose to use it, remember that property names are always on the left of the colon, in both object literals and on the left of an object destructuring assignment.\n\n> 这样写对象解构语法变得太复杂，但我发现，当变量名和属性名不相同时就非常有用，在这种情况下我倾向于避免使用简写。如果选择使用它，请记住无论是对象字面量还是对象解构赋值，属性名总是在冒号的左边。\n\nDestructuring assignment becomes even more complicated when it is used with nested objects, or arrays of objects, or objects of arrays, but it is legal:\n\n> 当与嵌套对象、对象数组或数组对象解构赋值时会变得更加复杂，但它是合法的：\n\n```js\nlet points = [{x: 1, y: 2}, {x: 3, y: 4}];     // An array of two point objects\nlet [{x: x1, y: y1}, {x: x2, y: y2}] = points; // destructured into 4 variables.\n(x1 === 1 && y1 === 2 && x2 === 3 && y2 === 4) // => true\n```\n\nOr, instead of destructuring an array of objects, we could destructure an object of arrays:\n\n> 或者，我们可以对数组对象进行解构，而不是对数组对象进行解构：\n\n```js\nlet points = { p1: [1,2], p2: [3,4] };         // An object with 2 array props\nlet { p1: [x1, y1], p2: [x2, y2] } = points;   // destructured into 4 vars\n(x1 === 1 && y1 === 2 && x2 === 3 && y2 === 4) // => true\n```\n\nComplex destructuring syntax like this can be hard to write and hard to read, and you may be better off just writing out your assignments explicitly with traditional code like `let x1 = points.p1[0];`.\n\n> 像这样复杂的解构语法可能很难写，也很难读，最好使用传统的代码，如`let x1 = points.p1[0];`来显式地写出你的赋值。\n\nUNDERSTANDING COMPLEX DESTRUCTURING\n\n> **理解复杂的解构**\n\nIf you find yourself working with code that uses complex destructuring assignments, there is a useful regularity that can help you make sense of the complex cases. Think first about a regular (single-value) assignment. After the assignment is done, you can take the variable name from the lefthand side of the assignment and use it as an expression in your code, where it will evaluate to whatever value you assigned it. The same thing is true for destructuring assignment. The lefthand side of a destructuring assignment looks like an array literal or an object literal (§6.2.1 and §6.10). After the assignment has been done, the lefthand side will actually work as a valid array literal or object literal elsewhere in your code. You can check that you’ve written a destructuring assignment correctly by trying to use the lefthand side on the righthand side of another assignment expression:\n\n> 如果发现自己正在处理使用复杂解构赋值的代码，那么有一个有用的规则可以帮助您理解复杂的情况。首先想象常规（单值）赋值。在赋值完成后，可以从赋值的左边取变量名，并在代码中使用它作为表达式，它将计算赋给它的任何值。对于解构赋值也是一样的。解构赋值的左边看起来像数组字面量或对象字面量（§6.2.1 和 §6.10）。赋值完成后，左边将在代码的任何地方作为有效的数组字面量或对象字面量。你可以通过尝试在另一个赋值表达式的右边使用解构赋值表达式的左边来检查你是否正确地编写了一个解构赋值：\n\n```js\n// Start with a data structure and a complex destructuring\nlet points = [{x: 1, y: 2}, {x: 3, y: 4}];\nlet [{x: x1, y: y1}, {x: x2, y: y2}] = points;\n\n// Check your destructuring syntax by flipping the assignment around\nlet points2 = [{x: x1, y: y1}, {x: x2, y: y2}]; // points2 == points\n```\n## 3.11 Summary\n\nSome key points to remember about this chapter:\n\n> 关于本章需要记住的一些要点:\n\n- How to write and manipulate numbers and strings of text in JavaScript.\n- How to work with JavaScript’s other primitive types: booleans, Symbols, null, and undefined.\n- The differences between immutable primitive types and mutable reference types.\n- How JavaScript converts values implicitly from one type to another and how you can do so explicitly in your programs.\n- How to declare and initialize constants and variables (including with destructuring assignment) and the lexical scope of the variables and constants you declare.\n\n---\n\n> - 如何在JavaScript中编写和操作数字和文本字符串。\n> - 如何使用JavaScript的其他原始类型:布尔型，符号，空，和未定义。\n> - 不可变基元类型和可变引用类型的区别。\n> - JavaScript如何隐式地将值从一种类型转换为另一种类型，以及你如何在你的程序中显式地这样做。\n> - 如何声明和初始化常量和变量(包括解构赋值)和变量和常量的词法范围你声明。\n\n---\n\n1. This is the format for numbers of type double in Java, C++, and most modern programming languages.\n2. There are JavaScript extensions, such as TypeScript and Flow (§17.8), that allow types to be specified as part of variable declarations with syntax like let x: number = 0;.\n"
  },
  {
    "path": "content/posts/ch4.md",
    "content": "---\ntitle: \"第 4 章 表达式和运算符\"\ndate: 2020-11-02T22:18:39+08:00\n---\n\nThis chapter documents JavaScript expressions and the operators with which many of those expressions are built. An expression is a phrase of JavaScript that can be evaluated to produce a value. A constant embedded literally in your program is a very simple kind of expression. A variable name is also a simple expression that evaluates to whatever value has been assigned to that variable. Complex expressions are built from simpler expressions. An array access expression, for example, consists of one expression that evaluates to an array followed by an open square bracket, an expression that evaluates to an integer, and a close square bracket. This new, more complex expression evaluates to the value stored at the specified index of the specified array. Similarly, a function invocation expression consists of one expression that evaluates to a function object and zero or more additional expressions that are used as the arguments to the function.\n\n> 本章介绍了JavaScript表达式和用于构建这些表达式的运算符。表达式是JavaScript的一个短语，可以计算它来产生一个值。在程序中嵌入的常量是一种非常简单的表达式。变量名也是一个简单的表达式，计算结果为分配给该变量的任何值。复杂的表达式是由简单的表达式构建的。例如，数组访问表达式由一个计算结果为数组的表达式、一个计算结果为整数的表达式和一个右方括号组成。这个新的更复杂的表达式计算为存储在指定数组的指定索引处的值。类似地，函数调用表达式由计算为函数对象的一个表达式和用作函数参数的零个或多个附加表达式组成。\n\nThe most common way to build a complex expression out of simpler expressions is with an operator. An operator combines the values of its operands (usually two of them) in some way and evaluates to a new value. The multiplication operator * is a simple example. The expression x * y evaluates to the product of the values of the expressions x and y. For simplicity, we sometimes say that an operator returns a value rather than “evaluates to” a value.\n\n> 从较简单的表达式构建复杂表达式的最常见方法是使用运算符。运算符以某种方式组合其操作数的值(通常是两个)并计算为一个新值。乘法运算符*是一个简单的示例。表达式x * y的值是表达式x和y的值的乘积。为了简单起见，我们有时说运算符返回一个值，而不是“求值”一个值。\n\nThis chapter documents all of JavaScript’s operators, and it also explains expressions (such as array indexing and function invocation) that do not use operators. If you already know another programming language that uses C-style syntax, you’ll find that the syntax of most of JavaScript’s expressions and operators is already familiar to you.\n\n> 这一章记录了所有的JavaScript运算符，也解释了不使用运算符的表达式(比如数组索引和函数调用)。如果您已经知道另一种使用c风格语法的编程语言，那么您会发现JavaScript的大多数表达式和运算符的语法对您来说已经很熟悉了。\n\n## 4.1 Primary Expressions\n\nThe simplest expressions, known as primary expressions, are those that stand alone—they do not include any simpler expressions. Primary expressions in JavaScript are constant or literal values, certain language keywords, and variable references.\n\n> 最简单的表达式被称为基本表达式，是那些独立的表达式——它们不包括任何更简单的表达式。JavaScript中的主表达式是常量或文字值、某些语言关键字和变量引用。\n\nLiterals are constant values that are embedded directly in your program. They look like these:\n\n> 文本是直接嵌入到程序中的常数值。它们看起来是这样的:\n\n```js\n1.23         // A number literal\n\"hello\"      // A string literal\n/pattern/    // A regular expression literal\n```\n\nJavaScript syntax for number literals was covered in §3.2. String literals were documented in §3.3. The regular expression literal syntax was introduced in §3.3.5 and will be documented in detail in §11.3.\n\n> 在§3.2中介绍了用于数字文字的JavaScript语法。在§3.3中记录了字符串文字。正则表达式字面语法是在§3.3.5中介绍的，并将在§11.3中详细说明。\n\nSome of JavaScript’s reserved words are primary expressions:\n\n> 一些JavaScript的保留字是主要的表达式:\n\n```js\ntrue       // Evalutes to the boolean true value\nfalse      // Evaluates to the boolean false value\nnull       // Evaluates to the null value\nthis       // Evaluates to the \"current\" object\n```\n\nWe learned about true, false, and null in §3.4 and §3.5. Unlike the other keywords, this is not a constant—it evaluates to different values in different places in the program. The this keyword is used in object-oriented programming. Within the body of a method, this evaluates to the object on which the method was invoked. See §4.5, Chapter 8 (especially §8.2.2), and Chapter 9 for more on this.\n\n> 我们在§3.4和§3.5中学习了真、假和空。与其他关键字不同，这不是常量—它在程序的不同位置计算不同的值。关键字this用于面向对象编程。在方法体中，这将计算为调用该方法的对象。请参阅§4.5，第 8 章(特别是§8.2.2)和第 9 章来了解更多这方面的内容。\n\nFinally, the third type of primary expression is a reference to a variable, constant, or property of the global object:\n\n> 最后，第三种类型的主表达式是对全局对象的变量、常量或属性的引用:\n\n```js\ni             // Evaluates to the value of the variable i.\nsum           // Evaluates to the value of the variable sum.\nundefined     // The value of the \"undefined\" property of the global object\n```\n\nWhen any identifier appears by itself in a program, JavaScript assumes it is a variable or constant or property of the global object and looks up its value. If no variable with that name exists, an attempt to evaluate a nonexistent variable throws a ReferenceError instead.\n\n\n> 当任何标识符在程序中单独出现时，JavaScript假定它是全局对象的变量、常量或属性，并查找其值。如果不存在具有该名称的变量，则尝试对不存在的变量求值会抛出一个ReferenceError。\n## 4.2 Object and Array Initializers\n\nObject and array initializers are expressions whose value is a newly created object or array. These initializer expressions are sometimes called object literals and array literals. Unlike true literals, however, they are not primary expressions, because they include a number of subexpressions that specify property and element values. Array initializers have a slightly simpler syntax, and we’ll begin with those.\n\n> 对象和数组初始化器是其值为新创建的对象或数组的表达式。这些初始化器表达式有时被称为对象字面量和数组字面量。然而，与真正的字面值不同，它们不是主表达式，因为它们包括许多指定属性和元素值的子表达式。数组初始化器的语法稍微简单一些，我们将从这些开始。\n\nAn array initializer is a comma-separated list of expressions contained within square brackets. The value of an array initializer is a newly created array. The elements of this new array are initialized to the values of the comma-separated expressions:\n\n> 数组初始化器是包含在方括号内的以逗号分隔的表达式列表。数组初始化器的值是一个新创建的数组。这个新数组的元素被初始化为逗号分隔的表达式的值:\n\n```js\n[]         // An empty array: no expressions inside brackets means no elements\n[1+2,3+4]  // A 2-element array.  First element is 3, second is 7\n```\n\nThe element expressions in an array initializer can themselves be array initializers, which means that these expressions can create nested arrays:\n\n> 数组初始化器中的元素表达式本身可以是数组初始化器，这意味着这些表达式可以创建嵌套数组:\n\n```js\nlet matrix = [[1,2,3], [4,5,6], [7,8,9]];\n```\n\nThe element expressions in an array initializer are evaluated each time the array initializer is evaluated. This means that the value of an array initializer expression may be different each time it is evaluated.\n\n> 数组初始化式中的元素表达式在每次求值数组初始化式时都会求值。这意味着数组初始化器表达式的值在每次求值时可能不同。\n\nUndefined elements can be included in an array literal by simply omitting a value between commas. For example, the following array contains five elements, including three undefined elements:\n\n> 只需在逗号之间省略一个值，就可以将未定义的元素包含在数组字面量中。例如，下面的数组包含5个元素，其中3个是未定义的元素:\n\n```js\nlet sparseArray = [1,,,,5];\n```\n\nA single trailing comma is allowed after the last expression in an array initializer and does not create an undefined element. However, any array access expression for an index after that of the last expression will necessarily evaluate to undefined.\n\n> 在数组初始化器的最后一个表达式后面可以使用一个逗号，并且不会创建未定义的元素。然而，在最后一个表达式的索引之后的任何数组访问表达式都必须求值为undefined。\n\nObject initializer expressions are like array initializer expressions, but the square brackets are replaced by curly brackets, and each subexpression is prefixed with a property name and a colon:\n\n> 对象初始化器表达式与数组初始化器表达式类似，但方括号被花括号替换，每个子表达式前面都有属性名和冒号:\n\n```js\nlet p = { x: 2.3, y: -1.2 };  // An object with 2 properties\nlet q = {};                   // An empty object with no properties\nq.x = 2.3; q.y = -1.2;        // Now q has the same properties as p\n```\n\nIn ES6, object literals have a much more feature-rich syntax (you can find details in §6.10). Object literals can be nested. For example:\n\n> 在ES6中，对象字面量有更丰富的语法特性(你可以在§6.10中找到细节)。对象字面量可以嵌套。例如:\n\n```js\nlet rectangle = {\n    upperLeft: { x: 2, y: 2 },\n    lowerRight: { x: 4, y: 5 }\n};\n```\n\nWe’ll see object and array initializers again in Chapters 6 and 7.\n\n> 我们将在第 6 章和第 7 章再次看到对象和数组初始化器。\n\n## 4.3 Function Definition Expressions\n\nA function definition expression defines a JavaScript function, and the value of such an expression is the newly defined function. In a sense, a function definition expression is a “function literal” in the same way that an object initializer is an “object literal.” A function definition expression typically consists of the keyword function followed by a comma-separated list of zero or more identifiers (the parameter names) in parentheses and a block of JavaScript code (the function body) in curly braces. For example:\n\n> 函数定义表达式定义一个JavaScript函数，这种表达式的值是新定义的函数。在某种意义上，函数定义表达式是“函数字面量”，就像对象初始化器是“对象字面量”一样。一个函数定义表达式通常由关键字function和一个以逗号分隔的列表组成，列表中包含0个或多个标识符(参数名)和一个JavaScript代码块(函数体)(花括号)。例如:\n\n```js\n// This function returns the square of the value passed to it.\nlet square = function(x) { return x * x; };\n```\n\nA function definition expression can also include a name for the function. Functions can also be defined using a function statement rather than a function expression. And in ES6 and later, function expressions can use a compact new “arrow function” syntax. Complete details on function definition are in Chapter 8.\n\n> 函数定义表达式还可以包含函数的名称。函数也可以使用函数语句而不是函数表达式来定义。在ES6及以后的版本中，函数表达式可以使用一种紧凑的新“箭头函数”语法。关于函数定义的完整细节见第 8 章。\n## 4.4 Property Access Expressions\n\nA property access expression evaluates to the value of an object property or an array element. JavaScript defines two syntaxes for property access:\n\n> 属性访问表达式的计算结果为对象属性或数组元素的值。JavaScript为属性访问定义了两种语法:\n\n```js\nexpression . identifier\nexpression [ expression ]\n```\n\nThe first style of property access is an expression followed by a period and an identifier. The expression specifies the object, and the identifier specifies the name of the desired property. The second style of property access follows the first expression (the object or array) with another expression in square brackets. This second expression specifies the name of the desired property or the index of the desired array element. Here are some concrete examples:\n\n> 属性访问的第一种样式是一个表达式，后面跟着句点和标识符。表达式指定对象，标识符指定所需属性的名称。第二种类型的属性访问跟在第一个表达式(对象或数组)后面，方括号中还有另一个表达式。第二个表达式指定所需属性的名称或所需数组元素的索引。以下是一些具体的例子:\n\n```js\nlet o = {x: 1, y: {z: 3}}; // An example object\nlet a = [o, 4, [5, 6]];    // An example array that contains the object\no.x                        // => 1: property x of expression o\no.y.z                      // => 3: property z of expression o.y\no[\"x\"]                     // => 1: property x of object o\na[1]                       // => 4: element at index 1 of expression a\na[2][\"1\"]                  // => 6: element at index 1 of expression a[2]\na[0].x                     // => 1: property x of expression a[0]\n```\n\nWith either type of property access expression, the expression before the . or [ is first evaluated. If the value is null or undefined, the expression throws a TypeError, since these are the two JavaScript values that cannot have properties. If the object expression is followed by a dot and an identifier, the value of the property named by that identifier is looked up and becomes the overall value of the expression. If the object expression is followed by another expression in square brackets, that second expression is evaluated and converted to a string. The overall value of the expression is then the value of the property named by that string. In either case, if the named property does not exist, then the value of the property access expression is undefined.\n\n> 使用任一类型的属性访问表达式时，前面的表达式。or[首先被求值。如果值为null或未定义，则表达式抛出TypeError，因为这两个JavaScript值不能具有属性。如果对象表达式后面跟着点和标识符，则该标识符命名的属性的值将被查找并成为表达式的整体值。如果对象表达式后面跟着方括号中的另一个表达式，则计算第二个表达式并将其转换为字符串。表达式的整体值就是由该字符串命名的属性的值。在这两种情况下，如果命名的属性不存在，那么属性访问表达式的值是未定义的。\n\nThe .identifier syntax is the simpler of the two property access options, but notice that it can only be used when the property you want to access has a name that is a legal identifier, and when you know the name when you write the program. If the property name includes spaces or punctuation characters, or when it is a number (for arrays), you must use the square bracket notation. Square brackets are also used when the property name is not static but is itself the result of a computation (see §6.3.1 for an example).\n\n> identifier语法是两种属性访问选项中比较简单的一种，但请注意，只有当您想要访问的属性具有合法标识符的名称，并且在编写程序时知道该名称时，才可以使用它。如果属性名包含空格或标点字符，或者是数字(用于数组)，则必须使用方括号表示法。方括号也用于属性名不是静态的，而是计算的结果(参看§6.3.1的例子)。\n\nObjects and their properties are covered in detail in Chapter 6, and arrays and their elements are covered in Chapter 7.\n\n> 对象及其属性将在第 6 章详细介绍，数组及其元素将在第 7 章介绍。\n\n### 4.4.1 Conditional Property Access\n\nES2020 adds two new kinds of property access expressions:\n\n> ES2020增加了两种新的属性访问表达式:\n\n```js\nexpression ?. identifier\nexpression ?.[ expression ]\n```\n\nIn JavaScript, the values null and undefined are the only two values that do not have properties. In a regular property access expression using . or [], you get a TypeError if the expression on the left evaluates to null or undefined. You can use ?. and ?.[] syntax to guard against errors of this type.\n\n> 在JavaScript中，值null和undefined是仅有的两个没有属性的值。在正则属性访问表达式中使用。或者[]，如果左边的表达式计算为null或未定义，则会得到一个TypeError。你可以用?和?。[]语法防止此类错误。\n\nConsider the expression a?.b. If a is null or undefined, then the expression evaluates to undefined without any attempt to access the property b. If a is some other value, then a?.b evaluates to whatever a.b would evaluate to (and if a does not have a property named b, then the value will again be undefined).\n\n> 考虑一下a? b这个表达式。如果a是null或undefined，则表达式计算为undefined，而没有尝试访问属性b。如果a是其他值，则a?b的值等于a.b的值(如果a没有名为b的属性，那么这个值也将是未定义的)。\n\nThis form of property access expression is sometimes called “optional chaining” because it also works for longer “chained” property access expressions like this one:\n\n```js\nlet a = { b: null };\na.b?.c.d   // => undefined\n```\na is an object, so a.b is a valid property access expression. But the value of a.b is null, so a.b.c would throw a TypeError. By using ?. instead of . we avoid the TypeError, and a.b?.c evaluates to undefined. This means that (a.b?.c).d will throw a TypeError, because that expression attempts to access a property of the value undefined. But—and this is a very important part of “optional chaining”—a.b?.c.d (without the parentheses) simply evaluates to undefined and does not throw an error. This is because property access with ?. is “short-circuiting”: if the subexpression to the left of ?. evaluates to null or undefined, then the entire expression immediately evaluates to undefined without any further property access attempts.\n\nOf course, if a.b is an object, and if that object has no property named c, then a.b?.c.d will again throw a TypeError, and we will want to use another conditional property access:\n```js\nlet a = { b: {} };\na.b?.c?.d  // => undefined\n```\nConditional property access is also possible using ?.[] instead of []. In the expression a?.[b][c], if the value of a is null or undefined, then the entire expression immediately evaluates to undefined, and subexpressions b and c are never even evaluated. If either of those expressions has side effects, the side effect will not occur if a is not defined:\n```js\nlet a;          // Oops, we forgot to initialize this variable!\nlet index = 0;\ntry {\n    a[index++]; // Throws TypeError\n} catch(e) {\n    index       // => 1: increment occurs before TypeError is thrown\n}\na?.[index++]    // => undefined: because a is undefined\nindex           // => 1: not incremented because ?.[] short-circuits\na[index++]      // !TypeError: can't index undefined.\n```\nConditional property access with ?. and ?.[] is one of the newest features of JavaScript. As of early 2020, this new syntax is supported in the current or beta versions of most major browsers.\n\n## 4.5 Invocation Expressions\nAn invocation expression is JavaScript’s syntax for calling (or executing) a function or method. It starts with a function expression that identifies the function to be called. The function expression is followed by an open parenthesis, a comma-separated list of zero or more argument expressions, and a close parenthesis. Some examples:\n```js\nf(0)            // f is the function expression; 0 is the argument expression.\nMath.max(x,y,z) // Math.max is the function; x, y, and z are the arguments.\na.sort()        // a.sort is the function; there are no arguments.\n```\nWhen an invocation expression is evaluated, the function expression is evaluated first, and then the argument expressions are evaluated to produce a list of argument values. If the value of the function expression is not a function, a TypeError is thrown. Next, the argument values are assigned, in order, to the parameter names specified when the function was defined, and then the body of the function is executed. If the function uses a return statement to return a value, then that value becomes the value of the invocation expression. Otherwise, the value of the invocation expression is undefined. Complete details on function invocation, including an explanation of what happens when the number of argument expressions does not match the number of parameters in the function definition, are in Chapter 8.\n\nEvery invocation expression includes a pair of parentheses and an expression before the open parenthesis. If that expression is a property access expression, then the invocation is known as a method invocation. In method invocations, the object or array that is the subject of the property access becomes the value of the this keyword while the body of the function is being executed. This enables an object-oriented programming paradigm in which functions (which we call “methods” when used this way) operate on the object of which they are part. See Chapter 9 for details.\n\n### 4.5.1 Conditional Invocation\nIn ES2020, you can also invoke a function using ?.() instead of (). Normally when you invoke a function, if the expression to the left of the parentheses is null or undefined or any other non-function, a TypeError is thrown. With the new ?.() invocation syntax, if the expression to the left of the ?. evaluates to null or undefined, then the entire invocation expression evaluates to undefined and no exception is thrown.\n\nArray objects have a sort() method that can optionally be passed a function argument that defines the desired sorting order for the array elements. Before ES2020, if you wanted to write a method like sort() that takes an optional function argument, you would typically use an if statement to check that the function argument was defined before invoking it in the body of the if:\n```js\nfunction square(x, log) { // The second argument is an optional function\n    if (log) {            // If the optional function is passed\n        log(x);           // Invoke it\n    }\n    return x * x;         // Return the square of the argument\n}\n```\nWith this conditional invocation syntax of ES2020, however, you can simply write the function invocation using ?.(), knowing that invocation will only happen if there is actually a value to be invoked:\n```js\nfunction square(x, log) { // The second argument is an optional function\n    log?.(x);             // Call the function if there is one\n    return x * x;         // Return the square of the argument\n}\n```\nNote, however, that ?.() only checks whether the lefthand side is null or undefined. It does not verify that the value is actually a function. So the square() function in this example would still throw an exception if you passed two numbers to it, for example.\n\nLike conditional property access expressions (§4.4.1), function invocation with ?.() is short-circuiting: if the value to the left of ?. is null or undefined, then none of the argument expressions within the parentheses are evaluated:\n```js\nlet f = null, x = 0;\ntry {\n    f(x++); // Throws TypeError because f is null\n} catch(e) {\n    x       // => 1: x gets incremented before the exception is thrown\n}\nf?.(x++)    // => undefined: f is null, but no exception thrown\nx           // => 1: increment is skipped because of short-circuiting\n```\nConditional invocation expressions with ?.() work just as well for methods as they do for functions. But because method invocation also involves property access, it is worth taking a moment to be sure you understand the differences between the following expressions:\n```js\no.m()     // Regular property access, regular invocation\no?.m()    // Conditional property access, regular invocation\no.m?.()   // Regular property access, conditional invocation\n```\nIn the first expression, o must be an object with a property m and the value of that property must be a function. In the second expression, if o is null or undefined, then the expression evaluates to undefined. But if o has any other value, then it must have a property m whose value is a function. And in the third expression, o must not be null or undefined. If it does not have a property m, or if the value of that property is null, then the entire expression evaluates to undefined.\n\nConditional invocation with ?.() is one of the newest features of JavaScript. As of the first months of 2020, this new syntax is supported in the current or beta versions of most major browsers.\n\n## 4.6 Object Creation Expressions\nAn object creation expression creates a new object and invokes a function (called a constructor) to initialize the properties of that object. Object creation expressions are like invocation expressions except that they are prefixed with the keyword new:\n```js\nnew Object()\nnew Point(2,3)\n```\nIf no arguments are passed to the constructor function in an object creation expression, the empty pair of parentheses can be omitted:\n```js\nnew Object\nnew Date\n```\nThe value of an object creation expression is the newly created object. Constructors are explained in more detail in Chapter 9.\n\n## 4.7 Operator Overview\nOperators are used for JavaScript’s arithmetic expressions, comparison expressions, logical expressions, assignment expressions, and more. Table 4-1 summarizes the operators and serves as a convenient reference.\n\nNote that most operators are represented by punctuation characters such as + and =. Some, however, are represented by keywords such as delete and instanceof. Keyword operators are regular operators, just like those expressed with punctuation; they simply have a less succinct syntax.\n\nTable 4-1 is organized by operator precedence. The operators listed first have higher precedence than those listed last. Operators separated by a horizontal line have different precedence levels. The column labeled A gives the operator associativity, which can be L (left-to-right) or R (right-to-left), and the column N specifies the number of operands. The column labeled Types lists the expected types of the operands and (after the → symbol) the result type for the operator. The subsections that follow the table explain the concepts of precedence, associativity, and operand type. The operators themselves are individually documented following that discussion.\n\nTable 4-1. JavaScript operators\n\n| Operator                     | Operation                        | A   | N   | Types            |\n| ---------------------------- | -------------------------------- | --- | --- | ---------------- |\n| ++                           | Pre- or post-increment           | R   | 1   | lval→num         |\n| --                           | Pre- or post-decrement           | R   | 1   | lval→num         |\n| -                            | Negate number                    | R   | 1   | num→num          |\n| +                            | Convert to number                | R   | 1   | any→num          |\n| ~                            | Invert bits                      | R   | 1   | int→int          |\n| !                            | Invert boolean value             | R   | 1   | bool→bool        |\n| delete                       | Remove a property                | R   | 1   | lval→bool        |\n| typeof                       | Determine type of operand        | R   | 1   | any→str          |\n| void                         | Return undefined value           | R   | 1   | any→undef        |\n| `**`                         | Exponentiate                     | R   | 2   | num,num→num      |\n| `*`, `/`, `%`                | Multiply, divide, remainder      | L   | 2   | num,num→num      |\n| +, -                         | Add, subtract                    | L   | 2   | num,num→num      |\n| +                            | Concatenate strings              | L   | 2   | str,str→str      |\n| <<                           | Shift left                       | L   | 2   | int,int→int      |\n| `>>`                         | Shift right with sign extension  | L   | 2   | int,int→int      |\n| `>>>`                        | Shift right with zero extension  | L   | 2   | int,int→int      |\n| <, <=,>, >=                  | Compare in numeric order         | L   | 2   | num,num→bool     |\n| <, <=,>, >=                  | Compare in alphabetical order    | L   | 2   | str,str→bool     |\n| instanceof                   | Test object class                | L   | 2   | obj,func→bool    |\n| in                           | Test whether property exists     | L   | 2   | any,obj→bool     |\n| ==                           | Test for non-strict equality     | L   | 2   | any,any→bool     |\n| !=                           | Test for non-strict inequality   | L   | 2   | any,any→bool     |\n| ===                          | Test for strict equality         | L   | 2   | any,any→bool     |\n| !==                          | Test for strict inequality       | L   | 2   | any,any→bool     |\n| &                            | Compute bitwise AND              | L   | 2   | int,int→int      |\n| ^                            | Compute bitwise XOR              | L   | 2   | int,int→int      |\n| \\|                           | Compute bitwise OR               | L   | 2   | int,int→int      |\n| &&                           | Compute logical AND              | L   | 2   | any,any→any      |\n| \\|\\|                         | Compute logical OR               | L   | 2   | any,any→any      |\n| ??                           | Choose 1st defined operand       | L   | 2   | any,any→any      |\n| ?:                           | Choose 2nd or 3rd operand        | R   | 3   | bool,any,any→any |\n| =                            | Assign to a variable or property | R   | 2   | lval,any→any     |\n| `**=, *=, /=, %=,`           | Operate and assign               | R   | 2   | lval,any→any     |\n| `+=`, `-=`, `&=`, `^=`, \\|=, |                                  |     |     |                  |\n| `<<=, >>=, >>>=`             |                                  |     |     |                  |\n| ,                            | Discard 1st operand, return 2nd  | L   | 2   | any,any→any      |\n\n\n### 4.7.1 Number of Operands\nOperators can be categorized based on the number of operands they expect (their arity). Most JavaScript operators, like the * multiplication operator, are binary operators that combine two expressions into a single, more complex expression. That is, they expect two operands. JavaScript also supports a number of unary operators, which convert a single expression into a single, more complex expression. The − operator in the expression −x is a unary operator that performs the operation of negation on the operand x. Finally, JavaScript supports one ternary operator, the conditional operator ?:, which combines three expressions into a single expression.\n\n### 4.7.2 Operand and Result Type\nSome operators work on values of any type, but most expect their operands to be of a specific type, and most operators return (or evaluate to) a value of a specific type. The Types column in Table 4-1 specifies operand types (before the arrow) and result type (after the arrow) for the operators.\n\nJavaScript operators usually convert the type (see §3.9) of their operands as needed. The multiplication operator * expects numeric operands, but the expression \"3\" * \"5\" is legal because JavaScript can convert the operands to numbers. The value of this expression is the number 15, not the string “15”, of course. Remember also that every JavaScript value is either “truthy” or “falsy,” so operators that expect boolean operands will work with an operand of any type.\n\nSome operators behave differently depending on the type of the operands used with them. Most notably, the + operator adds numeric operands but concatenates string operands. Similarly, the comparison operators such as < perform comparison in numerical or alphabetical order depending on the type of the operands. The descriptions of individual operators explain their type-dependencies and specify what type conversions they perform.\n\nNotice that the assignment operators and a few of the other operators listed in Table 4-1 expect an operand of type lval. lvalue is a historical term that means “an expression that can legally appear on the left side of an assignment expression.” In JavaScript, variables, properties of objects, and elements of arrays are lvalues.\n\n### 4.7.3 Operator Side Effects\nEvaluating a simple expression like 2 * 3 never affects the state of your program, and any future computation your program performs will be unaffected by that evaluation. Some expressions, however, have side effects, and their evaluation may affect the result of future evaluations. The assignment operators are the most obvious example: if you assign a value to a variable or property, that changes the value of any expression that uses that variable or property. The ++ and -- increment and decrement operators are similar, since they perform an implicit assignment. The delete operator also has side effects: deleting a property is like (but not the same as) assigning undefined to the property.\n\nNo other JavaScript operators have side effects, but function invocation and object creation expressions will have side effects if any of the operators used in the function or constructor body have side effects.\n\n### 4.7.4 Operator Precedence\nThe operators listed in Table 4-1 are arranged in order from high precedence to low precedence, with horizontal lines separating groups of operators at the same precedence level. Operator precedence controls the order in which operations are performed. Operators with higher precedence (nearer the top of the table) are performed before those with lower precedence (nearer to the bottom).\n\nConsider the following expression:\n```js\nw = x + y*z;\n```\nThe multiplication operator * has a higher precedence than the addition operator +, so the multiplication is performed before the addition. Furthermore, the assignment operator = has the lowest precedence, so the assignment is performed after all the operations on the right side are completed.\n\nOperator precedence can be overridden with the explicit use of parentheses. To force the addition in the previous example to be performed first, write:\n```js\nw = (x + y)*z;\n```\nNote that property access and invocation expressions have higher precedence than any of the operators listed in Table 4-1. Consider this expression:\n```js\n// my is an object with a property named functions whose value is an\n// array of functions. We invoke function number x, passing it argument\n// y, and then we ask for the type of the value returned.\ntypeof my.functions[x](y)\n```\nAlthough typeof is one of the highest-priority operators, the typeof operation is performed on the result of the property access, array index, and function invocation, all of which have higher priority than operators.\n\nIn practice, if you are at all unsure about the precedence of your operators, the simplest thing to do is to use parentheses to make the evaluation order explicit. The rules that are important to know are these: multiplication and division are performed before addition and subtraction, and assignment has very low precedence and is almost always performed last.\n\nWhen new operators are added to JavaScript, they do not always fit naturally into this precedence scheme. The ?? operator (§4.13.2) is shown in the table as lower-precedence than || and &&, but, in fact, its precedence relative to those operators is not defined, and ES2020 requires you to explicitly use parentheses if you mix ?? with either || or &&. Similarly, the new ** exponentiation operator does not have a well-defined precedence relative to the unary negation operator, and you must use parentheses when combining negation with exponentiation.\n\n### 4.7.5 Operator Associativity\nIn Table 4-1, the column labeled A specifies the associativity of the operator. A value of L specifies left-to-right associativity, and a value of R specifies right-to-left associativity. The associativity of an operator specifies the order in which operations of the same precedence are performed. Left-to-right associativity means that operations are performed from left to right. For example, the subtraction operator has left-to-right associativity, so:\n```js\nw = x - y - z;\n```\nis the same as:\n```js\nw = ((x - y) - z);\n```\nOn the other hand, the following expressions:\n```js\ny = a ** b ** c;\nx = ~-y;\nw = x = y = z;\nq = a?b:c?d:e?f:g;\n```\nare equivalent to:\n```js\ny = (a ** (b ** c));\nx = ~(-y);\nw = (x = (y = z));\nq = a?b:(c?d:(e?f:g));\n```\nbecause the exponentiation, unary, assignment, and ternary conditional operators have right-to-left associativity.\n\n### 4.7.6 Order of Evaluation\nOperator precedence and associativity specify the order in which operations are performed in a complex expression, but they do not specify the order in which the subexpressions are evaluated. JavaScript always evaluates expressions in strictly left-to-right order. In the expression w = x + y * z, for example, the subexpression w is evaluated first, followed by x, y, and z. Then the values of y and z are multiplied, added to the value of x, and assigned to the variable or property specified by expression w. Adding parentheses to the expressions can change the relative order of the multiplication, addition, and assignment, but not the left-to-right order of evaluation.\n\nOrder of evaluation only makes a difference if any of the expressions being evaluated has side effects that affect the value of another expression. If expression x increments a variable that is used by expression z, then the fact that x is evaluated before z is important.\n\n## 4.8 Arithmetic Expressions\nThis section covers the operators that perform arithmetic or other numerical manipulations on their operands. The exponentiation, multiplication, division, and subtraction operators are straightforward and are covered first. The addition operator gets a subsection of its own because it can also perform string concatenation and has some unusual type conversion rules. The unary operators and the bitwise operators are also covered in subsections of their own.\n\nMost of these arithmetic operators (except as noted as follows) can be used with BigInt (see §3.2.5) operands or with regular numbers, as long as you don’t mix the two types.\n\nThe basic arithmetic operators are `**` (exponentiation), * (multiplication), / (division), % (modulo: remainder after division), + (addition), and - (subtraction). As noted, we’ll discuss the + operator in a section of its own. The other five basic operators simply evaluate their operands, convert the values to numbers if necessary, and then compute the power, product, quotient, remainder, or difference. Non-numeric operands that cannot convert to numbers convert to the NaN value. If either operand is (or converts to) NaN, the result of the operation is (almost always) NaN.\n\nThe `**` operator has higher precedence than *, /, and % (which in turn have higher precedence than + and -). Unlike the other operators, `**` works right-to-left, so 2`**`2`**`3 is the same as 2`**`8, not 4`**`3. There is a natural ambiguity to expressions like -3`**`2. Depending on the relative precedence of unary minus and exponentiation, that expression could mean (-3)`**`2 or -(3`**`2). Different languages handle this differently, and rather than pick sides, JavaScript simply makes it a syntax error to omit parentheses in this case, forcing you to write an unambiguous expression. `**` is JavaScript’s newest arithmetic operator: it was added to the language with ES2016. The Math.pow() function has been available since the earliest versions of JavaScript, however, and it performs exactly the same operation as the `**` operator.\n\nThe / operator divides its first operand by its second. If you are used to programming languages that distinguish between integer and floating-point numbers, you might expect to get an integer result when you divide one integer by another. In JavaScript, however, all numbers are floating-point, so all division operations have floating-point results: 5/2 evaluates to 2.5, not 2. Division by zero yields positive or negative infinity, while 0/0 evaluates to NaN: neither of these cases raises an error.\n\nThe % operator computes the first operand modulo the second operand. In other words, it returns the remainder after whole-number division of the first operand by the second operand. The sign of the result is the same as the sign of the first operand. For example, 5 % 2 evaluates to 1, and -5 % 2 evaluates to -1.\n\nWhile the modulo operator is typically used with integer operands, it also works for floating-point values. For example, 6.5 % 2.1 evaluates to 0.2.\n\n### 4.8.1 The + Operator\nThe binary + operator adds numeric operands or concatenates string operands:\n```js\n1 + 2                        // => 3\n\"hello\" + \" \" + \"there\"      // => \"hello there\"\n\"1\" + \"2\"                    // => \"12\"\n```\nWhen the values of both operands are numbers, or are both strings, then it is obvious what the + operator does. In any other case, however, type conversion is necessary, and the operation to be performed depends on the conversion performed. The conversion rules for + give priority to string concatenation: if either of the operands is a string or an object that converts to a string, the other operand is converted to a string and concatenation is performed. Addition is performed only if neither operand is string-like.\n\nTechnically, the + operator behaves like this:\n\n- If either of its operand values is an object, it converts it to a primitive using the object-to-primitive algorithm described in §3.9.3. Date objects are converted by their toString() method, and all other objects are converted via valueOf(), if that method returns a primitive value. However, most objects do not have a useful valueOf() method, so they are converted via toString() as well.\n- After object-to-primitive conversion, if either operand is a string, the other is converted to a string and concatenation is performed.\n- Otherwise, both operands are converted to numbers (or to NaN) and addition is performed.\n\nHere are some examples:\n```js\n1 + 2         // => 3: addition\n\"1\" + \"2\"     // => \"12\": concatenation\n\"1\" + 2       // => \"12\": concatenation after number-to-string\n1 + {}        // => \"1[object Object]\": concatenation after object-to-string\ntrue + true   // => 2: addition after boolean-to-number\n2 + null      // => 2: addition after null converts to 0\n2 + undefined // => NaN: addition after undefined converts to NaN\n```\nFinally, it is important to note that when the + operator is used with strings and numbers, it may not be associative. That is, the result may depend on the order in which operations are performed.\n\nFor example:\n```js\n1 + 2 + \" blind mice\"    // => \"3 blind mice\"\n1 + (2 + \" blind mice\")  // => \"12 blind mice\"\n```\nThe first line has no parentheses, and the + operator has left-to-right associativity, so the two numbers are added first, and their sum is concatenated with the string. In the second line, parentheses alter this order of operations: the number 2 is concatenated with the string to produce a new string. Then the number 1 is concatenated with the new string to produce the final result.\n\n### 4.8.2 Unary Arithmetic Operators\nUnary operators modify the value of a single operand to produce a new value. In JavaScript, the unary operators all have high precedence and are all right-associative. The arithmetic unary operators described in this section (+, -, ++, and --) all convert their single operand to a number, if necessary. Note that the punctuation characters + and - are used as both unary and binary operators.\n\nThe unary arithmetic operators are the following:\n\nUnary plus (+)\nThe unary plus operator converts its operand to a number (or to NaN) and returns that converted value. When used with an operand that is already a number, it doesn’t do anything. This operator may not be used with BigInt values, since they cannot be converted to regular numbers.\n\nUnary minus (-)\nWhen - is used as a unary operator, it converts its operand to a number, if necessary, and then changes the sign of the result.\n\nIncrement (++)\nThe ++ operator increments (i.e., adds 1 to) its single operand, which must be an lvalue (a variable, an element of an array, or a property of an object). The operator converts its operand to a number, adds 1 to that number, and assigns the incremented value back into the variable, element, or property.\n\nThe return value of the ++ operator depends on its position relative to the operand. When used before the operand, where it is known as the pre-increment operator, it increments the operand and evaluates to the incremented value of that operand. When used after the operand, where it is known as the post-increment operator, it increments its operand but evaluates to the unincremented value of that operand. Consider the difference between these two lines of code:\n```js\nlet i = 1, j = ++i;    // i and j are both 2\nlet n = 1, m = n++;    // n is 2, m is 1\n```\nNote that the expression x++ is not always the same as x=x+1. The ++ operator never performs string concatenation: it always converts its operand to a number and increments it. If x is the string “1”, ++x is the number 2, but x+1 is the string “11”.\n\nAlso note that, because of JavaScript’s automatic semicolon insertion, you cannot insert a line break between the post-increment operator and the operand that precedes it. If you do so, JavaScript will treat the operand as a complete statement by itself and insert a semicolon before it.\n\nThis operator, in both its pre- and post-increment forms, is most commonly used to increment a counter that controls a for loop (§5.4.3).\n\nDecrement (--)\nThe -- operator expects an lvalue operand. It converts the value of the operand to a number, subtracts 1, and assigns the decremented value back to the operand. Like the ++ operator, the return value of -- depends on its position relative to the operand. When used before the operand, it decrements and returns the decremented value. When used after the operand, it decrements the operand but returns the undecremented value. When used after its operand, no line break is allowed between the operand and the operator.\n\n### 4.8.3 Bitwise Operators\nThe bitwise operators perform low-level manipulation of the bits in the binary representation of numbers. Although they do not perform traditional arithmetic operations, they are categorized as arithmetic operators here because they operate on numeric operands and return a numeric value. Four of these operators perform Boolean algebra on the individual bits of the operands, behaving as if each bit in each operand were a boolean value (1=true, 0=false). The other three bitwise operators are used to shift bits left and right. These operators are not commonly used in JavaScript programming, and if you are not familiar with the binary representation of integers, including the two’s complement representation of negative integers, you can probably skip this section.\n\nThe bitwise operators expect integer operands and behave as if those values were represented as 32-bit integers rather than 64-bit floating-point values. These operators convert their operands to numbers, if necessary, and then coerce the numeric values to 32-bit integers by dropping any fractional part and any bits beyond the 32nd. The shift operators require a right-side operand between 0 and 31. After converting this operand to an unsigned 32-bit integer, they drop any bits beyond the 5th, which yields a number in the appropriate range. Surprisingly, NaN, Infinity, and -Infinity all convert to 0 when used as operands of these bitwise operators.\n\nAll of these bitwise operators except >>> can be used with regular number operands or with BigInt (see §3.2.5) operands.\n\nBitwise AND (&)\nThe & operator performs a Boolean AND operation on each bit of its integer arguments. A bit is set in the result only if the corresponding bit is set in both operands. For example, 0x1234 & 0x00FF evaluates to 0x0034.\n\nBitwise OR (|)\nThe | operator performs a Boolean OR operation on each bit of its integer arguments. A bit is set in the result if the corresponding bit is set in one or both of the operands. For example, 0x1234 | 0x00FF evaluates to 0x12FF.\n\nBitwise XOR (^)\nThe ^ operator performs a Boolean exclusive OR operation on each bit of its integer arguments. Exclusive OR means that either operand one is true or operand two is true, but not both. A bit is set in this operation’s result if a corresponding bit is set in one (but not both) of the two operands. For example, 0xFF00 ^ 0xF0F0 evaluates to 0x0FF0.\n\nBitwise NOT (~)\nThe ~ operator is a unary operator that appears before its single integer operand. It operates by reversing all bits in the operand. Because of the way signed integers are represented in JavaScript, applying the ~ operator to a value is equivalent to changing its sign and subtracting 1. For example, ~0x0F evaluates to 0xFFFFFFF0, or −16.\n\nShift left (<<)\nThe << operator moves all bits in its first operand to the left by the number of places specified in the second operand, which should be an integer between 0 and 31. For example, in the operation a << 1, the first bit (the ones bit) of a becomes the second bit (the twos bit), the second bit of a becomes the third, etc. A zero is used for the new first bit, and the value of the 32nd bit is lost. Shifting a value left by one position is equivalent to multiplying by 2, shifting two positions is equivalent to multiplying by 4, and so on. For example, 7 << 2 evaluates to 28.\n\nShift right with sign (>>)\nThe >> operator moves all bits in its first operand to the right by the number of places specified in the second operand (an integer between 0 and 31). Bits that are shifted off the right are lost. The bits filled in on the left depend on the sign bit of the original operand, in order to preserve the sign of the result. If the first operand is positive, the result has zeros placed in the high bits; if the first operand is negative, the result has ones placed in the high bits. Shifting a positive value right one place is equivalent to dividing by 2 (discarding the remainder), shifting right two places is equivalent to integer division by 4, and so on. 7 >> 1 evaluates to 3, for example, but note that and −7 >> 1 evaluates to −4.\n\nShift right with zero fill (>>>)\nThe >>> operator is just like the >> operator, except that the bits shifted in on the left are always zero, regardless of the sign of the first operand. This is useful when you want to treat signed 32-bit values as if they are unsigned integers. −1 >> 4 evaluates to −1, but −1 >>> 4 evaluates to 0x0FFFFFFF, for example. This is the only one of the JavaScript bitwise operators that cannot be used with BigInt values. BigInt does not represent negative numbers by setting the high bit the way that 32-bit integers do, and this operator only makes sense for that particular two’s complement representation.\n\n## 4.9 Relational Expressions\nThis section describes JavaScript’s relational operators. These operators test for a relationship (such as “equals,” “less than,” or “property of”) between two values and return true or false depending on whether that relationship exists. Relational expressions always evaluate to a boolean value, and that value is often used to control the flow of program execution in if, while, and for statements (see Chapter 5). The subsections that follow document the equality and inequality operators, the comparison operators, and JavaScript’s other two relational operators, in and instanceof.\n\n### 4.9.1 Equality and Inequality Operators\nThe == and === operators check whether two values are the same, using two different definitions of sameness. Both operators accept operands of any type, and both return true if their operands are the same and false if they are different. The === operator is known as the strict equality operator (or sometimes the identity operator), and it checks whether its two operands are “identical” using a strict definition of sameness. The == operator is known as the equality operator; it checks whether its two operands are “equal” using a more relaxed definition of sameness that allows type conversions.\n\nThe != and !== operators test for the exact opposite of the == and === operators. The != inequality operator returns false if two values are equal to each other according to == and returns true otherwise. The !== operator returns false if two values are strictly equal to each other and returns true otherwise. As you’ll see in §4.10, the ! operator computes the Boolean NOT operation. This makes it easy to remember that != and !== stand for “not equal to” and “not strictly equal to.”\n\nTHE =, ==, AND === OPERATORS\nJavaScript supports =, ==, and === operators. Be sure you understand the differences between these assignment, equality, and strict equality operators, and be careful to use the correct one when coding! Although it is tempting to read all three operators as “equals,” it may help to reduce confusion if you read “gets” or “is assigned” for =, “is equal to” for ==, and “is strictly equal to” for ===.\n\nThe == operator is a legacy feature of JavaScript and is widely considered to be a source of bugs. You should almost always use === instead of ==, and !== instead of !=.\n\nAs mentioned in §3.8, JavaScript objects are compared by reference, not by value. An object is equal to itself, but not to any other object. If two distinct objects have the same number of properties, with the same names and values, they are still not equal. Similarly, two arrays that have the same elements in the same order are not equal to each other.\n\nSTRICT EQUALITY\nThe strict equality operator === evaluates its operands, then compares the two values as follows, performing no type conversion:\n\n- If the two values have different types, they are not equal.\n- If both values are null or both values are undefined, they are equal.\n- If both values are the boolean value true or both are the boolean value false, they are equal.\n- If one or both values is NaN, they are not equal. (This is surprising, but the NaN value is never equal to any other value, including itself! To check whether a value x is NaN, use x !== x, or the global isNaN() function.)\n- If both values are numbers and have the same value, they are equal. If one value is 0 and the other is -0, they are also equal.\n- If both values are strings and contain exactly the same 16-bit values (see the sidebar in §3.3) in the same positions, they are equal. If the strings differ in length or content, they are not equal. Two strings may have the same meaning and the same visual appearance, but still be encoded using different sequences of 16-bit values. JavaScript performs no Unicode normalization, and a pair of strings like this is not considered equal to the === or == operators.\n- If both values refer to the same object, array, or function, they are equal. If they refer to different objects, they are not equal, even if both objects have identical properties.\n\nEQUALITY WITH TYPE CONVERSION\nThe equality operator == is like the strict equality operator, but it is less strict. If the values of the two operands are not the same type, it attempts some type conversions and tries the comparison again:\n\n- If the two values have the same type, test them for strict equality as described previously. If they are strictly equal, they are equal. If they are not strictly equal, they are not equal.\n- If the two values do not have the same type, the == operator may still consider them equal. It uses the following rules and type conversions to check for equality:\n    - If one value is null and the other is undefined, they are equal.\n    - If one value is a number and the other is a string, convert the string to a number and try the comparison again, using the converted value.\n    - If either value is true, convert it to 1 and try the comparison again. If either value is false, convert it to 0 and try the comparison again.\n    - If one value is an object and the other is a number or string, convert the object to a primitive using the algorithm described in §3.9.3 and try the comparison again. An object is converted to a primitive value by either its toString() method or its valueOf() method. The built-in classes of core JavaScript attempt valueOf() conversion before toString() conversion, except for the Date class, which performs toString() conversion.\n    - Any other combinations of values are not equal.\n\nAs an example of testing for equality, consider the comparison:\n```js\n\"1\" == true  // => true\n```\nThis expression evaluates to true, indicating that these very different-looking values are in fact equal. The boolean value true is first converted to the number 1, and the comparison is done again. Next, the string \"1\" is converted to the number 1. Since both values are now the same, the comparison returns true.\n\n### 4.9.2 Comparison Operators\nThe comparison operators test the relative order (numerical or alphabetical) of their two operands:\n\nLess than (<)\nThe < operator evaluates to true if its first operand is less than its second operand; otherwise, it evaluates to false.\n\nGreater than (>)\nThe > operator evaluates to true if its first operand is greater than its second operand; otherwise, it evaluates to false.\n\nLess than or equal (<=)\nThe <= operator evaluates to true if its first operand is less than or equal to its second operand; otherwise, it evaluates to false.\n\nGreater than or equal (>=)\nThe >= operator evaluates to true if its first operand is greater than or equal to its second operand; otherwise, it evaluates to false.\n\nThe operands of these comparison operators may be of any type. Comparison can be performed only on numbers and strings, however, so operands that are not numbers or strings are converted.\n\nComparison and conversion occur as follows:\n\n- If either operand evaluates to an object, that object is converted to a primitive value, as described at the end of §3.9.3; if its valueOf() method returns a primitive value, that value is used. Otherwise, the return value of its toString() method is used.\n- If, after any required object-to-primitive conversion, both operands are strings, the two strings are compared, using alphabetical order, where “alphabetical order” is defined by the numerical order of the 16-bit Unicode values that make up the strings.\n- If, after object-to-primitive conversion, at least one operand is not a string, both operands are converted to numbers and compared numerically. 0 and -0 are considered equal. Infinity is larger than any number other than itself, and -Infinity is smaller than any number other than itself. If either operand is (or converts to) NaN, then the comparison operator always returns false. Although the arithmetic operators do not allow BigInt values to be mixed with regular numbers, the comparison operators do allow comparisons between numbers and BigInts.\n\nRemember that JavaScript strings are sequences of 16-bit integer values, and that string comparison is just a numerical comparison of the values in the two strings. The numerical encoding order defined by Unicode may not match the traditional collation order used in any particular language or locale. Note in particular that string comparison is case-sensitive, and all capital ASCII letters are “less than” all lowercase ASCII letters. This rule can cause confusing results if you do not expect it. For example, according to the < operator, the string “Zoo” comes before the string “aardvark”.\n\nFor a more robust string-comparison algorithm, try the String.localeCompare() method, which also takes locale-specific definitions of alphabetical order into account. For case-insensitive comparisons, you can convert the strings to all lowercase or all uppercase using String.toLowerCase() or String.toUpperCase(). And, for a more general and better localized string comparison tool, use the Intl.Collator class described in §11.7.3.\n\nBoth the + operator and the comparison operators behave differently for numeric and string operands. + favors strings: it performs concatenation if either operand is a string. The comparison operators favor numbers and only perform string comparison if both operands are strings:\n```js\n1 + 2        // => 3: addition.\n\"1\" + \"2\"    // => \"12\": concatenation.\n\"1\" + 2      // => \"12\": 2 is converted to \"2\".\n11 < 3       // => false: numeric comparison.\n\"11\" < \"3\"   // => true: string comparison.\n\"11\" < 3     // => false: numeric comparison, \"11\" converted to 11.\n\"one\" < 3    // => false: numeric comparison, \"one\" converted to NaN.\n```\nFinally, note that the <= (less than or equal) and >= (greater than or equal) operators do not rely on the equality or strict equality operators for determining whether two values are “equal.” Instead, the less-than-or-equal operator is simply defined as “not greater than,” and the greater-than-or-equal operator is defined as “not less than.” The one exception occurs when either operand is (or converts to) NaN, in which case, all four comparison operators return false.\n\n### 4.9.3 The in Operator\nThe in operator expects a left-side operand that is a string, symbol, or value that can be converted to a string. It expects a right-side operand that is an object. It evaluates to true if the left-side value is the name of a property of the right-side object. For example:\n```js\nlet point = {x: 1, y: 1};  // Define an object\n\"x\" in point               // => true: object has property named \"x\"\n\"z\" in point               // => false: object has no \"z\" property.\n\"toString\" in point        // => true: object inherits toString method\n\nlet data = [7,8,9];        // An array with elements (indices) 0, 1, and 2\n\"0\" in data                // => true: array has an element \"0\"\n1 in data                  // => true: numbers are converted to strings\n3 in data                  // => false: no element 3\n```\n### 4.9.4 The instanceof Operator\nThe instanceof operator expects a left-side operand that is an object and a right-side operand that identifies a class of objects. The operator evaluates to true if the left-side object is an instance of the right-side class and evaluates to false otherwise. Chapter 9 explains that, in JavaScript, classes of objects are defined by the constructor function that initializes them. Thus, the right-side operand of instanceof should be a function. Here are examples:\n```js\nlet d = new Date();  // Create a new object with the Date() constructor\nd instanceof Date    // => true: d was created with Date()\nd instanceof Object  // => true: all objects are instances of Object\nd instanceof Number  // => false: d is not a Number object\nlet a = [1, 2, 3];   // Create an array with array literal syntax\na instanceof Array   // => true: a is an array\na instanceof Object  // => true: all arrays are objects\na instanceof RegExp  // => false: arrays are not regular expressions\n```\nNote that all objects are instances of Object. instanceof considers the “superclasses” when deciding whether an object is an instance of a class. If the left-side operand of instanceof is not an object, instanceof returns false. If the righthand side is not a class of objects, it throws a TypeError.\n\nIn order to understand how the instanceof operator works, you must understand the “prototype chain.” This is JavaScript’s inheritance mechanism, and it is described in §6.3.2. To evaluate the expression o instanceof f, JavaScript evaluates f.prototype, and then looks for that value in the prototype chain of o. If it finds it, then o is an instance of f (or of a subclass of f) and the operator returns true. If f.prototype is not one of the values in the prototype chain of o, then o is not an instance of f and instanceof returns false.\n\n## 4.10 Logical Expressions\nThe logical operators &&, ||, and ! perform Boolean algebra and are often used in conjunction with the relational operators to combine two relational expressions into one more complex expression. These operators are described in the subsections that follow. In order to fully understand them, you may want to review the concept of “truthy” and “falsy” values introduced in §3.4.\n\n### 4.10.1 Logical AND (&&)\nThe && operator can be understood at three different levels. At the simplest level, when used with boolean operands, && performs the Boolean AND operation on the two values: it returns true if and only if both its first operand and its second operand are true. If one or both of these operands is false, it returns false.\n\n&& is often used as a conjunction to join two relational expressions:\n```js\nx === 0 && y === 0   // true if, and only if, x and y are both 0\n```\nRelational expressions always evaluate to true or false, so when used like this, the && operator itself returns true or false. Relational operators have higher precedence than && (and ||), so expressions like these can safely be written without parentheses.\n\nBut && does not require that its operands be boolean values. Recall that all JavaScript values are either “truthy” or “falsy.” (See §3.4 for details. The falsy values are false, null, undefined, 0, -0, NaN, and \"\". All other values, including all objects, are truthy.) The second level at which && can be understood is as a Boolean AND operator for truthy and falsy values. If both operands are truthy, the operator returns a truthy value. Otherwise, one or both operands must be falsy, and the operator returns a falsy value. In JavaScript, any expression or statement that expects a boolean value will work with a truthy or falsy value, so the fact that && does not always return true or false does not cause practical problems.\n\nNotice that this description says that the operator returns “a truthy value” or “a falsy value” but does not specify what that value is. For that, we need to describe && at the third and final level. This operator starts by evaluating its first operand, the expression on its left. If the value on the left is falsy, the value of the entire expression must also be falsy, so && simply returns the value on the left and does not even evaluate the expression on the right.\n\nOn the other hand, if the value on the left is truthy, then the overall value of the expression depends on the value on the righthand side. If the value on the right is truthy, then the overall value must be truthy, and if the value on the right is falsy, then the overall value must be falsy. So when the value on the left is truthy, the && operator evaluates and returns the value on the right:\n```js\nlet o = {x: 1};\nlet p = null;\no && o.x     // => 1: o is truthy, so return value of o.x\np && p.x     // => null: p is falsy, so return it and don't evaluate p.x\n```\nIt is important to understand that && may or may not evaluate its right-side operand. In this code example, the variable p is set to null, and the expression p.x would, if evaluated, cause a TypeError. But the code uses && in an idiomatic way so that p.x is evaluated only if p is truthy—not null or undefined.\n\nThe behavior of && is sometimes called short circuiting, and you may sometimes see code that purposely exploits this behavior to conditionally execute code. For example, the following two lines of JavaScript code have equivalent effects:\n```js\nif (a === b) stop();   // Invoke stop() only if a === b\n(a === b) && stop();   // This does the same thing\n```\nIn general, you must be careful whenever you write an expression with side effects (assignments, increments, decrements, or function invocations) on the righthand side of &&. Whether those side effects occur depends on the value of the lefthand side.\n\nDespite the somewhat complex way that this operator actually works, it is most commonly used as a simple Boolean algebra operator that works on truthy and falsy values.\n\n### 4.10.2 Logical OR (||)\nThe || operator performs the Boolean OR operation on its two operands. If one or both operands is truthy, it returns a truthy value. If both operands are falsy, it returns a falsy value.\n\nAlthough the || operator is most often used simply as a Boolean OR operator, it, like the && operator, has more complex behavior. It starts by evaluating its first operand, the expression on its left. If the value of this first operand is truthy, it short-circuits and returns that truthy value without ever evaluating the expression on the right. If, on the other hand, the value of the first operand is falsy, then || evaluates its second operand and returns the value of that expression.\n\nAs with the && operator, you should avoid right-side operands that include side effects, unless you purposely want to use the fact that the right-side expression may not be evaluated.\n\nAn idiomatic usage of this operator is to select the first truthy value in a set of alternatives:\n```js\n// If maxWidth is truthy, use that. Otherwise, look for a value in\n// the preferences object. If that is not truthy, use a hardcoded constant.\nlet max = maxWidth || preferences.maxWidth || 500;\n```\nNote that if 0 is a legal value for maxWidth, then this code will not work correctly, since 0 is a falsy value. See the ?? operator (§4.13.2) for an alternative.\n\nPrior to ES6, this idiom is often used in functions to supply default values for parameters:\n```js\n// Copy the properties of o to p, and return p\nfunction copy(o, p) {\n    p = p || {};  // If no object passed for p, use a newly created object.\n    // function body goes here\n}\n```\nIn ES6 and later, however, this trick is no longer needed because the default parameter value could simply be written in the function definition itself: function copy(o, p={}) { ... }.\n\n### 4.10.3 Logical NOT (!)\nThe ! operator is a unary operator; it is placed before a single operand. Its purpose is to invert the boolean value of its operand. For example, if x is truthy, !x evaluates to false. If x is falsy, then !x is true.\n\nUnlike the && and || operators, the ! operator converts its operand to a boolean value (using the rules described in Chapter 3) before inverting the converted value. This means that ! always returns true or false and that you can convert any value x to its equivalent boolean value by applying this operator twice: !!x (see §3.9.2).\n\nAs a unary operator, ! has high precedence and binds tightly. If you want to invert the value of an expression like p && q, you need to use parentheses: !(p && q). It is worth noting two laws of Boolean algebra here that we can express using JavaScript syntax:\n```js\n// DeMorgan's Laws\n!(p && q) === (!p || !q)  // => true: for all values of p and q\n!(p || q) === (!p && !q)  // => true: for all values of p and q\n```\n## 4.11 Assignment Expressions\nJavaScript uses the = operator to assign a value to a variable or property. For example:\n```js\ni = 0;     // Set the variable i to 0.\no.x = 1;   // Set the property x of object o to 1.\n```\nThe = operator expects its left-side operand to be an lvalue: a variable or object property (or array element). It expects its right-side operand to be an arbitrary value of any type. The value of an assignment expression is the value of the right-side operand. As a side effect, the = operator assigns the value on the right to the variable or property on the left so that future references to the variable or property evaluate to the value.\n\nAlthough assignment expressions are usually quite simple, you may sometimes see the value of an assignment expression used as part of a larger expression. For example, you can assign and test a value in the same expression with code like this:\n```js\n(a = b) === 0\n```\nIf you do this, be sure you are clear on the difference between the = and === operators! Note that = has very low precedence, and parentheses are usually necessary when the value of an assignment is to be used in a larger expression.\n\nThe assignment operator has right-to-left associativity, which means that when multiple assignment operators appear in an expression, they are evaluated from right to left. Thus, you can write code like this to assign a single value to multiple variables:\n```js\ni = j = k = 0;       // Initialize 3 variables to 0\n```\n### 4.11.1 Assignment with Operation\nBesides the normal = assignment operator, JavaScript supports a number of other assignment operators that provide shortcuts by combining assignment with some other operation. For example, the += operator performs addition and assignment. The following expression:\n```js\ntotal += salesTax;\n```\nis equivalent to this one:\n```js\ntotal = total + salesTax;\n```\nAs you might expect, the += operator works for numbers or strings. For numeric operands, it performs addition and assignment; for string operands, it performs concatenation and assignment.\n\nSimilar operators include -=, *=, &=, and so on. Table 4-2 lists them all.\n\nTable 4-2. Assignment operators\n\n| Operator | Example   | Equivalent   |\n| -------- | --------- | ------------ |\n| +=       | a += b    | a = a + b    |\n| -=       | a -= b    | a = a - b    |\n| `*=`     | a `*=` b  | a = a `*` b  |\n| /=       | a /= b    | a = a / b    |\n| %=       | a %= b    | a = a % b    |\n| `**=`    | a `**`= b | a = a `**` b |\n| <<=      | a <<= b   | a = a << b   |\n| >>=      | a >>= b   | a = a >> b   |\n| >>>=     | a >>>= b  | a = a >>> b  |\n| &=       | a &= b    | a = a & b    |\n| \\|=      | a \\|= b   | a = a \\| b   |\n| ^=       | a ^= b    | a = a ^ b    |\n\nIn most cases, the expression:\n```js\na op= b\n```\nwhere op is an operator, is equivalent to the expression:\n```js\na = a op b\n```\nIn the first line, the expression a is evaluated once. In the second, it is evaluated twice. The two cases will differ only if a includes side effects such as a function call or an increment operator. The following two assignments, for example, are not the same:\n```js\ndata[i++] *= 2;\ndata[i++] = data[i++] * 2;\n```\n## 4.12 Evaluation Expressions\nLike many interpreted languages, JavaScript has the ability to interpret strings of JavaScript source code, evaluating them to produce a value. JavaScript does this with the global function eval():\n```js\neval(\"3+2\")    // => 5\n```\nDynamic evaluation of strings of source code is a powerful language feature that is almost never necessary in practice. If you find yourself using eval(), you should think carefully about whether you really need to use it. In particular, eval() can be a security hole, and you should never pass any string derived from user input to eval(). With a language as complicated as JavaScript, there is no way to sanitize user input to make it safe to use with eval(). Because of these security issues, some web servers use the HTTP “Content-Security-Policy” header to disable eval() for an entire website.\n\nThe subsections that follow explain the basic use of eval() and explain two restricted versions of it that have less impact on the optimizer.\n\nIS EVAL() A FUNCTION OR AN OPERATOR?\neval() is a function, but it is included in this chapter on expressions because it really should have been an operator. The earliest versions of the language defined an eval() function, and ever since then, language designers and interpreter writers have been placing restrictions on it that make it more and more operator-like. Modern JavaScript interpreters perform a lot of code analysis and optimization. Generally speaking, if a function calls eval(), the interpreter cannot optimize that function. The problem with defining eval() as a function is that it can be given other names:\n```js\nlet f = eval;\nlet g = f;\n```\nIf this is allowed, then the interpreter can’t know for sure which functions call eval(), so it cannot optimize aggressively. This issue could have been avoided if eval() was an operator (and a reserved word). We’ll learn (in §4.12.2 and §4.12.3) about restrictions placed on eval() to make it more operator-like.\n\n### 4.12.1 eval()\neval() expects one argument. If you pass any value other than a string, it simply returns that value. If you pass a string, it attempts to parse the string as JavaScript code, throwing a SyntaxError if it fails. If it successfully parses the string, then it evaluates the code and returns the value of the last expression or statement in the string or undefined if the last expression or statement had no value. If the evaluated string throws an exception, that exception propogates from the call to eval().\n\nThe key thing about eval() (when invoked like this) is that it uses the variable environment of the code that calls it. That is, it looks up the values of variables and defines new variables and functions in the same way that local code does. If a function defines a local variable x and then calls eval(\"x\"), it will obtain the value of the local variable. If it calls eval(\"x=1\"), it changes the value of the local variable. And if the function calls eval(\"var y = 3;\"), it declares a new local variable y. On the other hand, if the evaluated string uses let or const, the variable or constant declared will be local to the evaluation and will not be defined in the calling environment.\n\nSimilarly, a function can declare a local function with code like this:\n```js\neval(\"function f() { return x+1; }\");\n```\nIf you call eval() from top-level code, it operates on global variables and global functions, of course.\n\nNote that the string of code you pass to eval() must make syntactic sense on its own: you cannot use it to paste code fragments into a function. It makes no sense to write eval(\"return;\"), for example, because return is only legal within functions, and the fact that the evaluated string uses the same variable environment as the calling function does not make it part of that function. If your string would make sense as a standalone script (even a very short one like x=0 ), it is legal to pass to eval(). Otherwise, eval() will throw a SyntaxError.\n\n### 4.12.2 Global eval()\nIt is the ability of eval() to change local variables that is so problematic to JavaScript optimizers. As a workaround, however, interpreters simply do less optimization on any function that calls eval(). But what should a JavaScript interpreter do, however, if a script defines an alias for eval() and then calls that function by another name? The JavaScript specification declares that when eval() is invoked by any name other than “eval”, it should evaluate the string as if it were top-level global code. The evaluated code may define new global variables or global functions, and it may set global variables, but it will not use or modify any variables local to the calling function, and will not, therefore, interfere with local optimizations.\n\nA “direct eval” is a call to the eval() function with an expression that uses the exact, unqualified name “eval” (which is beginning to feel like a reserved word). Direct calls to eval() use the variable environment of the calling context. Any other call—an indirect call—uses the global object as its variable environment and cannot read, write, or define local variables or functions. (Both direct and indirect calls can define new variables only with var. Uses of let and const inside an evaluated string create variables and constants that are local to the evaluation and do not alter the calling or global environment.)\n\nThe following code demonstrates:\n```js\nconst geval = eval;               // Using another name does a global eval\nlet x = \"global\", y = \"global\";   // Two global variables\nfunction f() {                    // This function does a local eval\n    let x = \"local\";              // Define a local variable\n    eval(\"x += 'changed';\");      // Direct eval sets local variable\n    return x;                     // Return changed local variable\n}\nfunction g() {                    // This function does a global eval\n    let y = \"local\";              // A local variable\n    geval(\"y += 'changed';\");     // Indirect eval sets global variable\n    return y;                     // Return unchanged local variable\n}\nconsole.log(f(), x); // Local variable changed: prints \"localchanged global\":\nconsole.log(g(), y); // Global variable changed: prints \"local globalchanged\":\n```\nNotice that the ability to do a global eval is not just an accommodation to the needs of the optimizer; it is actually a tremendously useful feature that allows you to execute strings of code as if they were independent, top-level scripts. As noted at the beginning of this section, it is rare to truly need to evaluate a string of code. But if you do find it necessary, you are more likely to want to do a global eval than a local eval.\n\n### 4.12.3 Strict eval()\nStrict mode (see §5.6.3) imposes further restrictions on the behavior of the eval() function and even on the use of the identifier “eval”. When eval() is called from strict-mode code, or when the string of code to be evaluated itself begins with a “use strict” directive, then eval() does a local eval with a private variable environment. This means that in strict mode, evaluated code can query and set local variables, but it cannot define new variables or functions in the local scope.\n\nFurthermore, strict mode makes eval() even more operator-like by effectively making “eval” into a reserved word. You are not allowed to overwrite the eval() function with a new value. And you are not allowed to declare a variable, function, function parameter, or catch block parameter with the name “eval”.\n\n## 4.13 Miscellaneous Operators\nJavaScript supports a number of other miscellaneous operators, described in the following sections.\n\n### 4.13.1 The Conditional Operator (?:)\nThe conditional operator is the only ternary operator (three operands) in JavaScript and is sometimes actually called the ternary operator. This operator is sometimes written ?:, although it does not appear quite that way in code. Because this operator has three operands, the first goes before the ?, the second goes between the ? and the :, and the third goes after the :. It is used like this:\n```js\nx > 0 ? x : -x     // The absolute value of x\n```\nThe operands of the conditional operator may be of any type. The first operand is evaluated and interpreted as a boolean. If the value of the first operand is truthy, then the second operand is evaluated, and its value is returned. Otherwise, if the first operand is falsy, then the third operand is evaluated and its value is returned. Only one of the second and third operands is evaluated; never both.\n\nWhile you can achieve similar results using the if statement (§5.3.1), the ?: operator often provides a handy shortcut. Here is a typical usage, which checks to be sure that a variable is defined (and has a meaningful, truthy value) and uses it if so or provides a default value if not:\n```js\ngreeting = \"hello \" + (username ? username : \"there\");\n```\nThis is equivalent to, but more compact than, the following if statement:\n```js\ngreeting = \"hello \";\nif (username) {\n    greeting += username;\n} else {\n    greeting += \"there\";\n}\n```\n### 4.13.2 First-Defined (??)\nThe first-defined operator ?? evaluates to its first defined operand: if its left operand is not null and not undefined, it returns that value. Otherwise, it returns the value of the right operand. Like the && and || operators, ?? is short-circuiting: it only evaluates its second operand if the first operand evaluates to null or undefined. If the expression a has no side effects, then the expression a ?? b is equivalent to:\n```js\n(a !== null && a !== undefined) ? a : b\n```\n?? is a useful alternative to || (§4.10.2) when you want to select the first defined operand rather than the first truthy operand. Although || is nominally a logical OR operator, it is also used idiomatically to select the first non-falsy operand with code like this:\n```js\n// If maxWidth is truthy, use that. Otherwise, look for a value in\n// the preferences object. If that is not truthy, use a hardcoded constant.\nlet max = maxWidth || preferences.maxWidth || 500;\n```\nThe problem with this idiomatic use is that zero, the empty string, and false are all falsy values that may be perfectly valid in some circumstances. In this code example, if maxWidth is zero, that value will be ignored. But if we change the || operator to ??, we end up with an expression where zero is a valid value:\n```js\n// If maxWidth is defined, use that. Otherwise, look for a value in\n// the preferences object. If that is not defined, use a hardcoded constant.\nlet max = maxWidth ?? preferences.maxWidth ?? 500;\n```\nHere are more examples showing how ?? works when the first operand is falsy. If that operand is falsy but defined, then ?? returns it. It is only when the first operand is “nullish” (i.e., null or undefined) that this operator evaluates and returns the second operand:\n```js\nlet options = { timeout: 0, title: \"\", verbose: false, n: null };\noptions.timeout ?? 1000     // => 0: as defined in the object\noptions.title ?? \"Untitled\" // => \"\": as defined in the object\noptions.verbose ?? true     // => false: as defined in the object\noptions.quiet ?? false      // => false: property is not defined\noptions.n ?? 10             // => 10: property is null\n```\nNote that the timeout, title, and verbose expressions here would have different values if we used || instead of ??.\n\nThe ?? operator is similar to the && and || operators but does not have higher precedence or lower precedence than they do. If you use it in an expression with either of those operators, you must use explicit parentheses to specify which operation you want to perform first:\n```js\n(a ?? b) || c   // ?? first, then ||\na ?? (b || c)   // || first, then ??\na ?? b || c     // SyntaxError: parentheses are required\n```\nThe ?? operator is defined by ES2020, and as of early 2020, is newly supported by current or beta versions of all major browsers. This operator is formally called the “nullish coalescing” operator, but I avoid that term because this operator selects one of its operands but does not “coalesce” them in any way that I can see.\n\n### 4.13.3 The typeof Operator\ntypeof is a unary operator that is placed before its single operand, which can be of any type. Its value is a string that specifies the type of the operand. Table 4-3 specifies the value of the typeof operator for any JavaScript value.\n\nTable 4-3. Values returned by the typeof operator\n\n| x                      | typeof x    |\n| ---------------------- | ----------- |\n| undefined              | \"undefined\" |\n| null                   | \"object\"    |\n| true or false          | \"boolean\"   |\n| any number or NaN      | \"number\"    |\n| any BigInt             | \"bigint\"    |\n| any string             | \"string\"    |\n| any symbol             | \"symbol\"    |\n| any function           | \"function\"  |\n| any nonfunction object | \"object\"    |\n\nYou might use the typeof operator in an expression like this:\n```js\n// If the value is a string, wrap it in quotes, otherwise, convert\n(typeof value === \"string\") ? \"'\" + value + \"'\" : value.toString()\n```\nNote that typeof returns “object” if the operand value is null. If you want to distinguish null from objects, you’ll have to explicitly test for this special-case value.\n\nAlthough JavaScript functions are a kind of object, the typeof operator considers functions to be sufficiently different that they have their own return value.\n\nBecause typeof evaluates to “object” for all object and array values other than functions, it is useful only to distinguish objects from other, primitive types. In order to distinguish one class of object from another, you must use other techniques, such as the instanceof operator (see §4.9.4), the class attribute (see §14.4.3), or the constructor property (see §9.2.2 and §14.3).\n\n### 4.13.4 The delete Operator\ndelete is a unary operator that attempts to delete the object property or array element specified as its operand. Like the assignment, increment, and decrement operators, delete is typically used for its property deletion side effect and not for the value it returns. Some examples:\n```js\nlet o = { x: 1, y: 2}; // Start with an object\ndelete o.x;            // Delete one of its properties\n\"x\" in o               // => false: the property does not exist anymore\n\nlet a = [1,2,3];       // Start with an array\ndelete a[2];           // Delete the last element of the array\n2 in a                 // => false: array element 2 doesn't exist anymore\na.length               // => 3: note that array length doesn't change, though\n```\nNote that a deleted property or array element is not merely set to the undefined value. When a property is deleted, the property ceases to exist. Attempting to read a nonexistent property returns undefined, but you can test for the actual existence of a property with the in operator (§4.9.3). Deleting an array element leaves a “hole” in the array and does not change the array’s length. The resulting array is sparse (§7.3).\n\ndelete expects its operand to be an lvalue. If it is not an lvalue, the operator takes no action and returns true. Otherwise, delete attempts to delete the specified lvalue. delete returns true if it successfully deletes the specified lvalue. Not all properties can be deleted, however: non-configurable properties (§14.1) are immune from deletion.\n\nIn strict mode, delete raises a SyntaxError if its operand is an unqualified identifier such as a variable, function, or function parameter: it only works when the operand is a property access expression (§4.4). Strict mode also specifies that delete raises a TypeError if asked to delete any non-configurable (i.e., nondeleteable) property. Outside of strict mode, no exception occurs in these cases, and delete simply returns false to indicate that the operand could not be deleted.\n\nHere are some example uses of the delete operator:\n```js\nlet o = {x: 1, y: 2};\ndelete o.x;   // Delete one of the object properties; returns true.\ntypeof o.x;   // Property does not exist; returns \"undefined\".\ndelete o.x;   // Delete a nonexistent property; returns true.\ndelete 1;     // This makes no sense, but it just returns true.\n// Can't delete a variable; returns false, or SyntaxError in strict mode.\ndelete o;\n// Undeletable property: returns false, or TypeError in strict mode.\ndelete Object.prototype;\n```\nWe’ll see the delete operator again in §6.4.\n\n### 4.13.5 The await Operator\nawait was introduced in ES2017 as a way to make asynchronous programming more natural in JavaScript. You will need to read Chapter 13 to understand this operator. Briefly, however, await expects a Promise object (representing an asynchronous computation) as its sole operand, and it makes your program behave as if it were waiting for the asynchronous computation to complete (but it does this without actually blocking, and it does not prevent other asynchronous operations from proceeding at the same time). The value of the await operator is the fulfillment value of the Promise object. Importantly, await is only legal within functions that have been declared asynchronous with the async keyword. Again, see Chapter 13 for full details.\n\n### 4.13.6 The void Operator\nvoid is a unary operator that appears before its single operand, which may be of any type. This operator is unusual and infrequently used; it evaluates its operand, then discards the value and returns undefined. Since the operand value is discarded, using the void operator makes sense only if the operand has side effects.\n\nThe void operator is so obscure that it is difficult to come up with a practical example of its use. One case would be when you want to define a function that returns nothing but also uses the arrow function shortcut syntax (see §8.1.3) where the body of the function is a single expression that is evaluated and returned. If you are evaluating the expression solely for its side effects and do not want to return its value, then the simplest thing is to use curly braces around the function body. But, as an alternative, you could also use the void operator in this case:\n```js\nlet counter = 0;\nconst increment = () => void counter++;\nincrement()   // => undefined\ncounter       // => 1\n```\n### 4.13.7 The comma Operator (,)\nThe comma operator is a binary operator whose operands may be of any type. It evaluates its left operand, evaluates its right operand, and then returns the value of the right operand. Thus, the following line:\n```js\ni=0, j=1, k=2;\n```\nevaluates to 2 and is basically equivalent to:\n```js\ni = 0; j = 1; k = 2;\n```\nThe lefthand expression is always evaluated, but its value is discarded, which means that it only makes sense to use the comma operator when the lefthand expression has side effects. The only situation in which the comma operator is commonly used is with a for loop (§5.4.3) that has multiple loop variables:\n```js\n// The first comma below is part of the syntax of the let statement\n// The second comma is the comma operator: it lets us squeeze 2\n// expressions (i++ and j--) into a statement (the for loop) that expects 1.\nfor(let i=0,j=10; i < j; i++,j--) {\n    console.log(i+j);\n}\n```\n## 4.14 Summary\nThis chapter covers a wide variety of topics, and there is lots of reference material here that you may want to reread in the future as you continue to learn JavaScript. Some key points to remember, however, are these:\n\n- Expressions are the phrases of a JavaScript program.\n- Any expression can be evaluated to a JavaScript value.\n- Expressions can also have side effects (such as variable assignment) in addition to producing a value.\n- Simple expressions such as literals, variable references, and property accesses can be combined with operators to produce larger expressions.\n- JavaScript defines operators for arithmetic, comparisons, Boolean logic, assignment, and bit manipulation, along with some miscellaneous operators, including the ternary conditional operator.\n- The JavaScript + operator is used to both add numbers and concatenate strings.\n- The logical operators && and || have special “short-circuiting” behavior and sometimes only evaluate one of their arguments. Common JavaScript idioms require you to understand the special behavior of these operators."
  },
  {
    "path": "content/posts/ch5.md",
    "content": "---\ntitle: \"第 5 章 语句\"\ndate: 2020-11-02T22:18:38+08:00\n---\n\nChapter 4 described expressions as JavaScript phrases. By that analogy, statements are JavaScript sentences or commands. Just as English sentences are terminated and separated from one another with periods, JavaScript statements are terminated with semicolons (§2.6). Expressions are evaluated to produce a value, but statements are executed to make something happen.\n\n> 第 4 章将表达式描述为 JavaScript 短语。根据这个比喻，语句就是 JavaScript 句子或命令。就像英语句子以句点结尾并彼此分开一样，JavaScript 语句以分号结尾（§2.6）。表达式计算来产生一个值，但是语句被执行则使某些事情发生。\n\nOne way to “make something happen” is to evaluate an expression that has side effects. Expressions with side effects, such as assignments and function invocations, can stand alone as statements, and when used this way are known as expression statements. A similar category of statements are the declaration statements that declare new variables and define new functions.\n\n> “使某事发生”的一种方法是对有副作用的表达式求值。带有副作用（如赋值和函数调用）的表达式可以单独作为语句使用，以这种方式使用时称为表达式语句。类似的语句是声明语句，它声明新的变量和定义的新函数。\n\nJavaScript programs are nothing more than a sequence of statements to execute. By default, the JavaScript interpreter executes these statements one after another in the order they are written. Another way to “make something happen” is to alter this default order of execution, and JavaScript has a number of statements or control structures that do just this:\n\n> JavaScript 程序只不过是一系列顺序执行的语句。默认情况下，JavaScript 解释器按照语句的编写顺序依次执行这些语句。另一种“让事情发生”的方法是改变默认的执行顺序，JavaScript 有很多语句或控制结构就是这样做的:\n\nConditionals\n> 条件\nStatements like if and switch that make the JavaScript interpreter execute or skip other statements depending on the value of an expression\n> 类似 if 和 switch 语句，让 JavaScript 解释器根据一个表达式的值计算或跳过一些语句\n\nLoops\n> 循环\nStatements like while and for that execute other statements repetitively\n> 类似 while 和 for 语句，重复执行一些语句\n\nJumps\n> 跳转\nStatements like break, return, and throw that cause the interpreter to jump to another part of the program\n> 类似 break、return 和 throw 语句，引发解释器跳转到程序的另一部分\n\nThe sections that follow describe the various statements in JavaScript and explain their syntax. Table 5-1, at the end of the chapter, summarizes the syntax. A JavaScript program is simply a sequence of statements, separated from one another with semicolons, so once you are familiar with the statements of JavaScript, you can begin writing JavaScript programs.\n\n> 接下来的小结讲解各种各样的 JavaScript 语句并解释它们的语法。本章最后表 5-1 总结了语法。JavaScript 程序是简单的顺序语句，被分号分隔，所以一旦熟悉了 JavaScript 语句，就可以开始编写 JavaScript 程序了。\n\n## 5.1 Expression Statements\n\nThe simplest kinds of statements in JavaScript are expressions that have side effects. This sort of statement was shown in Chapter 4. Assignment statements are one major category of expression statements. For example:\n\n> JavaScript 中最简单的语句是表达式，它们有副作用。下面这个简短的语句在第 4 章出现过。赋值语句是表达式语句分类中主要的语句之一。例如：\n\n```js\ngreeting = \"Hello \" + name;\ni *= 3;\n```\nThe increment and decrement operators, ++ and --, are related to assignment statements. These have the side effect of changing a variable value, just as if an assignment had been performed:\n\n> 递增和递减运算符 ++ 和 -- 与赋值语句有关。这些具有更改变量值的副作用，就像执行了赋值一样：\n\n```js\ncounter++;\n```\nThe delete operator has the important side effect of deleting an object property. Thus, it is almost always used as a statement, rather than as part of a larger expression:\n\n> delete 运算符具有删除对象属性的重要副作用。因此，它几乎总是用作语句，而不是用作较大表达式的一部分：\n\n```js\ndelete o.x;\n```\nFunction calls are another major category of expression statements. For example:\n\n> 函数调用是表达式语句的另一个主要类别。例如：\n\n```js\nconsole.log(debugMessage);\ndisplaySpinner(); // A hypothetical function to display a spinner in a web app.\n```\nThese function calls are expressions, but they have side effects that affect the host environment or program state, and they are used here as statements. If a function does not have any side effects, there is no sense in calling it, unless it is part of a larger expression or an assignment statement. For example, you wouldn’t just compute a cosine and discard the result:\n\n> 这些函数调用是表达式，但是它们具有影响主机环境或程序状态的副作用，在这里用作语句。如果函数没有任何副作用，则除非有任何较大的表达式或赋值语句的一部分，否则调用该函数没有任何意义。例如，您不会仅计算余弦并丢弃结果：\n\n```js\nMath.cos(x);\n```\nBut you might well compute the value and assign it to a variable for future use:\n\n> 但是可能会计算出该值并将其分配给变量以供将来使用：\n\n```js\ncx = Math.cos(x);\n```\nNote that each line of code in each of these examples is terminated with a semicolon.\n\n> 请注意，这些示例中的每行代码均以分号结尾。\n\n## 5.2 Compound and Empty Statements\nJust as the comma operator (§4.13.7) combines multiple expressions into a single expression, a statement block combines multiple statements into a single compound statement. A statement block is simply a sequence of statements enclosed within curly braces. Thus, the following lines act as a single statement and can be used anywhere that JavaScript expects a single statement:\n\n> 正如逗号运算符（§4.13.7）将多个表达式组合成一个表达式一样，一个语句块将多个语句组合成一个复合语句。语句块只是括在花括号内的一系列语句。因此，以下几行充当单个语句，并且可以在 JavaScript 需要单个语句的任何地方使用：\n\n```js\n{\n    x = Math.PI;\n    cx = Math.cos(x);\n    console.log(\"cos(π) = \" + cx);\n}\n```\nThere are a few things to note about this statement block. First, it does not end with a semicolon. The primitive statements within the block end in semicolons, but the block itself does not. Second, the lines inside the block are indented relative to the curly braces that enclose them. This is optional, but it makes the code easier to read and understand.\n\n> 关于此语句块需要注意一些事项。首先，它不以分号结尾。块中的原始语句以分号结尾，但是块本身没有。其次，相对于包围它们的花括号将块内的行缩进。这是可选的，但是它使代码更易于阅读和理解。\n\nJust as expressions often contain subexpressions, many JavaScript statements contain substatements. Formally, JavaScript syntax usually allows a single substatement. For example, the while loop syntax includes a single statement that serves as the body of the loop. Using a statement block, you can place any number of statements within this single allowed substatement.\n\n> 正如表达式通常包含子表达式一样，许多 JavaScript 语句也包含子语句。正式地，JavaScript 语法通常允许单个子语句。例如，while 循环语法包括一个用作循环主体的语句。使用语句块，可以在此单个允许的子语句中放置任意数量的语句。\n\nA compound statement allows you to use multiple statements where JavaScript syntax expects a single statement. The empty statement is the opposite: it allows you to include no statements where one is expected. The empty statement looks like this:\n\n> 复合语句允许在 JavaScript 语法要求使用单个语句的情况下使用多个语句。空语句则相反：它允许在期望的位置不包含任何语句。空语句如下所示：\n\n```js\n;\n```\nThe JavaScript interpreter takes no action when it executes an empty statement. The empty statement is occasionally useful when you want to create a loop that has an empty body. Consider the following for loop (for loops will be covered in §5.4.3):\n\n> JavaScript解释器执行空语句时不执行任何操作。当要创建一个包含空主体的循环时，空语句有时会很有用。考虑以下 for 循环（for 循环将在 §5.4.3 中介绍）：\n\n```js\n// Initialize an array a\nfor(let i = 0; i < a.length; a[i++] = 0) ;\n```\nIn this loop, all the work is done by the expression `a[i++] = 0`, and no loop body is necessary. JavaScript syntax requires a statement as a loop body, however, so an empty statement—just a bare semicolon—is used.\n\n> 在此循环中，所有工作均由表达式 `a[i++] = 0` 完成，并且不需要循环体。JavaScript 语法需要一个语句作为循环体，因此使用一个空语句（仅一个裸分号）。\n\nNote that the accidental inclusion of a semicolon after the right parenthesis of a for loop, while loop, or if statement can cause frustrating bugs that are difficult to detect. For example, the following code probably does not do what the author intended:\n\n> 请注意，在 for 循环，while 循环或 if 语句的右括号后面意外地包含分号，可能导致令人沮丧的错误，难以检测。例如，以下代码可能不符合作者的意图：\n\n```js\nif ((a === 0) || (b === 0));  // Oops! This line does nothing...\n    o = null;                 // and this line is always executed.\n```\nWhen you intentionally use the empty statement, it is a good idea to comment your code in a way that makes it clear that you are doing it on purpose. For example:\n\n> 当有意使用空语句时，以下注释代码是一种清楚表明是故意这样做的方式的好主意。 例如：\n\n```js\nfor(let i = 0; i < a.length; a[i++] = 0) /* empty */ ;\n```\n## 5.3 Conditionals\nConditional statements execute or skip other statements depending on the value of a specified expression. These statements are the decision points of your code, and they are also sometimes known as “branches.” If you imagine a JavaScript interpreter following a path through your code, the conditional statements are the places where the code branches into two or more paths and the interpreter must choose which path to follow.\n\n> 条件语句根据指定表达式的值执行或跳过其他语句。这些语句是代码的决策点，有时也称为“分支”。如果想象 JavaScript 解释器遵循代码路径，则条件语句是代码分支到两个或更多路径的位置，并且解释器必须选择要遵循的路径。\n\nThe following subsections explain JavaScript’s basic conditional, the if/else statement, and also cover switch, a more complicated, multiway branch statement.\n\n> 以下小节介绍了 JavaScript 的基本条件，if / else 语句，还介绍了 switch，更复杂的多路分支语句。\n\n### 5.3.1 if\nThe if statement is the fundamental control statement that allows JavaScript to make decisions, or, more precisely, to execute statements conditionally. This statement has two forms. The first is:\n\n> if 语句是基本的控制语句，它允许 JavaScript 进行决策，或更准确地说，可以有条件地执行语句。该语句有两种形式。 第一个是：\n\n```js\nif (expression)\n    statement\n```\nIn this form, expression is evaluated. If the resulting value is truthy, statement is executed. If expression is falsy, statement is not executed. (See §3.4 for a definition of truthy and falsy values.) For example:\n\n> 以这种形式，表达式被评估。如果结果值为真，则执行语句。如果 expression 是假，则不执行语句。（有关真实值和假值的定义，请参见 §3.4。）例如：\n\n```js\nif (username == null)       // If username is null or undefined,\n    username = \"John Doe\";  // define it\nOr similarly:\n\n// If username is null, undefined, false, 0, \"\", or NaN, give it a new value\nif (!username) username = \"John Doe\";\n```\nNote that the parentheses around the expression are a required part of the syntax for the if statement.\n\n> 请注意，表达式周围的括号是 if 语句语法的必需部分。\n\nJavaScript syntax requires a single statement after the if keyword and parenthesized expression, but you can use a statement block to combine multiple statements into one. So the if statement might also look like this:\n\n> JavaScript 语法在 if 关键字和带括号的表达式之后需要一个语句，但是可以使用语句块将多个语句组合为一个。因此，if 语句也可能如下所示：\n\n```js\nif (!address) {\n    address = \"\";\n    message = \"Please specify a mailing address.\";\n}\n```\nThe second form of the if statement introduces an else clause that is executed when expression is false. Its syntax is:\n\n> if 语句的第二种形式引入了 else 子句，当 expression 为 false 时将执行该子句。 它的语法是：\n\n```js\nif (expression)\n    statement1\nelse\n    statement2\n```\nThis form of the statement executes statement1 if expression is truthy and executes statement2 if expression is falsy. For example:\n\n> 如果 expression 为真，则此语句形式执行 statement1；如果 expression 为 false，则执行 statement2。 例如：\n\n```js\nif (n === 1)\n    console.log(\"You have 1 new message.\");\nelse\n    console.log(`You have ${n} new messages.`);\n```\nWhen you have nested if statements with else clauses, some caution is required to ensure that the else clause goes with the appropriate if statement. Consider the following lines:\n\n> 当将带有 if 子句的 if 语句嵌套时，需要格外小心，以确保 else 子句与适当的 if 语句一起使用。考虑以下几行：\n\n```js\ni = j = 1;\nk = 2;\nif (i === j)\n    if (j === k)\n        console.log(\"i equals k\");\nelse\n    console.log(\"i doesn't equal j\");    // WRONG!!\n```\nIn this example, the inner if statement forms the single statement allowed by the syntax of the outer if statement. Unfortunately, it is not clear (except from the hint given by the indentation) which if the else goes with. And in this example, the indentation is wrong, because a JavaScript interpreter actually interprets the previous example as:\n\n> 在此示例中，内部 if 语句形成外部 if 语句的语法允许的单个语句。不幸的是，不清楚（除非缩进提示）else 接连在哪个 if 后。 在此示例中，缩进是错误的，因为 JavaScript 解释器实际上将前面的示例解释为：\n\n```js\nif (i === j) {\n    if (j === k)\n        console.log(\"i equals k\");\n    else\n        console.log(\"i doesn't equal j\");    // OOPS!\n}\n```\nThe rule in JavaScript (as in most programming languages) is that by default an else clause is part of the nearest if statement. To make this example less ambiguous and easier to read, understand, maintain, and debug, you should use curly braces:\n\n> JavaScript 中的规则（与大多数编程语言一样）在默认情况下，else 子句是最接近的 if 语句的一部分。为了使此示例不太含糊且易于阅读、理解、维护和调试，应使用花括号： \n\n```js\nif (i === j) {\n    if (j === k) {\n        console.log(\"i equals k\");\n    }\n} else {  // What a difference the location of a curly brace makes!\n    console.log(\"i doesn't equal j\");\n}\n```\nMany programmers make a habit of enclosing the bodies of if and else statements (as well as other compound statements, such as while loops) within curly braces, even when the body consists of only a single statement. Doing so consistently can prevent the sort of problem just shown, and I advise you to adopt this practice. In this printed book, I place a premium on keeping example code vertically compact, and I do not always follow my own advice on this matter.\n\n> 许多程序员习惯将 if 和 else 语句（以及其他复合语句，例如 while 循环）的主体括在花括号内，即使主体仅由一个语句组成也是如此。始终如一地这样做可以防止出现刚才显示的问题，我建议采用这种做法。在这本书中，我非常重视保持示例代码在垂直方向上的紧凑性，并且在此问题上，我并不总是遵循自己的建议。\n\n### 5.3.2 else if\nThe if/else statement evaluates an expression and executes one of two pieces of code, depending on the outcome. But what about when you need to execute one of many pieces of code? One way to do this is with an else if statement. else if is not really a JavaScript statement, but simply a frequently used programming idiom that results when repeated if/else statements are used:\n\n> if/else 语句根据结果评估表达式并执行两段代码之一。但是，当需要执行许多代码之一时，该怎么办呢？一种方法是使用 else if 语句。else if 并不是真正的 JavaScript 语句，而只是一个经常使用的编程习惯用法，当使用重复的 if/else 语句时会产生该习惯用法：\n\n```js\nif (n === 1) {\n    // Execute code block #1\n} else if (n === 2) {\n    // Execute code block #2\n} else if (n === 3) {\n    // Execute code block #3\n} else {\n    // If all else fails, execute block #4\n}\n```\nThere is nothing special about this code. It is just a series of if statements, where each following if is part of the else clause of the previous statement. Using the else if idiom is preferable to, and more legible than, writing these statements out in their syntactically equivalent, fully nested form:\n\n> 此代码没有什么特别的。 它只是一系列的 if 语句，其后的每个 if 都是前一条语句的 else 子句的一部分。 使用 else if 惯用语比以语法上等效的完全嵌套形式写出这些语句更好，更易读：\n\n```js\nif (n === 1) {\n    // Execute code block #1\n}\nelse {\n    if (n === 2) {\n        // Execute code block #2\n    }\n    else {\n        if (n === 3) {\n            // Execute code block #3\n        }\n        else {\n            // If all else fails, execute block #4\n        }\n    }\n}\n```\n### 5.3.3 switch\nAn if statement causes a branch in the flow of a program’s execution, and you can use the else if idiom to perform a multiway branch. This is not the best solution, however, when all of the branches depend on the value of the same expression. In this case, it is wasteful to repeatedly evaluate that expression in multiple if statements.\n\n> if 语句会导致程序执行流程中的分支，可以使用 else if 惯用法来执行多路分支。但是，当所有分支都依赖于同一表达式的值时，这不是最佳解决方案。在这种情况下，在多个 if 语句中重复评估该表达式是浪费性能的。\n\nThe switch statement handles exactly this situation. The switch keyword is followed by an expression in parentheses and a block of code in curly braces:\n\n> switch 语句完全可以处理这种情况。switch 关键字后跟一个括号中的表达式和一个花括号中的代码块：\n\n```js\nswitch(expression) {\n    statements\n}\n```\nHowever, the full syntax of a switch statement is more complex than this. Various locations in the block of code are labeled with the case keyword followed by an expression and a colon. When a switch executes, it computes the value of expression and then looks for a case label whose expression evaluates to the same value (where sameness is determined by the === operator). If it finds one, it starts executing the block of code at the statement labeled by the case. If it does not find a case with a matching value, it looks for a statement labeled default:. If there is no default: label, the switch statement skips the block of code altogether.\n\n> 但是，switch 语句的完整语法比这更复杂。代码块中的各个位置都用 case 关键字标记，后跟一个表达式和一个冒号。switch 执行时，它将计算表达式的值，然后查找其表达式计算为相同值（其中相同性由 === 运算符确定）的case标签。如果找到一个，它将开始在由 case 标记的语句处执行代码块。如果找不到与值匹配的个案，则查找标记为 default: 的语句。 如果没有 default: 标签，则 switch 语句将完全跳过该代码块。\n\nswitch is a confusing statement to explain; its operation becomes much clearer with an example. The following switch statement is equivalent to the repeated if/else statements shown in the previous section:\n\n> switch 是一个令人困惑的陈述，难以解释；举个例子，它的操作变得更加清晰。以下 switch 语句等效于上一节中重复 if/else 语句：\n\n```js\nswitch(n) {\ncase 1:                        // Start here if n === 1\n    // Execute code block #1.\n    break;                     // Stop here\ncase 2:                        // Start here if n === 2\n    // Execute code block #2.\n    break;                     // Stop here\ncase 3:                        // Start here if n === 3\n    // Execute code block #3.\n    break;                     // Stop here\ndefault:                       // If all else fails...\n    // Execute code block #4.\n    break;                     // Stop here\n}\n```\nNote the break keyword used at the end of each case in this code. The break statement, described later in this chapter, causes the interpreter to jump to the end (or “break out”) of the switch statement and continue with the statement that follows it. The case clauses in a switch statement specify only the starting point of the desired code; they do not specify any ending point. In the absence of break statements, a switch statement begins executing its block of code at the case label that matches the value of its expression and continues executing statements until it reaches the end of the block. On rare occasions, it is useful to write code like this that “falls through” from one case label to the next, but 99% of the time you should be careful to end every case with a break statement. (When using switch inside a function, however, you may use a return statement instead of a break statement. Both serve to terminate the switch statement and prevent execution from falling through to the next case.)\n\n> 请注意此代码中每种情况结尾处使用的 break 关键字。在本章后面介绍的 break 语句使解释器跳到 switch 语句的末尾（或“中断”），并继续其后的语句。 switch 语句中的 case 子句仅指定所需代码的起点；他们没有指定任何终点。在没有 break 语句的情况下，switch 语句在与其表达式值匹配的 case 标签处开始执行其代码块，并继续执行语句，直到到达该块的末尾为止。在极少数情况下，编写这样的代码（从一个案例标签到另一个案例）“贯穿”是很有用的，但是在 99％ 的使用中，应小心以 break 语句结束每个案例。（但是，在函数内部使用 switch 时，可以使用 return 语句而不是 break 语句。两者都可用于终止 switch 语句并防止执行陷入下一种情况。）\n\nHere is a more realistic example of the switch statement; it converts a value to a string in a way that depends on the type of the value:\n\n> 这是 switch 语句的一个更实际的示例；它将值转换为字符串的方式取决于值的类型：\n\n```js\nfunction convert(x) {\n    switch(typeof x) {\n    case \"number\":            // Convert the number to a hexadecimal integer\n        return x.toString(16);\n    case \"string\":            // Return the string enclosed in quotes\n        return '\"' + x + '\"';\n    default:                  // Convert any other type in the usual way\n        return String(x);\n    }\n}\n```\nNote that in the two previous examples, the case keywords are followed by number and string literals, respectively. This is how the switch statement is most often used in practice, but note that the ECMAScript standard allows each case to be followed by an arbitrary expression.\n\n> 请注意，在前面的两个示例中，case 关键字后面分别跟有数字和字符串文字。这是实践中最经常使用 switch 语句的方式，但是请注意，ECMAScript 标准允许每种情况后面都可以有一个任意表达式。\n\nThe switch statement first evaluates the expression that follows the switch keyword and then evaluates the case expressions, in the order in which they appear, until it finds a value that matches.1 The matching case is determined using the === identity operator, not the == equality operator, so the expressions must match without any type conversion.\n\n> switch 语句首先评估 switch 关键字后面的表达式，然后按它们出现的顺序评估 case 表达式，直到找到匹配的值。[^1] 匹配的 case 是使用 === 恒等运算符确定的，而不是 == 相等运算符，因此表达式必须匹配，且不进行任何类型转换。\n\nBecause not all of the case expressions are evaluated each time the switch statement is executed, you should avoid using case expressions that contain side effects such as function calls or assignments. The safest course is simply to limit your case expressions to constant expressions.\n\n> 由于并非每次执行 switch 语句时都会评估所有的 case 表达式，因此应避免使用包含副作用的 case 表达式，例如函数调用或赋值。最安全的方法只是将 case 表达式限制为常量表达式。\n\nAs explained earlier, if none of the case expressions match the switch expression, the switch statement begins executing its body at the statement labeled default:. If there is no default: label, the switch statement skips its body altogether. Note that in the examples shown, the default: label appears at the end of the switch body, following all the case labels. This is a logical and common place for it, but it can actually appear anywhere within the body of the statement.\n\n> 如前所述，如果所有 case 表达式都不与 switch 表达式匹配，则 switch 语句将在标有 default: 的语句处开始执行其主体。如果没有 default: 标签，则 switch语句将完全跳过其主体。请注意，在所示示例中，default: 标签出现在 switch 主体的末尾，紧随所有案例标签之后。这是一个逻辑上通用的地方，但实际上它可以出现在语句主体内的任何位置。\n\n## 5.4 Loops\nTo understand conditional statements, we imagined the JavaScript interpreter following a branching path through your source code. The looping statements are those that bend that path back upon itself to repeat portions of your code. JavaScript has five looping statements: while, do/while, for, for/of (and its for/await variant), and for/in. The following subsections explain each in turn. One common use for loops is to iterate over the elements of an array. §7.6 discusses this kind of loop in detail and covers special looping methods defined by the Array class.\n\n> 为了理解条件语句，我们假设 JavaScript 解释器遵循源代码中的分支路径。循环语句是那些使该路径重新弯曲以重复代码部分的语句。JavaScript 有五个循环语句：while、do/while、for、for/of（及其 for/await 变体）和 for/in。以下小节依次解释。循环的一种常见用法是遍历数组的元素。§7.6 详细讨论了这种循环，并涵盖了 Array 类定义的特殊循环方法。\n\n### 5.4.1 while\nJust as the if statement is JavaScript’s basic conditional, the while statement is JavaScript’s basic loop. It has the following syntax:\n\n> 正如 if 语句是 JavaScript 的基本条件一样，while 语句也是 JavaScript 的基本循环。它具有以下语法：\n\n```js\nwhile (expression)\n    statement\n```\nTo execute a while statement, the interpreter first evaluates expression. If the value of the expression is falsy, then the interpreter skips over the statement that serves as the loop body and moves on to the next statement in the program. If, on the other hand, the expression is truthy, the interpreter executes the statement and repeats, jumping back to the top of the loop and evaluating expression again. Another way to say this is that the interpreter executes statement repeatedly while the expression is truthy. Note that you can create an infinite loop with the syntax while(true).\n\n> 要执行 while 语句，解释器首先对表达式求值。如果表达式的值是假值，则解释器将跳过用作循环主体的语句，然后继续执行程序中的下一条语句。另一方面，如果表达式是真值，则解释器将执行该语句并重复，跳回到循环的顶部并再次评估表达式。另一种说法是，解释器在表达式为真时重复执行语句。请注意，可以使用 while(true) 语法创建无限循环。\n\nUsually, you do not want JavaScript to perform exactly the same operation over and over again. In almost every loop, one or more variables change with each iteration of the loop. Since the variables change, the actions performed by executing statement may differ each time through the loop. Furthermore, if the changing variable or variables are involved in expression, the value of the expression may be different each time through the loop. This is important; otherwise, an expression that starts off truthy would never change, and the loop would never end! Here is an example of a while loop that prints the numbers from 0 to 9:\n\n> 通常，不希望 JavaScript 一次又一次地执行完全相同的操作。在几乎每个循环中，一个或多个变量随循环的每次迭代而变化。由于变量会发生变化，因此每次循环执行时，执行语句所执行的动作可能会有所不同。此外，如果表达式中包含一个或多个变化的变量，则每次循环时表达式的值可能会有所不同。这个很重要；否则，以 true 开始的表达式将永远不会改变，循环永远不会结束！这是一个while循环的示例，该循环打印从 0 到 9 的数字：\n\n```js\nlet count = 0;\nwhile(count < 10) {\n    console.log(count);\n    count++;\n}\n```\nAs you can see, the variable count starts off at 0 and is incremented each time the body of the loop runs. Once the loop has executed 10 times, the expression becomes false (i.e., the variable count is no longer less than 10), the while statement finishes, and the interpreter can move on to the next statement in the program. Many loops have a counter variable like count. The variable names i, j, and k are commonly used as loop counters, though you should use more descriptive names if it makes your code easier to understand.\n\n> 如你所见，变量计数从 0 开始，并在每次循环主体运行时递增。循环执行 10 次后，表达式变为 false（即变量计数不小于 10），while 语句结束，解释器可以继续执行程序中的下一个语句。许多循环都有一个计数器变量，例如 count。变量名 i、j 和 k 通常用作循环计数器，但是如果可以使代码更易于理解，则应使用更具描述性的名称。\n\n### 5.4.2 do/while\nThe do/while loop is like a while loop, except that the loop expression is tested at the bottom of the loop rather than at the top. This means that the body of the loop is always executed at least once. The syntax is:\n\n> do/while 循环类似于 while 循环，除了循环表达式是在循环的底部而不是顶部进行测试的。这意味着循环的主体始终至少执行一次。语法为：\n\n```js\ndo\n    statement\nwhile (expression);\n```\nThe do/while loop is less commonly used than its while cousin—in practice, it is somewhat uncommon to be certain that you want a loop to execute at least once. Here’s an example of a do/while loop:\n\n> do/while 循环不如 while 常用——在实践中，确定要至少执行一次循环在某种程度上并不常见。这是 do/while 循环的示例：\n\n```js\nfunction printArray(a) {\n    let len = a.length, i = 0;\n    if (len === 0) {\n        console.log(\"Empty Array\");\n    } else {\n        do {\n            console.log(a[i]);\n        } while(++i < len);\n    }\n}\n```\nThere are a couple of syntactic differences between the do/while loop and the ordinary while loop. First, the do loop requires both the do keyword (to mark the beginning of the loop) and the while keyword (to mark the end and introduce the loop condition). Also, the do loop must always be terminated with a semicolon. The while loop doesn’t need a semicolon if the loop body is enclosed in curly braces.\n\n> do/while 循环和普通的 while 循环之间在语法上有一些区别。首先，do 循环需要同时使用do关键字（以标记循环的开始）和while关键字（以标记结束并引入循环条件）。 另外，do 循环必须始终以分号终止。如果循环主体用花括号括起来，则 while 循环不需要分号。\n\n### 5.4.3 for\nThe for statement provides a looping construct that is often more convenient than the while statement. The for statement simplifies loops that follow a common pattern. Most loops have a counter variable of some kind. This variable is initialized before the loop starts and is tested before each iteration of the loop. Finally, the counter variable is incremented or otherwise updated at the end of the loop body, just before the variable is tested again. In this kind of loop, the initialization, the test, and the update are the three crucial manipulations of a loop variable. The for statement encodes each of these three manipulations as an expression and makes those expressions an explicit part of the loop syntax:\n\n> for 语句提供了一个循环构造，通常比 while 语句更方便。 for 语句简化了遵循通用模式的循环。大多数循环都有某种计数器变量。在循环开始之前初始化此变量，并在每次循环迭代之前对其进行测试。最后，在再次测试变量之前，计数器变量在循环主体的末尾增加或更新。在这种循环中，初始化、测试和更新是循环变量的三个关键操作。for 语句将这三个操作中的每一个都编码为一个表达式，并使这些表达式成为循环语法的显式部分：\n\n```js\nfor(initialize ; test ; increment)\n    statement\n```\ninitialize, test, and increment are three expressions (separated by semicolons) that are responsible for initializing, testing, and incrementing the loop variable. Putting them all in the first line of the loop makes it easy to understand what a for loop is doing and prevents mistakes such as forgetting to initialize or increment the loop variable.\n\n> 初始化、测试和递增三个表达式（用分号分隔）。将它们全部放入循环的第一行可轻松了解 for 循环的作用，并防止诸如忘记初始化或增加循环变量之类的错误。\n\nThe simplest way to explain how a for loop works is to show the equivalent while loop:2\n\n> 解释 for 循环如何工作的最简单方法是显示等效的while循环：[^2]\n\n```js\ninitialize;\nwhile(test) {\n    statement\n    increment;\n}\n```\nIn other words, the initialize expression is evaluated once, before the loop begins. To be useful, this expression must have side effects (usually an assignment). JavaScript also allows initialize to be a variable declaration statement so that you can declare and initialize a loop counter at the same time. The test expression is evaluated before each iteration and controls whether the body of the loop is executed. If test evaluates to a truthy value, the statement that is the body of the loop is executed. Finally, the increment expression is evaluated. Again, this must be an expression with side effects in order to be useful. Generally, it is either an assignment expression, or it uses the ++ or -- operators.\n\n> 换句话说，在循环开始之前，对初始化表达式进行一次求值。该表达式必须具有副作用（通常是赋值）。JavaScript 还允许将初始化用作变量声明语句，以便可以同时声明和初始化循环计数器。测试表达式在每次迭代之前进行评估，并控制是否执行循环主体。如果测试评估为真值，则执行作为循环主体的语句。最后，计算增量表达式。同样，该表达式必须是具有副作用的表达式才有效。通常，它可以是赋值表达式，也可以使用 ++ 或 -- 运算符。\n\nWe can print the numbers from 0 to 9 with a for loop like the following. Contrast it with the equivalent while loop shown in the previous section:\n\n> 我们可以使用如下所示的 for 循环打印 0 到 9 之间的数字。 将其与上一节中显示的等效 while 循环进行对比：\n\n```js\nfor(let count = 0; count < 10; count++) {\n    console.log(count);\n}\n```\nLoops can become a lot more complex than this simple example, of course, and sometimes multiple variables change with each iteration of the loop. This situation is the only place that the comma operator is commonly used in JavaScript; it provides a way to combine multiple initialization and increment expressions into a single expression suitable for use in a for loop:\n\n> 当然，循环比这个简单的例子要复杂得多，有时循环的每次迭代都会改变多个变量。这种情况是在 JavaScript 中唯一使用逗号运算符的地方。它提供了一种将多个初始化和增量表达式组合成适合在 for 循环中使用的单个表达式的方法：\n\n```js\nlet i, j, sum = 0;\nfor(i = 0, j = 10 ; i < 10 ; i++, j--) {\n    sum += i * j;\n}\n```\nIn all our loop examples so far, the loop variable has been numeric. This is quite common but is not necessary. The following code uses a for loop to traverse a linked list data structure and return the last object in the list (i.e., the first object that does not have a next property):\n\n> 到目前为止，在我们所有的循环示例中，循环变量都是数字。这是很常见的，但不是必需的。以下代码使用 for 循环遍历链接列表数据结构并返回列表中的最后一个对象（即第一个不具有 next 属性的对象）：\n\n```js\nfunction tail(o) {                          // Return the tail of linked list o\n    for(; o.next; o = o.next) /* empty */ ; // Traverse while o.next is truthy\n    return o;\n}\n```\nNote that this code has no initialize expression. Any of the three expressions may be omitted from a for loop, but the two semicolons are required. If you omit the test expression, the loop repeats forever, and for(;;) is another way of writing an infinite loop, like while(true).\n\n> 请注意，此代码没有初始化表达式。for 循环可以省略三个表达式中的任何一个，但是需要两个分号。如果省略测试表达式，则循环将永远重复，而 for（;;）是编写无限循环的另一种方式，例如 while(true)。\n\n### 5.4.4 for/of\nES6 defines a new loop statement: for/of. This new kind of loop uses the for keyword but is a completely different kind of loop than the regular for loop. (It is also completely different than the older for/in loop that we’ll describe in §5.4.5.)\n\n> ES6 定义了一个新的循环语句：for/of。这种新的循环使用 for 关键字，但是与常规的 for 循环完全不同。（它也与我们在 §5.4.5 中描述的较早的 for/in 循环完全不同。）\n\nThe for/of loop works with iterable objects. We’ll explain exactly what it means for an object to be iterable in Chapter 12, but for this chapter, it is enough to know that arrays, strings, sets, and maps are iterable: they represent a sequence or set of elements that you can loop or iterate through using a for/of loop.\n\n> for/of 循环适用于可迭代对象。我们将在第 12 章中确切解释对象可迭代的含义，但是对于本章而言，知道数组、字符串、set 和 map 是可迭代的足以：它们代表了所需要可以使用 for/of 循环的序列或元素集合。\n\nHere, for example, is how we can use for/of to loop through the elements of an array of numbers and compute their sum:\n\n> 例如，这里是我们如何使用 for/of 遍历数字数组的元素并计算它们的总和的方法：\n\n```js\nlet data = [1, 2, 3, 4, 5, 6, 7, 8, 9], sum = 0;\nfor(let element of data) {\n    sum += element;\n}\nsum       // => 45\n```\nSuperficially, the syntax looks like a regular for loop: the for keyword is followed by parentheses that contain details about what the loop should do. In this case, the parentheses contain a variable declaration (or, for variables that have already been declared, simply the name of the variable) followed by the of keyword and an expression that evaluates to an iterable object, like the data array in this case. As with all loops, the body of a for/of loop follows the parentheses, typically within curly braces.\n\n> 从表面上看，语法看起来像是常规的 for 循环：for 关键字后面是括号，其中包含有关循环应执行的操作的详细信息。在这种情况下，括号中包含一个变量声明（或者对于已经声明的变量，仅是变量的名称），后跟 of 关键字和一个表达式，该表达式的结果为可迭代对象，例如本例中的数据数组。与所有循环一样，for/of 循环的主体跟随括号后，通常在花括号内。\n\nIn the code just shown, the loop body runs once for each element of the data array. Before each execution of the loop body, the next element of the array is assigned to the element variable. Array elements are iterated in order from first to last.\n\n> 在刚刚显示的代码中，循环主体为数据数组的每个元素运行一次。在每次执行循环主体之前，将数组的下一个元素分配给 element 变量。数组元素从第一个到最后一个顺序进行迭代。\n\nArrays are iterated “live”—changes made during the iteration may affect the outcome of the iteration. If we modify the preceding code by adding the line data.push(sum); inside the loop body, then we create an infinite loop because the iteration can never reach the last element of the array.\n\n> 数组是“实时”迭代的，在迭代过程中进行的更改可能会影响迭代的结果。如果我们通过添加行 `data.push(sum);` 在循环体内修改前面的代码，我们则创建一个无限循环，因为迭代永远无法到达数组的最后一个元素。\n\n#### FOR/OF WITH OBJECTS\n\nObjects are not (by default) iterable. Attempting to use for/of on a regular object throws a TypeError at runtime:\n\n> 对象是不可迭代的（默认情况下）。尝试在常规对象上使用 for/of 会在运行时引发 TypeError：\n\n```js\nlet o = { x: 1, y: 2, z: 3 };\nfor(let element of o) { // Throws TypeError because o is not iterable\n    console.log(element);\n}\n```\nIf you want to iterate through the properties of an object, you can use the for/in loop (introduced in §5.4.5), or use for/of with the Object.keys() method:\n\n> 如果要遍历对象的属性，则可以使用 for/in 循环（在 §5.4.5 中介绍），或通过 Object.keys() 方法使用 for/of：\n\n```js\nlet o = { x: 1, y: 2, z: 3 };\nlet keys = \"\";\nfor(let k of Object.keys(o)) {\n    keys += k;\n}\nkeys  // => \"xyz\"\n```\nThis works because Object.keys() returns an array of property names for an object, and arrays are iterable with for/of. Note also that this iteration of the keys of an object is not live as the array example above was—changes to the object o made in the loop body will have no effect on the iteration. If you don’t care about the keys of an object, you can also iterate through their corresponding values like this:\n\n> 之所以可行，是因为 Object.keys() 返回一个对象的属性名称数组，并且该数组可以使用 for/of 进行迭代。还要注意，对象的键的这种迭代不像上面的数组示例那样有效——在循环主体中对对象 o 所做的更改将对该迭代没有影响。 如果您不关心对象的键，也可以像下面这样遍历它们的对应值：\n\n```js\nlet sum = 0;\nfor(let v of Object.values(o)) {\n    sum += v;\n}\nsum // => 6\n```\nAnd if you are interested in both the keys and the values of an object’s properties, you can use for/of with Object.entries() and destructuring assignment:\n\n> 而且，如果对对象属性的键和值都感兴趣，则可以将 Object.entries() 与 for/of 一起使用，并销毁赋值：\n\n```js\nlet pairs = \"\";\nfor(let [k, v] of Object.entries(o)) {\n    pairs += k + v;\n}\npairs  // => \"x1y2z3\"\n```\nObject.entries() returns an array of arrays, where each inner array represents a key/value pair for one property of the object. We use destructuring assignment in this code example to unpack those inner arrays into two individual variables.\n\n> Object.entries() 返回一个数组的数组，其中每个内部数组代表对象一个属性的键 / 值对。在此代码示例中，我们使用解构将这些内部数组拆成两个单独的变量。\n\n#### FOR/OF WITH STRINGS\n\nStrings are iterable character-by-character in ES6:\n\n> 在 ES6 中，字符串是一个一个字符可迭代对象：\n\n```js\nlet frequency = {};\nfor(let letter of \"mississippi\") {\n    if (frequency[letter]) {\n        frequency[letter]++;\n    } else {\n        frequency[letter] = 1;\n    }\n}\nfrequency   // => {m: 1, i: 4, s: 4, p: 2}\n```\nNote that strings are iterated by Unicode codepoint, not by UTF-16 character. The string “I ❤ ” has a .length of 5 (because the two emoji characters each require two UTF-16 characters to represent). But if you iterate that string with for/of, the loop body will run three times, once for each of the three code points “I”, “❤”, and “.”\n\n> 请注意，字符串是通过 Unicode 麻点而不是 UTF-16 字符进行迭代的。字符串“ I❤”的长度为 5（因为两个表情符号字符每个都需要两个 UTF-16 字符来表示）。但是，如果使用 for/of 迭代该字符串，则循环主体将运行三次，对于三个麻点“ I”，“❤”和“.”中的每一个都运行一次。\n\n#### FOR/OF WITH SET AND MAP\n\nThe built-in ES6 Set and Map classes are iterable. When you iterate a Set with for/of, the loop body runs once for each element of the set. You could use code like this to print the unique words in a string of text:\n\n> ES6 内置的 Set 和 Map 类是可迭代的。当使用 for/of 迭代 Set 时，循环主体对 set 的每个元素运行一次。可以使用如下代码在文本字符串中打印出唯一的单词：\n\n```js\nlet text = \"Na na na na na na na na Batman!\";\nlet wordSet = new Set(text.split(\" \"));\nlet unique = [];\nfor(let word of wordSet) {\n    unique.push(word);\n}\nunique // => [\"Na\", \"na\", \"Batman!\"]\n```\nMaps are an interesting case because the iterator for a Map object does not iterate the Map keys, or the Map values, but key/value pairs. Each time through the iteration, the iterator returns an array whose first element is a key and whose second element is the corresponding value. Given a Map m, you could iterate and destructure its key/value pairs like this:\n\n> Map 是一种有趣的情况，因为 Map 对象的迭代器不会迭代 Map 键或 Map 值，而是键 / 值对。每次迭代时，迭代器都会返回一个数组，该数组的第一个元素是键，而第二个元素是对应的值。给定一个 Map m，可以像这样迭代和解构其键 / 值对：\n\n```js\nlet m = new Map([[1, \"one\"]]);\nfor(let [key, value] of m) {\n    key    // => 1\n    value  // => \"one\"\n}\n```\n#### ASYNCHRONOUS ITERATION WITH FOR/AWAIT\n\nES2018 introduces a new kind of iterator, known as an asynchronous iterator, and a variant on the for/of loop, known as the for/await loop that works with asynchronous iterators.\n\n> ES2018 引入了一种新型的迭代器，称为异步迭代器，以及 for/of 循环的一种变体，即与异步迭代器一起使用的 for/await 循环。\n\nYou’ll need to read Chapters 12 and 13 in order to understand the for/await loop, but here is how it looks in code:\n\n> 需要阅读第 12 章和第 13 章，才能了解 for/await 循环，这里展示一下它的代码：\n\n```js\n// Read chunks from an asynchronously iterable stream and print them out\nasync function printStream(stream) {\n    for await (let chunk of stream) {\n        console.log(chunk);\n    }\n}\n```\n### 5.4.5 for/in\nA for/in loop looks a lot like a for/of loop, with the of keyword changed to in. While a for/of loop requires an iterable object after the of, a for/in loop works with any object after the in. The for/of loop is new in ES6, but for/in has been part of JavaScript since the very beginning (which is why it has the more natural sounding syntax).\n\n> for/in 循环看起来很像 for/of 循环，将 of 关键字更改为 in。for/of 循环在 of 之后需要可迭代的对象，而 for/in 循环则在 in 之后可用于任何对象。for/of 循环是 ES6 中的新功能，但是 for/in 从一开始就已经是 JavaScript 的一部分（这就是为什么它具有更自然的发音语法）。\n\nThe for/in statement loops through the property names of a specified object. The syntax looks like this:\n\n> for/in 语句循环遍历指定对象的属性名称。语法如下所示：\n\n```js\nfor (variable in object)\n    statement\n```\nvariable typically names a variable, but it may be a variable declaration or anything suitable as the left-hand side of an assignment expression. object is an expression that evaluates to an object. As usual, statement is the statement or statement block that serves as the body of the loop.\n\n> 变量通常命名为变量，但它可以是变量声明或任何适合作为赋值表达式左侧的内容。object 是一个计算结果为对象的表达式。像往常一样，statement 是用作循环正文的语句或语句块。\n\nAnd you might use a for/in loop like this:\n\n> 可能会使用如下所示的 for/in 循环：\n\n```js\nfor(let p in o) {      // Assign property names of o to variable p\n    console.log(o[p]); // Print the value of each property\n}\n```\nTo execute a for/in statement, the JavaScript interpreter first evaluates the object expression. If it evaluates to null or undefined, the interpreter skips the loop and moves on to the next statement. The interpreter now executes the body of the loop once for each enumerable property of the object. Before each iteration, however, the interpreter evaluates the variable expression and assigns the name of the property (a string value) to it.\n\n> 为了执行 for/in 语句，JavaScript 解释器首先评估对象表达式。如果评估结果为 null 或未定义，则解释器将跳过循环并继续执行下一条语句。现在，解释器对对象的每个可枚举属性执行一次循环主体。但是，在每次迭代之前，解释器都会对变量表达式求值，并为其分配属性名称（字符串值）。\n\nNote that the variable in the for/in loop may be an arbitrary expression, as long as it evaluates to something suitable for the left side of an assignment. This expression is evaluated each time through the loop, which means that it may evaluate differently each time. For example, you can use code like the following to copy the names of all object properties into an array:\n\n> 请注意，for/in 循环中的变量可以是任意表达式，只要它的计算结果适合于赋值的左侧即可。每次通过循环都会对该表达式进行求值，这意味着它每次都可能会进行不同的求值。例如，可以使用以下代码将所有对象属性的名称复制到数组中：\n\n```js\nlet o = { x: 1, y: 2, z: 3 };\nlet a = [], i = 0;\nfor(a[i++] in o) /* empty */;\n```\nJavaScript arrays are simply a specialized kind of object, and array indexes are object properties that can be enumerated with a for/in loop. For example, following the previous code with this line enumerates the array indexes 0, 1, and 2:\n\n> JavaScript 数组只是一种特殊的对象，而数组索引是可以用 for/in 循环枚举的对象属性。例如，在前面的代码之后加上此行，将枚举数组索引 0、1 和 2：\n\n```js\nfor(let i in a) console.log(i);\n```\nI find that a common source of bugs in my own code is the accidental use of for/in with arrays when I meant to use for/of. When working with arrays, you almost always want to use for/of instead of for/in.\n\n> 我发现我自己的代码中常见的错误源是当我打算使用 for/of 时偶然将 for/in 与数组结合使用。使用数组时，几乎总是要使用 for/of 而不是 for/in。\n\nThe for/in loop does not actually enumerate all properties of an object. It does not enumerate properties whose names are symbols. And of the properties whose names are strings, it only loops over the enumerable properties (see §14.1). The various built-in methods defined by core JavaScript are not enumerable. All objects have a toString() method, for example, but the for/in loop does not enumerate this toString property. In addition to built-in methods, many other properties of the built-in objects are non-enumerable. All properties and methods defined by your code are enumerable, by default. (You can make them non-enumerable using techniques explained in §14.1.)\n\n> for/in 循环实际上并未枚举对象的所有属性。它不枚举名称为符号的属性。在名称为字符串的属性中，它仅循环可枚举的属性（请参见第14.1节）。核心 JavaScript 定义的各种内置方法是无法枚举的。例如，所有对象都具有 toString() 方法，但是 for/in 循环不会枚举此 toString 属性。除了内置方法之外，内置对象的许多其他属性也是不可枚举的。 默认情况下，代码定义的所有属性和方法都是可枚举的。（可以使用 §14.1 中介绍的技术使它们不可枚举。）\n\nEnumerable inherited properties (see §6.3.2) are also enumerated by the for/in loop. This means that if you use for/in loops and also use code that defines properties that are inherited by all objects, then your loop may not behave in the way you expect. For this reason, many programmers prefer to use a for/of loop with Object.keys() instead of a for/in loop.\n\n> for/in 循环还枚举了可枚举的继承属性（参见 §6.3.2）。这意味着，如果使用 for/in 循环，并且还使用定义所有对象都继承的属性的代码，则的循环可能无法按照您期望的方式运行。因此，许多程序员更喜欢对 Object.keys() 使用 for/of 循环，而不是 for/in 循环。\n\nIf the body of a for/in loop deletes a property that has not yet been enumerated, that property will not be enumerated. If the body of the loop defines new properties on the object, those properties may or may not be enumerated. See §6.6.1 for more information on the order in which for/in enumerates the properties of an object.\n\n> 如果 for/in 循环的主体删除了尚未枚举的属性，则不会枚举该属性。如果循环的主体在对象上定义了新属性，则这些属性可能会也可能不会被枚举。有关 for/in 枚举对象属性的顺序的更多信息，请参见 §6.6.1。\n\n## 5.5 Jumps\nAnother category of JavaScript statements are jump statements. As the name implies, these cause the JavaScript interpreter to jump to a new location in the source code. The break statement makes the interpreter jump to the end of a loop or other statement. continue makes the interpreter skip the rest of the body of a loop and jump back to the top of a loop to begin a new iteration. JavaScript allows statements to be named, or labeled, and break and continue can identify the target loop or other statement label.\n\n> JavaScript 语句的另一类是跳转语句。顾名思义，这些会导致 JavaScript 解释器跳至源代码中的新位置。break 语句使解释器跳到循环或其他语句的末尾。continue 使解释器跳过循环主体的其余部分，然后跳回到循环的顶部以开始新的迭代。JavaScript 允许对语句进行命名或标记，并且 break 和 continue 可以标识目标循环或其他语句标签。\n\nThe return statement makes the interpreter jump from a function invocation back to the code that invoked it and also supplies the value for the invocation. The throw statement is a kind of interim return from a generator function. The throw statement raises, or throws, an exception and is designed to work with the try/catch/finally statement, which establishes a block of exception-handling code. This is a complicated kind of jump statement: when an exception is thrown, the interpreter jumps to the nearest enclosing exception handler, which may be in the same function or up the call stack in an invoking function.\n\n> return 语句使解释器从函数调用跳回到调用它的代码，并提供该调用的值。throw 语句是生成器函数的一种临时返回。throw 语句引发或抛出异常，并设计为与 try/catch/finally 语句一起使用，该语句建立一个异常处理代码块。这是一种复杂的跳转语句：抛出异常时，解释器将跳转到最近的封闭异常处理程序，该处理程序可以在同一函数中，也可以在调用函数中的调用堆栈中向上移动。\n\nDetails about each of these jump statements are in the sections that follow.\n\n> 有关这些跳转语句的详细信息，请参见以下各节。\n\n### 5.5.1 Labeled Statements\nAny statement may be labeled by preceding it with an identifier and a colon:\n\n> 任何语句都可以在其前面加上标识符和冒号来标记：\n\nidentifier: statement\n\n> 标识符: 语句\n\nBy labeling a statement, you give it a name that you can use to refer to it elsewhere in your program. You can label any statement, although it is only useful to label statements that have bodies, such as loops and conditionals. By giving a loop a name, you can use break and continue statements inside the body of the loop to exit the loop or to jump directly to the top of the loop to begin the next iteration. break and continue are the only JavaScript statements that use statement labels; they are covered in the following subsections. Here is an example of a labeled while loop and a continue statement that uses the label.\n\n> 通过给语句加标签，可以为它指定一个名称，可以使用该名称在程序的其他位置引用它。可以标记任何语句，尽管只有于标记具有主体（例如循环和条件）的语句才有用。通过给循环命名，可以在循环体内使用 break 和 continue 语句退出循环或直接跳转到循环顶部以开始下一个迭代。break 和 continue 是唯一使用语句标签的 JavaScript 语句；它们在以下小节中介绍。这是一个带有标签的 while 循环和一个使用该标签的 continue 语句的示例。\n\n```js\nmainloop: while(token !== null) {\n    // Code omitted...\n    continue mainloop;  // Jump to the next iteration of the named loop\n    // More code omitted...\n}\n```\nThe identifier you use to label a statement can be any legal JavaScript identifier that is not a reserved word. The namespace for labels is different than the namespace for variables and functions, so you can use the same identifier as a statement label and as a variable or function name. Statement labels are defined only within the statement to which they apply (and within its substatements, of course). A statement may not have the same label as a statement that contains it, but two statements may have the same label as long as neither one is nested within the other. Labeled statements may themselves be labeled. Effectively, this means that any statement may have multiple labels.\n\n> 用于标记语句的标识符可以是非保留字的任何合法 JavaScript 标识符。标签的命名空间与变量和函数的命名空间不同，因此可以将相同的标识符用作语句标签以及变量或函数名称。语句标签仅在它们适用的语句内定义（当然，在其子语句中也定义）。一条语句可能没有与包含该语句的语句相同的标签，但是两个语句都可以具有相同的标签，只要两个语句都不嵌套在另一个语句中即可。带标签的语句本身可以被标记。实际上，这意味着任何语句都可以具有多个标签。\n\n### 5.5.2 break\nThe break statement, used alone, causes the innermost enclosing loop or switch statement to exit immediately. Its syntax is simple:\n\n> 单独使用 break 语句会使最里面的循环或 switch 语句立即退出。它的语法很简单：\n\n```js\nbreak;\n```\nBecause it causes a loop or switch to exit, this form of the break statement is legal only if it appears inside one of these statements.\n\n> 因为它导致循环或开关退出，所以这种形式的break语句仅当出现在这些语句之一内时才是合法的。\n\nYou’ve already seen examples of the break statement within a switch statement. In loops, it is typically used to exit prematurely when, for whatever reason, there is no longer any need to complete the loop. When a loop has complex termination conditions, it is often easier to implement some of these conditions with break statements rather than trying to express them all in a single loop expression. The following code searches the elements of an array for a particular value. The loop terminates in the normal way when it reaches the end of the array; it terminates with a break statement if it finds what it is looking for in the array:\n\n> 您已经在 switch 语句中看到了 break 语句的示例。在循环中，通常由于某种原因而不再需要完成循环时，它会过早退出。当循环具有复杂的终止条件时，通常使用 break 语句更容易实现其中一些条件，而不是尝试在单个循环表达式中全部表达它们。以下代码在数组的元素中搜索特定值。当循环到达数组末尾时，循环以通常的方式终止。如果在数组中找到要查找的内容，则以 break 语句终止：\n\n```js\nfor(let i = 0; i < a.length; i++) {\n    if (a[i] === target) break;\n}\n```\nJavaScript also allows the break keyword to be followed by a statement label (just the identifier, with no colon):\n\n> JavaScript 还允许 break 关键字后跟一个语句标签（只是标识符，没有冒号）：\n\n```\nbreak labelname;\n```\nWhen break is used with a label, it jumps to the end of, or terminates, the enclosing statement that has the specified label. It is a syntax error to use break in this form if there is no enclosing statement with the specified label. With this form of the break statement, the named statement need not be a loop or switch: break can “break out of” any enclosing statement. This statement can even be a statement block grouped within curly braces for the sole purpose of naming the block with a label.\n\n> 当 break 与标签一起使用时，它跳转到具有指定标签的封闭语句的末尾或终止。如果没有带有指定标签的封闭语句，则使用这种形式的 break 是语法错误。 使用 break 语句的这种形式，命名的语句不必是循环或 switch：break 可以“跳出”任何封闭的语句。该语句甚至可以是用大括号括起来的语句块，其唯一目的是用标签命名该块。\n\nA newline is not allowed between the break keyword and the label name. This is a result of JavaScript’s automatic insertion of omitted semicolons: if you put a line terminator between the break keyword and the label that follows, JavaScript assumes you meant to use the simple, unlabeled form of the statement and treats the line terminator as a semicolon. (See §2.6.)\n\n> 在 break 关键字和标签名之间不允许使用换行符。 这是 JavaScript 自动插入省略的分号的结果：如果在 break 关键字和后面的标签之间放置了行终止符，则 JavaScript 会假定您打算使用语句的简单、无标签形式并将行终止符视为分号 。（请参见 §2.6）\n\nYou need the labeled form of the break statement when you want to break out of a statement that is not the nearest enclosing loop or a switch. The following code demonstrates:\n\n> 当想脱离不是最近的封闭循环或 switch 的语句时，需要使用 break 语句的标签形式。以下代码演示：\n\n```js\nlet matrix = getData();  // Get a 2D array of numbers from somewhere\n// Now sum all the numbers in the matrix.\nlet sum = 0, success = false;\n// Start with a labeled statement that we can break out of if errors occur\ncomputeSum: if (matrix) {\n    for(let x = 0; x < matrix.length; x++) {\n        let row = matrix[x];\n        if (!row) break computeSum;\n        for(let y = 0; y < row.length; y++) {\n            let cell = row[y];\n            if (isNaN(cell)) break computeSum;\n            sum += cell;\n        }\n    }\n    success = true;\n}\n// The break statements jump here. If we arrive here with success == false\n// then there was something wrong with the matrix we were given.\n// Otherwise, sum contains the sum of all cells of the matrix.\n```\nFinally, note that a break statement, with or without a label, can not transfer control across function boundaries. You cannot label a function definition statement, for example, and then use that label inside the function.\n\n> 最后，请注意，带有或不带有标签的 break 语句无法跨函数边界转移控制权。例如，不能标记函数定义语句，然后在函数内部使用该标记。\n\n### 5.5.3 continue\nThe continue statement is similar to the break statement. Instead of exiting a loop, however, continue restarts a loop at the next iteration. The continue statement’s syntax is just as simple as the break statement’s:\n\n> continue 语句类似于 break 语句。但是，continue 不是退出循环，而是在重新开始循环的下一次迭代。continue 语句的语法与 break 语句的语法一样简单：\n\n```js\ncontinue;\n```\n\nThe continue statement can also be used with a label:\n\n> continue 语句也可以与标签一起使用：\n\n```js\ncontinue labelname;\n```\n\nThe continue statement, in both its labeled and unlabeled forms, can be used only within the body of a loop. Using it anywhere else causes a syntax error.\n\n> 标记和未标记形式的 continue 语句只能在循环体内使用。在其他任何地方使用它都会导致语法错误。\n\nWhen the continue statement is executed, the current iteration of the enclosing loop is terminated, and the next iteration begins. This means different things for different types of loops:\n\n> 当执行 continue 语句时，封闭循环的当前迭代将终止，并且下一个迭代开始。对于不同类型的循环，这意味着不同的事情：\n\nIn a while loop, the specified expression at the beginning of the loop is tested again, and if it’s true, the loop body is executed starting from the top.\n\n> 在 while 循环中，将再次测试循环开始处的指定表达式，如果该表达式为 true，则循环主体将从顶部开始执行。\n\nIn a do/while loop, execution skips to the bottom of the loop, where the loop condition is tested again before restarting the loop at the top.\n\n> 在 do/while 循环中，执行跳到循环的底部，在重新测试顶部的循环之前，先对循环条件进行测试。\n\nIn a for loop, the increment expression is evaluated, and the test expression is tested again to determine if another iteration should be done.\n\n> 在 for 循环中，对增量表达式进行求值，然后再次对测试表达式进行测试，以确定是否应执行另一次迭代。\n\nIn a for/of or for/in loop, the loop starts over with the next iterated value or next property name being assigned to the specified variable.\n\n> 在 for/of 或 for/in 循环中，循环从下一个迭代值或下一个属性名称分配给指定变量开始。\n\nNote the difference in behavior of the continue statement in the while and for loops: a while loop returns directly to its condition, but a for loop first evaluates its increment expression and then returns to its condition. Earlier, we considered the behavior of the for loop in terms of an “equivalent” while loop. Because the continue statement behaves differently for these two loops, however, it is not actually possible to perfectly simulate a for loop with a while loop alone.\n\n> 注意 while 和 for 循环中 continue 语句的行为差异：while 循环直接返回其条件，但是 for 循环首先计算其增量表达式，然后返回其条件。之前，我们以“等效” while 循环的形式考虑了 for 循环的行为。但是，由于 continue 语句在这两个循环中的行为不同，因此实际上不可能仅使用 while 循环来完美地模拟 for 循环。\n\nThe following example shows an unlabeled continue statement being used to skip the rest of the current iteration of a loop when an error occurs:\n\n> 以下示例显示了一个未标记的 continue 语句，该语句在发生错误时用于跳过循环的当前迭代的其余部分：\n\n```js\nfor(let i = 0; i < data.length; i++) {\n    if (!data[i]) continue;  // Can't proceed with undefined data\n    total += data[i];\n}\n```\nLike the break statement, the continue statement can be used in its labeled form within nested loops when the loop to be restarted is not the immediately enclosing loop. Also, as with the break statement, line breaks are not allowed between the continue statement and its label name.\n\n> 像 break 语句一样，当要重新启动的循环不是立即封闭的循环时，可以在嵌套循环内以标记形式使用 continue 语句。同样，与 break 语句一样，在 continue 语句及其标签名之间不允许换行。\n\n### 5.5.4 return\nRecall that function invocations are expressions and that all expressions have values. A return statement within a function specifies the value of invocations of that function. Here’s the syntax of the return statement:\n\n> 回想一下，函数调用是表达式，并且所有表达式都具有值。函数内的 return 语句指定该函数的调用值。这是 return 语句的语法：\n\n```js\nreturn expression;\n```\n\nA return statement may appear only within the body of a function. It is a syntax error for it to appear anywhere else. When the return statement is executed, the function that contains it returns the value of expression to its caller. For example:\n\n> return 语句可能仅出现在函数体内。它出现在其他任何地方都是语法错误。当执行 return 语句时，包含它的函数会将 expression 的值返回给其调用者。例如：\n\n```js\nfunction square(x) { return x*x; } // A function that has a return statement\nsquare(2)                          // => 4\n```\nWith no return statement, a function invocation simply executes each of the statements in the function body in turn until it reaches the end of the function and then returns to its caller. In this case, the invocation expression evaluates to undefined. The return statement often appears as the last statement in a function, but it need not be last: a function returns to its caller when a return statement is executed, even if there are other statements remaining in the function body.\n\n> 在没有 return 语句的情况下，函数调用只是依次依次执行函数体中的每个语句，直到到达函数末尾，然后返回到其调用者。在这种情况下，调用表达式的计算结果为 undefined。return 语句通常显示为函数中的最后一条语句，但不一定要最后一条：执行 return 语句时，函数返回到其调用者，即使函数体内还有其他语句。\n\nThe return statement can also be used without an expression to make the function return undefined to its caller. For example:\n\n> 还可以在没有表达式的情况下使用 return 语句，以使函数返回未定义给调用者。例如：\n\n```js\nfunction displayObject(o) {\n    // Return immediately if the argument is null or undefined.\n    if (!o) return;\n    // Rest of function goes here...\n}\n```\nBecause of JavaScript’s automatic semicolon insertion (§2.6), you cannot include a line break between the return keyword and the expression that follows it.\n\n> 由于 JavaScript 自动插入了分号（§2.6），因此不能在 return 关键字和其后的表达式之间包含换行符。\n\n### 5.5.5 yield\nThe yield statement is much like the return statement but is used only in ES6 generator functions (see §12.3) to produce the next value in the generated sequence of values without actually returning:\n\n> yield 语句与 return 语句非常相似，但仅在 ES6 生成器函数（请参阅 §12.3）中使用，以在所生成的值序列中生成下一个值，而无需实际返回：\n\n```js\n// A generator function that yields a range of integers\nfunction* range(from, to) {\n    for(let i = from; i <= to; i++) {\n        yield i;\n    }\n}\n```\nIn order to understand yield, you must understand iterators and generators, which will not be covered until Chapter 12. yield is included here for completeness, however. (Technically, though, yield is an operator rather than a statement, as explained in §12.4.2.)\n\n> 为了了解 yield，必须了解迭代器和生成器，但是为了完整起见，直到第 12 章都不会涉及。（但是，从技术上讲，yield 是运算符，而不是语句，如 §12.4.2 节中所述。）\n\n### 5.5.6 throw\nAn exception is a signal that indicates that some sort of exceptional condition or error has occurred. To throw an exception is to signal such an error or exceptional condition. To catch an exception is to handle it—to take whatever actions are necessary or appropriate to recover from the exception. In JavaScript, exceptions are thrown whenever a runtime error occurs and whenever the program explicitly throws one using the throw statement. Exceptions are caught with the try/catch/finally statement, which is described in the next section.\n\n> 异常是表示已发生某种异常情况或错误的信号。引发异常是表示这种错误或异常情况。捕获异常就是对它进行处理——采取必要措施或适当措施以从异常中恢复。在 JavaScript 中，只要发生运行时错误，并且只要程序使用 throw 语句显式抛出一个异常，就会引发异常。使用 try/catch/finally 语句捕获异常，这将在下一部分中进行描述。\n\nThe throw statement has the following syntax:\n\n> throw语句具有以下语法：\n\n```js\nthrow expression;\n```\n\nexpression may evaluate to a value of any type. You might throw a number that represents an error code or a string that contains a human-readable error message. The Error class and its subclasses are used when the JavaScript interpreter itself throws an error, and you can use them as well. An Error object has a name property that specifies the type of error and a message property that holds the string passed to the constructor function. Here is an example function that throws an Error object when invoked with an invalid argument:\n\n> 表达式可以计算为任何类型的值。可能会抛出代表错误代码的数字或包含人类可读错误消息的字符串。当 JavaScript 解释器本身抛出错误时，将使用 Error 类及其子类，并且您也可以使用它们。Error 对象具有用于指定错误类型的 name 属性和用于保存传递给构造函数的字符串的 message 属性。这是一个示例函数，当使用无效参数调用该函数时，将引发 Error 对象：\n\n```js\nfunction factorial(x) {\n    // If the input argument is invalid, throw an exception!\n    if (x < 0) throw new Error(\"x must not be negative\");\n    // Otherwise, compute a value and return normally\n    let f;\n    for(f = 1; x > 1; f *= x, x--) /* empty */ ;\n    return f;\n}\nfactorial(4)   // => 24\n```\nWhen an exception is thrown, the JavaScript interpreter immediately stops normal program execution and jumps to the nearest exception handler. Exception handlers are written using the catch clause of the try/catch/finally statement, which is described in the next section. If the block of code in which the exception was thrown does not have an associated catch clause, the interpreter checks the next-highest enclosing block of code to see if it has an exception handler associated with it. This continues until a handler is found. If an exception is thrown in a function that does not contain a try/catch/finally statement to handle it, the exception propagates up to the code that invoked the function. In this way, exceptions propagate up through the lexical structure of JavaScript methods and up the call stack. If no exception handler is ever found, the exception is treated as an error and is reported to the user.\n\n> 引发异常时，JavaScript 解释器立即停止正常程序执行并跳转到最近的异常处理程序。异常处理程序使用 try/catch/finally 语句的 catch 子句编写，这将在下一部分中进行描述。如果在其中引发了异常的代码块中没有关联的 catch 子句，则解释器将检查下一个最顶层的代码块，以查看其是否具有与之关联的异常处理程序。这一直持续到找到处理程序为止。如果在不包含 try/catch/finally 语句来处理它的函数中引发异常，则该异常会传播到调用该函数的代码。这样，异常会通过 JavaScript 方法的词法结构向上传播，并向上扩展到调用堆栈。如果未找到异常处理程序，则将该异常视为错误并报告给用户。\n\n### 5.5.7 try/catch/finally\nThe try/catch/finally statement is JavaScript’s exception handling mechanism. The try clause of this statement simply defines the block of code whose exceptions are to be handled. The try block is followed by a catch clause, which is a block of statements that are invoked when an exception occurs anywhere within the try block. The catch clause is followed by a finally block containing cleanup code that is guaranteed to be executed, regardless of what happens in the try block. Both the catch and finally blocks are optional, but a try block must be accompanied by at least one of these blocks. The try, catch, and finally blocks all begin and end with curly braces. These braces are a required part of the syntax and cannot be omitted, even if a clause contains only a single statement.\n\n> try/catch/finally 语句是 JavaScript 的异常处理机制。该语句的 try 子句仅定义要处理其异常的代码块。在 try 块之后是 catch 子句，catch 子句是在 try 块内任何地方发生异常时调用的语句块。catch 子句后面是一个 finally 块，其中包含保证可以执行的清除代码，无论 try 块中发生了什么。catch 和 finally 块都是可选的，但是 try 块必须至少与这些块之一相伴。尝试，捕获和最终阻止都以花括号开头和结尾。这些花括号是语法的必需部分，即使子句仅包含一个语句，也不能忽略这些花括号。\n\nThe following code illustrates the syntax and purpose of the try/catch/finally statement:\n\n> 以下代码说明了 try/catch/finally 语句的语法和用途：\n\n```js\ntry {\n    // Normally, this code runs from the top of the block to the bottom\n    // without problems. But it can sometimes throw an exception,\n    // either directly, with a throw statement, or indirectly, by calling\n    // a method that throws an exception.\n}\ncatch(e) {\n    // The statements in this block are executed if, and only if, the try\n    // block throws an exception. These statements can use the local variable\n    // e to refer to the Error object or other value that was thrown.\n    // This block may handle the exception somehow, may ignore the\n    // exception by doing nothing, or may rethrow the exception with throw.\n}\nfinally {\n    // This block contains statements that are always executed, regardless of\n    // what happens in the try block. They are executed whether the try\n    // block terminates:\n    //   1) normally, after reaching the bottom of the block\n    //   2) because of a break, continue, or return statement\n    //   3) with an exception that is handled by a catch clause above\n    //   4) with an uncaught exception that is still propagating\n}\n```\nNote that the catch keyword is generally followed by an identifier in parentheses. This identifier is like a function parameter. When an exception is caught, the value associated with the exception (an Error object, for example) is assigned to this parameter. The identifier associated with a catch clause has block scope—it is only defined within the catch block.\n\n> 请注意，catch 关键字通常在括号后跟一个标识符。该标识符就像一个函数参数。捕获到异常后，与该异常关联的值（例如 Error 对象）将分配给该参数。与 catch 子句关联的标识符具有块作用域——仅在catch块内有定义。\n\nHere is a realistic example of the try/catch statement. It uses the factorial() method defined in the previous section and the client-side JavaScript methods prompt() and alert() for input and output:\n\n> 这是 try/catch 语句的实际示例。它使用上一节中定义的 factorial() 方法以及客户端 JavaScript 方法 hint() 和 alert() 进行输入和输出：\n\n```js\ntry {\n    // Ask the user to enter a number\n    let n = Number(prompt(\"Please enter a positive integer\", \"\"));\n    // Compute the factorial of the number, assuming the input is valid\n    let f = factorial(n);\n    // Display the result\n    alert(n + \"! = \" + f);\n}\ncatch(ex) {     // If the user's input was not valid, we end up here\n    alert(ex);  // Tell the user what the error is\n}\n```\nThis example is a try/catch statement with no finally clause. Although finally is not used as often as catch, it can be useful. However, its behavior requires additional explanation. The finally clause is guaranteed to be executed if any portion of the try block is executed, regardless of how the code in the try block completes. It is generally used to clean up after the code in the try clause.\n\n> 这个例子是一个 try/catch 语句，没有 finally 子句。虽然 finally 不像 catch 那样经常使用，但它很有用。可是，其行为需要额外说明。如果 try 块的任何部分被执行，则必定执行 finally 子句，无论 try 块中的代码如何完成。通常用于在 try 子句中的代码之后进行清理。\n\nIn the normal case, the JavaScript interpreter reaches the end of the try block and then proceeds to the finally block, which performs any necessary cleanup. If the interpreter left the try block because of a return, continue, or break statement, the finally block is executed before the interpreter jumps to its new destination.\n\n> 在正常情况下，JavaScript 解释器到达 try 块的末尾，然后进行到 finally 块，该块执行任何必要的清除。如果解释器由于返回，继续或中断语句而离开 try 块，则在解释器跳转到其新目的地之前执行 finally 块。\n\nIf an exception occurs in the try block and there is an associated catch block to handle the exception, the interpreter first executes the catch block and then the finally block. If there is no local catch block to handle the exception, the interpreter first executes the finally block and then jumps to the nearest containing catch clause.\n\n> 如果 try 块中发生异常，并且有一个关联的 catch 块来处理该异常，则解释器将首先执行 catch 块，然后执行 finally 块。如果没有本地 catch 块来处理该异常，则解释器将首先执行 finally 块，然后跳转到最近的包含 catch 子句。\n\nIf a finally block itself causes a jump with a return, continue, break, or throw statement, or by calling a method that throws an exception, the interpreter abandons whatever jump was pending and performs the new jump. For example, if a finally clause throws an exception, that exception replaces any exception that was in the process of being thrown. If a finally clause issues a return statement, the method returns normally, even if an exception has been thrown and has not yet been handled.\n\n> 如果 finally 块本身通过 return、continue、break 或 throw 语句导致跳转，或者通过调用引发异常的方法来调用，则解释器将放弃所有挂起的任何程序并执行新的跳转。例如，如果 finally 子句引发异常，则该异常将替换正在引发过程中的所有异常。如果 finally 子句发出 return 语句，则即使已引发异常且尚未处理异常，该方法也会正常返回。\n\ntry and finally can be used together without a catch clause. In this case, the finally block is simply cleanup code that is guaranteed to be executed, regardless of what happens in the try block. Recall that we can’t completely simulate a for loop with a while loop because the continue statement behaves differently for the two loops. If we add a try/finally statement, we can write a while loop that works like a for loop and that handles continue statements correctly:\n\n> try 和 finally 可以一起使用，而无需使用 catch 子句。在这种情况下，无论 try 块发生什么情况，finally 块都是保证可以执行的清除代码。回想一下，我们无法完全通过 while 循环来模拟 for 循环，因为 continue 语句在两个循环中的行为有所不同。如果添加 try/finally 语句，则可以编写一个 while 循环，该循环类似于 for 循环，并且可以正确处理 continue 语句：\n\n```js\n// Simulate for(initialize ; test ;increment ) body;\ninitialize ;\nwhile( test ) {\n    try { body ; }\n    finally { increment ; }\n}\n```\nNote, however, that a body that contains a break statement behaves slightly differently (causing an extra increment before exiting) in the while loop than it does in the for loop, so even with the finally clause, it is not possible to completely simulate the for loop with while.\n\n> 但是请注意，包含 break 语句的主体在 while 循环中的行为与 for 循环中的行为略有不同（在退出前会导致额外的增量计算），因此即使使用 finally 子句，也无法完全用 while 模拟 for 循环。\n\nBARE CATCH CLAUSES\nOccasionally you may find yourself using a catch clause solely to detect and stop the propagation of an exception, even though you do not care about the type or the value of the exception. In ES2019 and later, you can omit the parentheses and the identifier and use the catch keyword bare in this case. Here is an example:\n\n> 有时，即使不关心异常的类型或值，您可能会发现自己仅使用 catch 子句来检测并停止异常的传播。在 ES2019 及更高版本中，可以省略括号和标识符，并在这种情况下裸露使用 catch 关键字。 这是一个例子：\n\n```js\n// Like JSON.parse(), but return undefined instead of throwing an error\nfunction parseJSON(s) {\n    try {\n        return JSON.parse(s);\n    } catch {\n        // Something went wrong but we don't care what it was\n        return undefined;\n    }\n}\n```\n## 5.6 Miscellaneous Statements\nThis section describes the remaining three JavaScript statements—with, debugger, and \"use strict\".\n\n> 本节描述了其余三个 JavaScript 语句——with、debugger 和“ use strict”。\n\n### 5.6.1 with\nThe with statement runs a block of code as if the properties of a specified object were variables in scope for that code. It has the following syntax:\n\n> with 语句运行代码块，就像指定对象的属性是该代码范围内的变量一样。它具有以下语法：\n\n```js\nwith (object)\n    statement\n```\nThis statement creates a temporary scope with the properties of object as variables and then executes statement within that scope.\n\n> 该语句使用对象的属性作为变量创建一个临时作用域，然后在该作用域内执行语句。\n\nThe with statement is forbidden in strict mode (see §5.6.3) and should be considered deprecated in non-strict mode: avoid using it whenever possible. JavaScript code that uses with is difficult to optimize and is likely to run significantly more slowly than the equivalent code written without the with statement.\n\n> 在严格模式下禁止 with 语句（请参见 §5.6.3），并且在非严格模式下应将其视为已弃用：请尽可能避免使用它。与 with 一起使用的 JavaScript 代码难以优化，并且与没有 with 语句编写的等效代码相比，运行速度可能慢得多。\n\nThe common use of the with statement is to make it easier to work with deeply nested object hierarchies. In client-side JavaScript, for example, you may have to type expressions like this one to access elements of an HTML form:\n\n> with 语句的常用用法是使使用深度嵌套的对象层次结构更清晰。例如，在客户端 JavaScript 中，可能必须键入此类表达式才能访问 HTML 表单的元素：\n\n```js\ndocument.forms[0].address.value\n```\nIf you need to write expressions like this a number of times, you can use the with statement to treat the properties of the form object like variables:\n\n> 如果需要多次编写这样的表达式，则可以使用 with 语句将表单对象的属性像变量一样对待：\n\n```js\nwith(document.forms[0]) {\n    // Access form elements directly here. For example:\n    name.value = \"\";\n    address.value = \"\";\n    email.value = \"\";\n}\n```\nThis reduces the amount of typing you have to do: you no longer need to prefix each form property name with document.forms[0]. It is just as simple, of course, to avoid the with statement and write the preceding code like this:\n\n> 这减少了必须进行的键入操作的数量：不再需要为每个表单属性名称加上 document.forms[0] 前缀。当然，避免 with 语句并像这样编写前面的代码也很简单：\n\n```js\nlet f = document.forms[0];\nf.name.value = \"\";\nf.address.value = \"\";\nf.email.value = \"\";\n```\nNote that if you use const or let or var to declare a variable or constant within the body of a with statement, it creates an ordinary variable and does not define a new property within the specified object.\n\n> 请注意，如果使用 const 或 let 或 var 在 with 语句的主体内声明变量或常量，则它将创建普通变量，并且不会在指定对象内定义新属性。\n\n### 5.6.2 debugger\nThe debugger statement normally does nothing. If, however, a debugger program is available and is running, then an implementation may (but is not required to) perform some kind of debugging action. In practice, this statement acts like a breakpoint: execution of JavaScript code stops, and you can use the debugger to print variables’ values, examine the call stack, and so on. Suppose, for example, that you are getting an exception in your function f() because it is being called with an undefined argument, and you can’t figure out where this call is coming from. To help you in debugging this problem, you might alter f() so that it begins like this:\n\n> debugger 语句通常不执行任何操作。但是，如果 debugger 程序可用并且正在运行，则这个实现可以（但不是必需）执行某种调试操作。实际上，该语句就像一个断点：停止执行 JavaScript 代码，并且可以使用调试器打印变量的值，检查调用堆栈等。例如，假设您在函数 f() 中遇到一个异常，因为该异常正在使用未定义的参数进行调用，而您无法弄清楚该调用的来源。为了帮助调试此问题，可以更改 f() 使其开始，如下所示：\n\n```js\nfunction f(o) {\n  if (o === undefined) debugger;  // Temporary line for debugging purposes\n  ...                             // The rest of the function goes here.\n}\n```\nNow, when f() is called with no argument, execution will stop, and you can use the debugger to inspect the call stack and find out where this incorrect call is coming from.\n\n> 现在，当不带任何参数调用 f() 时，执行将停止，并且可以使用调试器检查调用堆栈，并找出此错误调用的出处。\n\nNote that it is not enough to have a debugger available: the debugger statement won’t start the debugger for you. If you’re using a web browser and have the developer tools console open, however, this statement will cause a breakpoint.\n\n> 请注意，仅有 debugger 是不够的：debugger 语句不会为启动调试器。但是，如果您使用的是网络浏览器并打开了开发者工具控制台，则此语句将导致一个断点。\n\n### 5.6.3 “use strict”\n\"use strict\" is a directive introduced in ES5. Directives are not statements (but are close enough that \"use strict\" is documented here). There are two important differences between the \"use strict\" directive and regular statements:\n\n> “use strict”是ES5中引入的指令。指令不是语句（但足够接近，因此此处记录了“use strict”）。“use strict”指令和常规语句之间有两个重要区别：\n\nIt does not include any language keywords: the directive is just an expression statement that consists of a special string literal (in single or double quotes).\n\n> 它不包含任何语言关键字：指令只是由特殊字符串文字（单引号或双引号）组成的表达式语句。\n\nIt can appear only at the start of a script or at the start of a function body, before any real statements have appeared.\n\n> 它只能出现在脚本的开始处或函数体的开始处，然后才出现任何实际的语句。\n\nThe purpose of a \"use strict\" directive is to indicate that the code that follows (in the script or function) is strict code. The top-level (nonfunction) code of a script is strict code if the script has a \"use strict\" directive. A function body is strict code if it is defined within strict code or if it has a \"use strict\" directive. Code passed to the eval() method is strict code if eval() is called from strict code or if the string of code includes a \"use strict\" directive. In addition to code explicitly declared to be strict, any code in a class body (Chapter 9) or in an ES6 module (§10.3) is automatically strict code. This means that if all of your JavaScript code is written as modules, then it is all automatically strict, and you will never need to use an explicit \"use strict\" directive.\n\n> “use strict”指令的目的是指示（在脚本或函数中）后面的代码是严格代码。如果脚本具有“use strict”指令，则该脚本的顶级（非函数）代码为严格代码。如果函数体是在严格代码中定义的，或者具有“use strict”指令，则它是严格代码。如果从严格代码中调用 eval() 或代码字符串包含“use strict”指令，则传递给 eval() 方法的代码为严格代码。除了明确声明为严格的代码之外，类主体（第9章）或 ES6 模块（10.3）中的任何代码都是自动严格的代码。这意味着，如果所有的JavaScript代码都是作为模块编写的，那么它们都是自动严格的，并且将不需要使用显式的“use strict”指令。 \n\nStrict code is executed in strict mode. Strict mode is a restricted subset of the language that fixes important language deficiencies and provides stronger error checking and increased security. Because strict mode is not the default, old JavaScript code that still uses the deficient legacy features of the language will continue to run correctly. The differences between strict mode and non-strict mode are the following (the first three are particularly important):\n\n> 严格代码在严格模式下执行。严格模式是该语言的受限子集，可修复重要的语言缺陷并提供更强的错误检查和更高的安全性。由于严格模式不是默认模式，因此旧 JavaScript 代码使用不足传统特性将继续正确运行。严格模式和非严格模式之间的区别如下（前三个特别重要）：\n\nThe with statement is not allowed in strict mode.\n\n> 在严格模式下，不允许使用 with 语句。\n\nIn strict mode, all variables must be declared: a ReferenceError is thrown if you assign a value to an identifier that is not a declared variable, function, function parameter, catch clause parameter, or property of the global object. (In non-strict mode, this implicitly declares a global variable by adding a new property to the global object.)\n\n> 在严格模式下，必须声明所有变量：如果将值分配给未声明的变量、函数、函数参数、catch 子句参数或全局对象的属性的标识符，则会引发 ReferenceError。（在非严格模式下，通过向全局对象添加新属性来隐式声明全局变量。）\n\nIn strict mode, functions invoked as functions (rather than as methods) have a this value of undefined. (In non-strict mode, functions invoked as functions are always passed the global object as their this value.) Also, in strict mode, when a function is invoked with call() or apply() (§8.7.4), the this value is exactly the value passed as the first argument to call() or apply(). (In non-strict mode, null and undefined values are replaced with the global object and nonobject values are converted to objects.)\n\n> 在严格模式下，作为函数（而不是方法）调用的函数的 this 值是 undefined。（在非严格模式下，作为函数调用的函数总是将全局对象作为其 this 值。）此外，在严格模式下，当使用 call() 或 apply() 调用函数时（§8.7.4），this 值作为第一个参数传递给 call() 或 apply() 的值。（在非严格模式下，将 null 和 undefined 值替换为全局对象，并将 nonobject 值转换为对象。）\n\nIn strict mode, assignments to nonwritable properties and attempts to create new properties on non-extensible objects throw a TypeError. (In non-strict mode, these attempts fail silently.)\n\n> 在严格模式下，对不可写属性的分配以及在不可扩展对象上尝试创建新属性的尝试将引发 TypeError。（在非严格模式下，这些尝试将以静默方式失败。）\n\nIn strict mode, code passed to eval() cannot declare variables or define functions in the caller’s scope as it can in non-strict mode. Instead, variable and function definitions live in a new scope created for the eval(). This scope is discarded when the eval() returns.\n\n> 在严格模式下，传递给 eval() 的代码无法像在非严格模式下那样在调用者的作用域中声明变量或定义函数。相反，变量和函数定义位于为 eval() 创建的新作用域中。当 eval() 返回时，将放弃此 this 用域。\n\nIn strict mode, the Arguments object (§8.3.3) in a function holds a static copy of the values passed to the function. In non-strict mode, the Arguments object has “magical” behavior in which elements of the array and named function parameters both refer to the same value.\n\nIn strict mode, a SyntaxError is thrown if the delete operator is followed by an unqualified identifier such as a variable, function, or function parameter. (In nonstrict mode, such a delete expression does nothing and evaluates to false.)\n\nIn strict mode, an attempt to delete a nonconfigurable property throws a TypeError. (In non-strict mode, the attempt fails and the delete expression evaluates to false.)\n\nIn strict mode, it is a syntax error for an object literal to define two or more properties by the same name. (In non-strict mode, no error occurs.)\n\nIn strict mode, it is a syntax error for a function declaration to have two or more parameters with the same name. (In non-strict mode, no error occurs.)\n\nIn strict mode, octal integer literals (beginning with a 0 that is not followed by an x) are not allowed. (In non-strict mode, some implementations allow octal literals.)\n\nIn strict mode, the identifiers eval and arguments are treated like keywords, and you are not allowed to change their value. You cannot assign a value to these identifiers, declare them as variables, use them as function names, use them as function parameter names, or use them as the identifier of a catch block.\n\nIn strict mode, the ability to examine the call stack is restricted. arguments.caller and arguments.callee both throw a TypeError within a strict mode function. Strict mode functions also have caller and arguments properties that throw TypeError when read. (Some implementations define these nonstandard properties on non-strict functions.)\n\n## 5.7 Declarations\nThe keywords const, let, var, function, class, import, and export are not technically statements, but they look a lot like statements, and this book refers informally to them as statements, so they deserve a mention in this chapter.\n\n> 从技术上讲，关键字 const、let、var、function、class、import 和 export 并不是语句，但它们看起来很像语句，并且本书将它们非正式地称为语句，因此在本章中应予提及。\n\nThese keywords are more accurately described as declarations rather than statements. We said at the start of this chapter that statements “make something happen.” Declarations serve to define new values and give them names that we can use to refer to those values. They don’t make much happen themselves, but by providing names for values they, in an important sense, define the meaning of the other statements in your program.\n\n> 这些关键字被更准确地描述为声明而不是语句。我们在本章开始时说过，语句“使某事发生”。声明用于定义新值，并为它们提供可用来引用这些值的名称。它们本身并不会带来太大的变化，但是通过提供值的名称，它们在重要的意义上定义了程序中其他语句的含义。\n\nWhen a program runs, it is the program’s expressions that are being evaluated and the program’s statements that are being executed. The declarations in a program don’t “run” in the same way: instead, they define the structure of the program itself. Loosely, you can think of declarations as the parts of the program that are processed before the code starts running.\n\n> 程序运行时，将对程序的表达式进行评估，并执行程序的语句。程序中的声明不是以相同的方式“运行”：相反，它们定义了程序本身的结构。不确切地说，可以将声明视为代码开始运行之前已处理的部分程序。\n\nJavaScript declarations are used to define constants, variables, functions, and classes and for importing and exporting values between modules. The next subsections give examples of all of these declarations. They are all covered in much more detail elsewhere in this book.\n\n> JavaScript 声明用于定义常量、变量、函数和类，以及在模块之间导入和导出值。接下来的小节将给出所有这些声明的示例。在本书的其他地方，将对它们进行更详细的介绍。\n\n### 5.7.1 const, let, and var\nThe const, let, and var declarations are covered in §3.10. In ES6 and later, const declares constants, and let declares variables. Prior to ES6, the var keyword was the only way to declare variables and there was no way to declare constants. Variables declared with var are scoped to the containing function rather than the containing block. This can be a source of bugs, and in modern JavaScript there is really no reason to use var instead of let.\n```js\nconst TAU = 2*Math.PI;\nlet radius = 3;\nvar circumference = TAU * radius;\n```\n### 5.7.2 function\nThe function declaration is used to define functions, which are covered in detail in Chapter 8. (We also saw function in §4.3, where it was used as part of a function expression rather than a function declaration.) A function declaration looks like this:\n```js\nfunction area(radius) {\n    return Math.PI * radius * radius;\n}\n```\nA function declaration creates a function object and assigns it to the specified name—area in this example. Elsewhere in our program, we can refer to the function—and run the code inside it—by using this name. The function declarations in any block of JavaScript code are processed before that code runs, and the function names are bound to the function objects throughout the block. We say that function declarations are “hoisted” because it is as if they had all been moved up to the top of whatever scope they are defined within. The upshot is that code that invokes a function can exist in your program before the code that declares the function.\n\n§12.3 describes a special kind of function known as a generator. Generator declarations use the function keyword but follow it with an asterisk. §13.3 describes asynchronous functions, which are also declared using the function keyword but are prefixed with the async keyword.\n\n### 5.7.3 class\nIn ES6 and later, the class declaration creates a new class and gives it a name that we can use to refer to it. Classes are described in detail in Chapter 9. A simple class declaration might look like this:\n```js\nclass Circle {\n    constructor(radius) { this.r = radius; }\n    area() { return Math.PI * this.r * this.r; }\n    circumference() { return 2 * Math.PI * this.r; }\n}\n```\nUnlike functions, class declarations are not hoisted, and you cannot use a class declared this way in code that appears before the declaration.\n\n### 5.7.4 import and export\nThe import and export declarations are used together to make values defined in one module of JavaScript code available in another module. A module is a file of JavaScript code with its own global namespace, completely independent of all other modules. The only way that a value (such as function or class) defined in one module can be used in another module is if the defining module exports it with export and the using module imports it with import. Modules are the subject of Chapter 10, and import and export are covered in detail in §10.3.\n\nimport directives are used to import one or more values from another file of JavaScript code and give them names within the current module. import directives come in a few different forms. Here are some examples:\n```js\nimport Circle from './geometry/circle.js';\nimport { PI, TAU } from './geometry/constants.js';\nimport { magnitude as hypotenuse } from './vectors/utils.js';\n```\nValues within a JavaScript module are private and cannot be imported into other modules unless they have been explicitly exported. The export directive does this: it declares that one or more values defined in the current module are exported and therefore available for import by other modules. The export directive has more variants than the import directive does. Here is one of them:\n```js\n// geometry/constants.js\nconst PI = Math.PI;\nconst TAU = 2 * PI;\nexport { PI, TAU };\n```\nThe export keyword is sometimes used as a modifier on other declarations, resulting in a kind of compound declaration that defines a constant, variable, function, or class and exports it at the same time. And when a module exports only a single value, this is typically done with the special form export default:\n```js\nexport const TAU = 2 * Math.PI;\nexport function magnitude(x,y) { return Math.sqrt(x*x + y*y); }\nexport default class Circle { /* class definition omitted here */ }\n```\n## 5.8 Summary of JavaScript Statements\nThis chapter introduced each of the JavaScript language’s statements, which are summarized in Table 5-1.\n\nTable 5-1. JavaScript statement syntax\nStatement\tPurpose\nbreak\n\nExit from the innermost loop or switch or from named enclosing statement\n\ncase\n\nLabel a statement within a switch\n\nclass\n\nDeclare a class\n\nconst\n\nDeclare and initialize one or more constants\n\ncontinue\n\nBegin next iteration of the innermost loop or the named loop\n\ndebugger\n\nDebugger breakpoint\n\ndefault\n\nLabel the default statement within a switch\n\ndo/while\n\nAn alternative to the while loop\n\nexport\n\nDeclare values that can be imported into other modules\n\nfor\n\nAn easy-to-use loop\n\nfor/await\n\nAsynchronously iterate the values of an async iterator\n\nfor/in\n\nEnumerate the property names of an object\n\nfor/of\n\nEnumerate the values of an iterable object such as an array\n\nfunction\n\nDeclare a function\n\nif/else\n\nExecute one statement or another depending on a condition\n\nimport\n\nDeclare names for values defined in other modules\n\nlabel\n\nGive statement a name for use with break and continue\n\nlet\n\nDeclare and initialize one or more block-scoped variables (new syntax)\n\nreturn\n\nReturn a value from a function\n\nswitch\n\nMultiway branch to case or default: labels\n\nthrow\n\nThrow an exception\n\ntry/catch/finally\n\nHandle exceptions and code cleanup\n\n“use strict”\n\nApply strict mode restrictions to script or function\n\nvar\n\nDeclare and initialize one or more variables (old syntax)\n\nwhile\n\nA basic loop construct\n\nwith\n\nExtend the scope chain (deprecated and forbidden in strict mode)\n\nyield\n\nProvide a value to be iterated; only used in generator functions\n\n---\n\n1. The fact that the case expressions are evaluated at runtime makes the JavaScript switch statement much different from (and less efficient than) the switch statement of C, C++, and Java. In those languages, the case expressions must be compile-time constants of the same type, and switch statements can often compile down to highly efficient jump tables.\n2. When we consider the continue statement in §5.5.3, we’ll see that this while loop is not an exact equivalent of the for loop."
  },
  {
    "path": "content/posts/ch6.md",
    "content": "---\ntitle: \"第 6 章 对象\"\ndate: 2020-11-02T22:18:37+08:00\n---\n\nObjects are JavaScript’s most fundamental datatype, and you have already seen them many times in the chapters that precede this one. Because objects are so important to the JavaScript language, it is important that you understand how they work in detail, and this chapter provides that detail. It begins with a formal overview of objects, then dives into practical sections about creating objects and querying, setting, deleting, testing, and enumerating the properties of objects. These property-focused sections are followed by sections that explain how to extend, serialize, and define important methods on objects. Finally, the chapter concludes with a long section about new object literal syntax in ES6 and more recent versions of the language.\n\n> 对象是 JavaScript 最基本的数据类型，在本章之前的章节中已经多次看到它们。因为对象对 JavaScript 语言非常重要，所以了解它们的工作原理非常重要，本章将提供这些细节。它从对象的正式概述开始，然后深入到关于创建对象以及查询、设置、删除、测试和枚举对象属性的实用部分。在这些以属性为中心的章节之后，将介绍如何扩展、序列化和定义对象上的重要方法。最后，本章以一大段关于 ES6 中的新对象文字语法和该语言的最新版本结尾。\n\n## 6.1 Introduction to Objects\n\nAn object is a composite value: it aggregates multiple values (primitive values or other objects) and allows you to store and retrieve those values by name. An object is an unordered collection of properties, each of which has a name and a value. Property names are usually strings (although, as we’ll see in §6.10.3, property names can also be Symbols), so we can say that objects map strings to values. This string-to-value mapping goes by various names—you are probably already familiar with the fundamental data structure under the name “hash,” “hashtable,” “dictionary,” or “associative array.” An object is more than a simple string-to-value map, however. In addition to maintaining its own set of properties, a JavaScript object also inherits the properties of another object, known as its “prototype.” The methods of an object are typically inherited properties, and this “prototypal inheritance” is a key feature of JavaScript.\n\n> 对象是一个复合值：它聚合了多个值（原始值或其他对象），并允许按名称存储和获取这些值。对象是属性的无序集合，每个属性都有一个名称和一个值。属性名通常是字符串（尽管，正如我们将在 §6.10.3 中看到的，属性名也可以是 Symbol），所以我们可以说对象将字符串映射到值。这种字符串到值的映射有不同的名称——可能已经熟悉这种基本数据结构的别的名称“散列”（hash）、“散列表”（hashtable）、“字典”（dictionary）或“关联数组”（associative array）。然而，对象不仅仅是简单的字符串到值的映射。除了维护自己的属性集，JavaScript 对象还继承另一个对象的属性，即它的“原型”。对象的方法通常是继承的属性，而这种“原型继承”是 JavaScript 的一个关键特性。\n\nJavaScript objects are dynamic—properties can usually be added and deleted—but they can be used to simulate the static objects and “structs” of statically typed languages. They can also be used (by ignoring the value part of the string-to-value mapping) to represent sets of strings.\n\n> JavaScript 对象是动态的——属性通常可以添加和删除——但它们可以用来模拟静态类型语言的静态对象和“结构体”（struct）。有时它们也用做字符串的集 合（忽略名/值对中的值）。\n\nAny value in JavaScript that is not a string, a number, a Symbol, or true, false, null, or undefined is an object. And even though strings, numbers, and booleans are not objects, they can behave like immutable objects.\n\n> JavaScript 中任何不是字符串、数字、Symbol 或 true、false、null 或 undefined 的值都是对象。即使字符串、数字和布尔值不是对象，它们的行为和不可变对象非常类似。\n\nRecall from §3.8 that objects are mutable and manipulated by reference rather than by value. If the variable x refers to an object and the code let y = x; is executed, the variable y holds a reference to the same object, not a copy of that object. Any modifications made to the object through the variable y are also visible through the variable x.\n\n> 回想一下 §3.8，对象是可变的，通过引用而不是值来操作。如果变量 x 指向一个对象的引用，执行 `y = x;` 时，变量 y 也是指向同一对象的引用，而不是该对象的副本。通过变量 y 修改这个对象也会对变量 x 造成影响。\n\nThe most common things to do with objects are to create them and set, query, delete, test, and enumerate their properties. These fundamental operations are described in the opening sections of this chapter. The sections after that cover more advanced topics.\n\n> 对对象最常见的操作是创建它们并设置、查询、删除、测试和枚举它们的属性。这些基本的操作将在本章的开头部分进行描述。之后的部分将介绍更高级的主题。\n\nA property has a name and a value. A property name may be any string, including the empty string (or any Symbol), but no object may have two properties with the same name. The value may be any JavaScript value, or it may be a getter or setter function (or both). We’ll learn about getter and setter functions in §6.10.6.\n\n> 属性有一个名称和一个值。属性名可以是任何字符串，包括空字符串（或任何 Symbol），但任何对象都不能有两个同名的属性。值可以是任何 JavaScript 值，也可以是 getter 或 setter 函数（或两者都是）。我们将在 §6.10.6 中学习 getter 和 setter 函数。\n\nIt is sometimes important to be able to distinguish between properties defined directly on an object and those that are inherited from a prototype object. JavaScript uses the term own property to refer to non-inherited properties.\n\n> 有时，能够区分直接在对象上定义的属性和从原型对象继承的属性是很重要的。JavaScript 使用术语“自有属性”来指代非继承属性。\n\nIn addition to its name and value, each property has three property attributes:\n\n> 除了名称和值之外，每个属性还有三个属性属性:\n\n- The writable attribute specifies whether the value of the property can be set.\n- The enumerable attribute specifies whether the property name is returned by a for/in loop.\n- The configurable attribute specifies whether the property can be deleted and whether its attributes can be altered.\n\n---\n\n- writable 属性指定是否可以设置属性的值。\n- enumerable 属性指定 for/in 循环是否返回属性名称。\n- 可配置属性指定该属性是否可以删除，是否可以修改其属性。\n\nMany of JavaScript’s built-in objects have properties that are read-only, non-enumerable, or non-configurable. By default, however, all properties of the objects you create are writable, enumerable, and configurable. §14.1 explains techniques for specifying non-default property attribute values for your objects.\n\n> 许多 JavaScript 的内置对象具有只读、不可枚举或不可配置的属性。但是，在默认情况下，创建的对象的所有属性都是可写、可枚举和可配置的。§14.1 解释了为对象指定非默认属性属性值的技巧。\n\n## 6.2 Creating Objects\n\nObjects can be created with object literals, with the new keyword, and with the Object.create() function. The subsections below describe each technique.\n\n> 对象可以用对象字面量创建，也可以用 new 关键字和 Object.create() 函数来创建。接下来的几部分对这些技术一一讲述。\n\n### 6.2.1 Object Literals\n\nThe easiest way to create an object is to include an object literal in your JavaScript code. In its simplest form, an object literal is a comma-separated list of colon-separated name:value pairs, enclosed within curly braces. A property name is a JavaScript identifier or a string literal (the empty string is allowed). A property value is any JavaScript expression; the value of the expression (it may be a primitive value or an object value) becomes the value of the property. Here are some examples:\n\n> 创建对象最简单的方式就是在 JavaScript 代码中使用对象直接量。对象直接量是由若干名/值对组成的映射表，名/值对中间用冒号分隔，名/值对之间用逗号分隔，整个映射表用花括号括起来。属性名可以是 JavaScript 标识符也可以是字符串字面量（包括空字符串）。属性的值可以是任意类型的 JavaScript 表达式；表达式的值（可以是原始值也可以是对象值）变成这个属性的值。下面有一些例子：\n\n```js\nlet empty = {};                          // An object with no properties\nlet point = { x: 0, y: 0 };              // Two numeric properties\nlet p2 = { x: point.x, y: point.y+1 };   // More complex values\nlet book = {\n    \"main title\": \"JavaScript\",          // These property names include spaces,\n    \"sub-title\": \"The Definitive Guide\", // and hyphens, so use string literals.\n    for: \"all audiences\",                // for is reserved, but no quotes.\n    author: {                            // The value of this property is\n        firstname: \"David\",              // itself an object.\n        surname: \"Flanagan\"\n    }\n};\n```\nA trailing comma following the last property in an object literal is legal, and some programming styles encourage the use of these trailing commas so you’re less likely to cause a syntax error if you add a new property at the end of the object literal at some later time.\n\n> 对象文本中最后一个属性的尾随逗号是合法的，并且某些编程样式鼓励使用这些尾随逗号，因为，如果之后在对象文本的末尾添加新属性，则不太可能导致语法错误。\n\nAn object literal is an expression that creates and initializes a new and distinct object each time it is evaluated. The value of each property is evaluated each time the literal is evaluated. This means that a single object literal can create many new objects if it appears within the body of a loop or in a function that is called repeatedly, and that the property values of these objects may differ from each other.\n\n> 对象字面量是一个表达式，它每次计算时都会创建和初始化一个新对象。每次字面量计算时，将计算每个属性的值。这意味着，如果单个对象字面量出现在循环体或重复调用的函数中，它能创建许多个新对象，并且这些对象的属性值可能彼此不同。\n\nThe object literals shown here use simple syntax that has been legal since the earliest versions of JavaScript. Recent versions of the language have introduced a number of new object literal features, which are covered in §6.10.\n\n> 此处的简单语法对象字面量，自 JavaScript 最早的版本以来一直是合法的。最新版本引入了许多对象字面量的新的特性，这些特性在 §6.10 中介绍。\n\n### 6.2.2 Creating Objects with new\nThe new operator creates and initializes a new object. The new keyword must be followed by a function invocation. A function used in this way is called a constructor and serves to initialize a newly created object. JavaScript includes constructors for its built-in types. For example:\n\n> new 运算符创建并初始化一个新的对象。new 关键字必须紧跟一个函数调用。这种方式使用函数叫做构造函数调用，其提供初始化一个新创建的对象的服务。在 JavaScript 中，内置类型都包含相对应的构造函数。例如：\n\n```js\nlet o = new Object();  // Create an empty object: same as {}.\nlet a = new Array();   // Create an empty array: same as [].\nlet d = new Date();    // Create a Date object representing the current time\nlet r = new Map();     // Create a Map object for key/value mapping\n```\nIn addition to these built-in constructors, it is common to define your own constructor functions to initialize newly created objects. Doing so is covered in Chapter 9.\n\n> 除了这些内置构造函数，常常用自定义构造函数来初始化新对象。 第 9 章将详细讲述其中的细节。\n\n### 6.2.3 Prototypes\nBefore we can cover the third object creation technique, we must pause for a moment to explain prototypes. Almost every JavaScript object has a second JavaScript object associated with it. This second object is known as a prototype, and the first object inherits properties from the prototype.\n\n> 在讲述第三种对象创建技术之前，我们应当首先解释一下原型。每一个 JavaScript 对象都和另一个对象相关联。“另一个”对象就是我们熟知的原型，每一个对象都从原型继承属性。\n\nAll objects created by object literals have the same prototype object, and we can refer to this prototype object in JavaScript code as Object.prototype. Objects created using the new keyword and a constructor invocation use the value of the prototype property of the constructor function as their prototype. So the object created by new Object() inherits from Object.prototype, just as the object created by {} does. Similarly, the object created by new Array() uses Array.prototype as its prototype, and the object created by new Date() uses Date.prototype as its prototype. This can be confusing when first learning JavaScript. Remember: almost all objects have a prototype, but only a relatively small number of objects have a prototype property. It is these objects with prototype properties that define the prototypes for all the other objects.\n\n> 所有通过对象字面量创建的对象都具有同一个原型对象，并可以通过 JavaScript 代码 Object.prototype 获得对原型对象的引用。通过关键字 new 和构造函数调用创建的对象的原型就是构造函数的 prototype 属性的值。因此，同使用 {} 创建对象一样，通过 new Object() 创建的对象也继承自Object.prototype。同样，通过 new Array() 创建的对象的原型就是 Array.prototype，通过 new Date() 创建的对象的原型就是 Date.prototype。当第一次学习 JavaScript 时，这可能令人困惑。请记住：几乎所有对象都有原型，但只有相对较少的对象具有原型属性。正是这些具有原型属性的对象定义了所有其他对象的原型。\n\nObject.prototype is one of the rare objects that has no prototype: it does not inherit any properties. Other prototype objects are normal objects that do have a prototype. Most built-in constructors (and most user-defined constructors) have a prototype that inherits from Object.prototype. For example, Date.prototype inherits properties from Object.prototype, so a Date object created by new Date() inherits properties from both Date.prototype and Object.prototype. This linked series of prototype objects is known as a prototype chain.\n\n> 没有原型的对象为数不多，Object.prototype就是其中之一：它不继承任何属性。其他原型对象都是普通对象，普通对象都具有原型。大部分的内置构造函数（以及大部分自定义的构造函数）都具有一个继承自 Object.prototype 的原型。例如， Date.prototype 的属性继承自 Object.prototype，因此由 new Date() 创建的 Date 对象的属性同时继承自 Date.prototype 和 Object.prototype。这一系列链接的原型对象就是所谓 “原型链”（prototype chain）。\n\nAn explanation of how property inheritance works is in §6.3.2. Chapter 9 explains the connection between prototypes and constructors in more detail: it shows how to define new “classes” of objects by writing a constructor function and setting its prototype property to the prototype object to be used by the “instances” created with that constructor. And we’ll learn how to query (and even change) the prototype of an object in §14.3.\n\n> §6.3.2 节讲述属性继承的工作机制。第 9 章将会更详细地讨论原型和构造函数：包括如何通过编写构造函数定义对象的“类”，以及给构造函数的 prototype 属性赋值可以让其“实例”直接使用这个原型上的属性和方法。并且在 §14.3 我们会学习如何查询（甚至改变）对象的原型。\n\n### 6.2.4 Object.create()\nObject.create() creates a new object, using its first argument as the prototype of that object:\n\n> Object.create() 创建一个新的对象，用第一个实参作为它的原型：\n\n```js\nlet o1 = Object.create({x: 1, y: 2});     // o1 inherits properties x and y.\no1.x + o1.y                               // => 3\n```\nYou can pass null to create a new object that does not have a prototype, but if you do this, the newly created object will not inherit anything, not even basic methods like toString() (which means it won’t work with the + operator either):\n\n> 可以通过传入参数 null 来创建一个没有原型的新对象，但通过这种方式创建的对象不会继承任何东西，甚至不包括基础方法，比如 toString()，也就是说，它将不能和“+”运算符一起正常工作：\n\n```js\nlet o2 = Object.create(null);             // o2 inherits no props or methods.\n```\nIf you want to create an ordinary empty object (like the object returned by {} or new Object()), pass Object.prototype:\n\n> 如果想创建一个普通的空对象（像通过 {} 或 new Object() 创建的对象），需要传入 Object.prototype：\n\n```js\nlet o3 = Object.create(Object.prototype); // o3 is like {} or new Object().\n```\nThe ability to create a new object with an arbitrary prototype is a powerful one, and we’ll use Object.create() in a number of places throughout this chapter. (Object.create() also takes an optional second argument that describes the properties of the new object. This second argument is an advanced feature covered in §14.1.)\n\n> 可以通过任意原型创建新对象，这是一个强大的特性，并且本章我们会在很多地方使用 Object.create()。（Object.create() 也可以传入第二个可选实参来描述这个新的对象的属性。第二个实参是一个高级特性，在  §14.1 再进行描述。）\n\nOne use for Object.create() is when you want to guard against unintended (but nonmalicious) modification of an object by a library function that you don’t have control over. Instead of passing the object directly to the function, you can pass an object that inherits from it. If the function reads properties of that object, it will see the inherited values. If it sets properties, however, those writes will not affect the original object.\n\n> Object.create() 其中一个用途是预防对象无意间（非恶意地）被无法支配的库函数篡改。可以创建一个继承它的对象来传递给函数，而不是将其直接传递给函数。当函数读取继承对象的属性时，实际上读取的是继承来的值。如果给继承对象的属性赋值，则这些属性只会影响这个继承对象自身，而不是原始对象：\n\n```js\nlet o = { x: \"don't change this value\" };\nlibrary.function(Object.create(o));  // Guard against accidental modifications\n```\nTo understand why this works, you need to know how properties are queried and set in JavaScript. These are the topics of the next section.\n\n> 想要了解其工作原理，需要先知道 JavaScript 中属性的查询和设置机制。这是接下来这节的主题。\n\n## 6.3 Querying and Setting Properties\nTo obtain the value of a property, use the dot (.) or square bracket ([]) operators described in §4.4. The lefthand side should be an expression whose value is an object. If using the dot operator, the righthand side must be a simple identifier that names the property. If using square brackets, the value within the brackets must be an expression that evaluates to a string that contains the desired property name:\n\n> §4.4 已经提到，可以通过点（.）或方括号（[]）运算符来获取属性的值。运算符左侧应当是一个表达式，它返回一个对象。如果使用点运算符，右侧必须是一个以属性名称命名的简单标识符。如果使用方括号，方括号内必须是一个计算结果为字符串的表达式，这个字符串就是属性的名字：\n\n```js\nlet author = book.author;       // Get the \"author\" property of the book.\nlet name = author.surname;      // Get the \"surname\" property of the author.\nlet title = book[\"main title\"]; // Get the \"main title\" property of the book.\n```\nTo create or set a property, use a dot or square brackets as you would to query the property, but put them on the lefthand side of an assignment expression:\n\n> 和查询属性值的写法一样，通过点和方括号也可以创建属性或给属性赋值，但需要将它们放在赋值表达式的左侧：\n\n```js\nbook.edition = 7;                   // Create an \"edition\" property of book.\nbook[\"main title\"] = \"ECMAScript\";  // Change the \"main title\" property.\n```\nWhen using square bracket notation, we’ve said that the expression inside the square brackets must evaluate to a string. A more precise statement is that the expression must evaluate to a string or a value that can be converted to a string or to a Symbol (§6.10.3). In Chapter 7, for example, we’ll see that it is common to use numbers inside the square brackets.\n\n> 当使用方括号时，我们说方括号内的表达式必须返回字符串。其实更严格地讲，表达式必须返回字符串或返回一个可以转换为字符串的值或 Symbol（§6.10.3）。在第 7 章里有一些例子中的方括号内使用了数字，这情况是非常常用的。\n\n### 6.3.1 Objects As Associative Arrays\nAs explained in the preceding section, the following two JavaScript expressions have the same value:\n\n> 上文提到，下面两个表达式有相同的值：\n\n```js\nobject.property\nobject[\"property\"]\n```\nThe first syntax, using the dot and an identifier, is like the syntax used to access a static field of a struct or object in C or Java. The second syntax, using square brackets and a string, looks like array access, but to an array indexed by strings rather than by numbers. This kind of array is known as an associative array (or hash or map or dictionary). JavaScript objects are associative arrays, and this section explains why that is important.\n\n> 第一种语法使用点运算符和一个标识符，这和 C 和 Java 中访问一个结构体或对象的静态字段非常类似。第二种语法使用方括号和一个字符串，看起来更像数组，只是这个数组元素是通过字符串索引而不是数字索引。这种数组就是我们所说的关联数组（associative array）（也称做散列、映射或字典）。JavaScript 对象都是关联数组，本节将讨论它的重要性。\n\nIn C, C++, Java, and similar strongly typed languages, an object can have only a fixed number of properties, and the names of these properties must be defined in advance. Since JavaScript is a loosely typed language, this rule does not apply: a program can create any number of properties in any object. When you use the . operator to access a property of an object, however, the name of the property is expressed as an identifier. Identifiers must be typed literally into your JavaScript program; they are not a datatype, so they cannot be manipulated by the program.\n\n> 在 C、C++、Java 和一些强类型语言中，对象只能拥有固定数目的属性，并且这些属性的名称必须提前定义好。由于 JavaScript 是一个弱类型语言，因此不适用这条规则：对象在程序中可以创建任意数量的属性。当使用 . 运算符访问对象的属性时，属性名用一个标识符来表示。标识符必须直接出现在 JavaScript 程序中，它们不是数据类型，所以无法在程序中修改。\n\nOn the other hand, when you access a property of an object with the [] array notation, the name of the property is expressed as a string. Strings are JavaScript datatypes, so they can be manipulated and created while a program is running. So, for example, you can write the following code in JavaScript:\n\n> 另一种方式，当通过 [] 来访问对象的属性时，属性名通过字符串来表示。字符串是 JavaScript 的数据类型，在程序运行时可以修改和创建它们。因此，可以在 JavaScript 中使用下面这种代码：\n\n```js\nlet addr = \"\";\nfor(let i = 0; i < 4; i++) {\n    addr += customer[`address${i}`] + \"\\n\";\n}\n```\nThis code reads and concatenates the address0, address1, address2, and address3 properties of the customer object.\n\n> 这段代码读取 customer 对象的 address0、address1、address2 和 address3 属性，并将它们连接起来。\n\nThis brief example demonstrates the flexibility of using array notation to access properties of an object with string expressions. This code could be rewritten using the dot notation, but there are cases in which only the array notation will do. Suppose, for example, that you are writing a program that uses network resources to compute the current value of the user’s stock market investments. The program allows the user to type in the name of each stock they own as well as the number of shares of each stock. You might use an object named portfolio to hold this information. The object has one property for each stock. The name of the property is the name of the stock, and the property value is the number of shares of that stock. So, for example, if a user holds 50 shares of stock in IBM, the portfolio.ibm property has the value 50.\n\n> 这个例子主要说明了通过字符串表达式使用数组标记来访问对象属性的灵活性。这段代码也可以通过点运算符来重写，但是一些场景只能使用数组写法来完成。假设你正在写一个程序，这个程序利用网络资源计算用户股票市场投资的当前价值。程序允许用户输入他们拥有的股票名称以及对应的数量。你可以用一个名为 portfolio 的对象来储存这些信息。每一个股票在对象中都有一个属性与之对应。属性名是股票名，属性值是股票持有份额。例如，如果用户持有 IBM 的 50 股，那么 portfolio.ibm 属性的值就为 50。\n\nPart of this program might be a function for adding a new stock to the portfolio:\n\n> 下面是程序的部分代码，这个函数用来给 portifolio 添加新的股票：\n\n```js\nfunction addstock(portfolio, stockname, shares) {\n    portfolio[stockname] = shares;\n}\n```\nSince the user enters stock names at runtime, there is no way that you can know the property names ahead of time. Since you can’t know the property names when you write the program, there is no way you can use the . operator to access the properties of the portfolio object. You can use the [] operator, however, because it uses a string value (which is dynamic and can change at runtime) rather than an identifier (which is static and must be hardcoded in the program) to name the property.\n\n> 由于用户是在程序运行时输入股票名称，因此在之前无法得知这些股票的名称是什么。而由于在写程序的时候不知道属性名称，因此无法通过点运算符（.）来访问对象 portfolio 的属性。但可以使用 [] 运算符，因为它使用字符串值（字符串值是动态的，可以在运行时更改）而不是标识符（标识符是静态的，必须写死在程序中）作为索引对属性进行访问。\n\nIn Chapter 5, we introduced the for/in loop (and we’ll see it again shortly, in §6.6). The power of this JavaScript statement becomes clear when you consider its use with associative arrays. Here is how you would use it when computing the total value of a portfolio:\n\n> 第 5 章介绍了 for/in 循环（§6.6 节还会进一步介绍）。当使用 for/in 循环遍历关联数组时，就可以清晰地体会到 for/in 的强大之处。下面的例子就是利用 for/in 计算  portfolio 的合计值：\n\n```js\nfunction computeValue(portfolio) {\n    let total = 0.0;\n    for(let stock in portfolio) {       // For each stock in the portfolio:\n        let shares = portfolio[stock];  // get the number of shares\n        let price = getQuote(stock);    // look up share price\n        total += shares * price;        // add stock value to total value\n    }\n    return total;                       // Return total value.\n}\n```\nJavaScript objects are commonly used as associative arrays as shown here, and it is important to understand how this works. In ES6 and later, however, the Map class described in §11.1.2 is often a better choice than using a plain object.\n\n> 如本节所示，JavaScript 对象通常用作关联数组，理解其工作原理非常重要。但是，在 ES6 之后使用 Map 类常常是一个更好的选择，我们将在 §11.1.2 中进行描述。\n\n### 6.3.2 Inheritance\nJavaScript objects have a set of “own properties,” and they also inherit a set of properties from their prototype object. To understand this, we must consider property access in more detail. The examples in this section use the Object.create() function to create objects with specified prototypes. We’ll see in Chapter 9, however, that every time you create an instance of a class with new, you are creating an object that inherits properties from a prototype object.\n\n> JavaScript 对象中有一组“自有属性”，也有一组属性是继承自它的原型对象。想要理解属性继承，必须更深入地了解属性访问的细节。这一节的例子通过使用 Object.create() 函数创建对象来指定它的原型。我们会在第 9 章再次看到它，但是，每次使用 new 创建类的实例时，都会创建一个从原型对象继承属性的对象。\n\nSuppose you query the property x in the object o. If o does not have an own property with that name, the prototype object of o1 is queried for the property x. If the prototype object does not have an own property by that name, but has a prototype itself, the query is performed on the prototype of the prototype. This continues until the property x is found or until an object with a null prototype is searched. As you can see, the prototype attribute of an object creates a chain or linked list from which properties are inherited:\n\n> 假设要查询对象 o 的属性 x。如果 o 中不存在 x 名称的自由属性，那么将会继续在 o 的原型对象中查询属性 x。如果原型对象中也没有 x，但这个原型对象也有原型，那么继续在这个原型对象的原型上执行查询，直到找到 x 或者查找到一个原型是 null 的对象为止。可以看到，对象的原型属性构成了一个“链”，通过这个“链”可以实现属性的继承。\n\n```js\nlet o = {};               // o inherits object methods from Object.prototype\no.x = 1;                  // and it now has an own property x.\nlet p = Object.create(o); // p inherits properties from o and Object.prototype\np.y = 2;                  // and has an own property y.\nlet q = Object.create(p); // q inherits properties from p, o, and...\nq.z = 3;                  // ...Object.prototype and has an own property z.\nlet f = q.toString();     // toString is inherited from Object.prototype\nq.x + q.y                 // => 3; x and y are inherited from o and p\n```\nNow suppose you assign to the property x of the object o. If o already has an own (non-inherited) property named x, then the assignment simply changes the value of this existing property. Otherwise, the assignment creates a new property named x on the object o. If o previously inherited the property x, that inherited property is now hidden by the newly created own property with the same name.\n\n> 现在假设给对象 o 的属性 x 赋值，如果 o 中已经有属性 x（这个属性不是继承来的），那么这个赋值操作只改变这个已有属性 x 的值。否则，赋值操作给 o 添加一个新属性 x。如果之前 o 继承自属性 x，那么这个继承的属性就被新创建的同名属性覆盖了。\n\nProperty assignment examines the prototype chain only to determine whether the assignment is allowed. If o inherits a read-only property named x, for example, then the assignment is not allowed. (Details about when a property may be set are in §6.3.3.) If the assignment is allowed, however, it always creates or sets a property in the original object and never modifies objects in the prototype chain. The fact that inheritance occurs when querying properties but not when setting them is a key feature of JavaScript because it allows us to selectively override inherited properties:\n\n> 属性赋值操作检查原型链只是判断是否允许赋值操作。例如，如果 o 继承自一个只读属性 x，那么赋值操作是不允许的（§6.3.3 将对此进行详细讨论）。 如果允许属性赋值操作，它也总是在原始对象上创建属性或对已有的属性赋值，而不会去修改原型链。在 JavaScript 中，只有在查询属性时才会体会到继承的存在，而设置属性则和继承无关，这是 JavaScript 的一个重要特性，该特性让程序员可以有选择地重写继承的属性。\n\n```js\nlet unitcircle = { r: 1 };         // An object to inherit from\nlet c = Object.create(unitcircle); // c inherits the property r\nc.x = 1; c.y = 1;                  // c defines two properties of its own\nc.r = 2;                           // c overrides its inherited property\nunitcircle.r                       // => 1: the prototype is not affected\n```\nThere is one exception to the rule that a property assignment either fails or creates or sets a property in the original object. If o inherits the property x, and that property is an accessor property with a setter method (see §6.10.6), then that setter method is called rather than creating a new property x in o. Note, however, that the setter method is called on the object o, not on the prototype object that defines the property, so if the setter method defines any properties, it will do so on o, and it will again leave the prototype chain unmodified.\n\n> 属性赋值要么失败，要么创建一个属性，要么在原始对象中设置属性。但有一个例外，如果 o 继承自属性 x，而这个属性是一个具有 setter 方法的存取器属性（参照 §6.10.6），那么这时将调用 setter 方法而不是给 o 创建一个属性 x。需要注意的是，setter 方法是由对象 o 调用的，而不是定义这个属性的原型对象调用的。因此如果 setter 方法定义任意属性，这个操作只是针对 o 本身，并不会修改原型链。\n\n### 6.3.3 Property Access Errors\nProperty access expressions do not always return or set a value. This section explains the things that can go wrong when you query or set a property.\n\n> 属性访问表达式并不总是返回或设置一个值。本节讲述查询或设置属性时的一些出错情况。\n\nIt is not an error to query a property that does not exist. If the property x is not found as an own property or an inherited property of o, the property access expression o.x evaluates to undefined. Recall that our book object has a “sub-title” property, but not a “subtitle” property:\n\n> 查询一个不存在的属性并不会报错，如果在对象 o 自身的属性或继承的属性中均未找到属性 x，属性访问表达式 o.x 返回 undefined。回想一下我们的 book 对象有属性“sub-title”，而没有属性“subtitle”：\n\n```js\nbook.subtitle    // => undefined: property doesn't exist\n```\nIt is an error, however, to attempt to query a property of an object that does not exist. The null and undefined values have no properties, and it is an error to query properties of these values. Continuing the preceding example:\n\n> 但是，如果对象不存在，那么试图查询这个不存在的对象的属性就会报错。null 和 undefined 值都没有属性，因此查询这些值的属性会报错，接上例：\n\n```js\nlet len = book.subtitle.length; // !TypeError: undefined doesn't have length\n```\nProperty access expressions will fail if the lefthand side of the . is null or undefined. So when writing an expression like book.author.surname, you should be careful if you are not certain that book and book.author are actually defined. Here are two ways to guard against this kind of problem: \n\n> 如果 . 的左边是 null 或 undefined 时，其属性表达式会失败。所以当写一个像 book.author.surname 一样的表达式时，如果你不确定 book 和 book.author 确实被定义就要小心了。下面提供了两种避免出错的方法：\n\n```js\n// A verbose and explicit technique\nlet surname = undefined;\nif (book) {\n    if (book.author) {\n        surname = book.author.surname;\n    }\n}\n\n// A concise and idiomatic alternative to get surname or null or undefined\nsurname = book && book.author && book.author.surname;\n```\nTo understand why this idiomatic expression works to prevent TypeError exceptions, you might want to review the short-circuiting behavior of the && operator in §4.10.1.\n\n> 为了理解为什么这里的第二种方法可以避免类型错误异常，可以参照 §4.10.1节 中关于 && 运算符的短路行为。\n\nAs described in §4.4.1, ES2020 supports conditional property access with ?., which allows us to rewrite the previous assignment expression as:\n\n> 如 §4.4.1 中所描述，ES2020 支持用 ?. 条件属性访问，它允许这样重写上面的赋值表达式：\n\n```js\nlet surname = book?.author?.surname;\n```\nAttempting to set a property on null or undefined also causes a TypeError. Attempts to set properties on other values do not always succeed, either: some properties are read-only and cannot be set, and some objects do not allow the addition of new properties. In strict mode (§5.6.3), a TypeError is thrown whenever an attempt to set a property fails. Outside of strict mode, these failures are usually silent.\n\n> 当然，给 null 和 undefined 设置属性也会报类型错误。给其他值设置属性也不总是成功，有一些属性是只读的，不能重新赋值，有一些对象不允许新增属性。在严格模式下（§5.6.3），属性设定失败时会抛出 TypeError 异常。在非严格模式下，这些失败的处理经常没有任何反应。\n\nThe rules that specify when a property assignment succeeds and when it fails are intuitive but difficult to express concisely. An attempt to set a property p of an object o fails in these circumstances:\n\n> 尽管属性赋值成功或失败的规律看起来很简单，但要描述清楚并不容易。在这些场景下给对象 o 设置属性 p 会失败：\n\no has an own property p that is read-only: it is not possible to set read-only properties.\n\n> o 中的属性 p 是只读的：不能给只读属性重新赋值。\n\no has an inherited property p that is read-only: it is not possible to hide an inherited read-only property with an own property of the same name.\n\n> o 中的属性 p 是继承属性，且它是只读的：不能通过同名自有属性覆盖只读的继承属性。\n\no does not have an own property p; o does not inherit a property p with a setter method, and o’s extensible attribute (see §14.2) is false. Since p does not already exist in o, and if there is no setter method to call, then p must be added to o. But if o is not extensible, then no new properties can be defined on it.\n\n> o 中不存在自有属性 p：o 没有使用 setter 方法继承属性 p，并且o的可扩展性是（见 §14.2）false。如果 o 中不存在 p，而且没有 setter 方法可供调用，则 p 一定会添加至 o 中。但如果 o 不是可扩展的，那么在 o 中不能定义新属性。\n\n## 6.4 Deleting Properties\nThe delete operator (§4.13.4) removes a property from an object. Its single operand should be a property access expression. Surprisingly, delete does not operate on the value of the property but on the property itself:\n\n> 删除运算符（§4.13.4）能删除对象中的属性。它的操作数应当是一个属性访问表达式。令人意外的是，delete 没有操作属性的值，而是操作属性的属性：\n\n```js\ndelete book.author;          // The book object now has no author property.\ndelete book[\"main title\"];   // Now it doesn't have \"main title\", either.\n```\nThe delete operator only deletes own properties, not inherited ones. (To delete an inherited property, you must delete it from the prototype object in which it is defined. Doing this affects every object that inherits from that prototype.)\n\n> delete 运算符只删除自有属性，不删除继承属性。（想要删除一个继承属性，必须从定义这个属性的原型对象上删除它。这会影响所有继承这个原型的对象。）\n\nA delete expression evaluates to true if the delete succeeded or if the delete had no effect (such as deleting a nonexistent property). delete also evaluates to true when used (meaninglessly) with an expression that is not a property access expression:\n\n> 如果删除成功或删除没有任何影响时删除表达式计算结果是 true（如删除不存在的属性）。delete 作用于非属性访问表达式（无用代码）时也返回 true。\n\n```js\nlet o = {x: 1};    // o has own property x and inherits property toString\ndelete o.x         // => true: deletes property x\ndelete o.x         // => true: does nothing (x doesn't exist) but true anyway\ndelete o.toString  // => true: does nothing (toString isn't an own property)\ndelete 1           // => true: nonsense, but true anyway\n```\ndelete does not remove properties that have a configurable attribute of false. Certain properties of built-in objects are non-configurable, as are properties of the global object created by variable declaration and function declaration. In strict mode, attempting to delete a non-configurable property causes a TypeError. In non-strict mode, delete simply evaluates to false in this case:\n\n> delete 不能删除那些可配置性为 false 的属性。某些内置对象的属性是不可配置的，比如通过变量声明和函数声明创建的全局对象的属性。在严格模式中，删除一个不可配置属性会报一个类型错误。在非严格模式中，在这些情况下的 delete 操作会返回 false：\n\n```js\n// In strict mode, all these deletions throw TypeError instead of returning false\ndelete Object.prototype // => false: property is non-configurable\nvar x = 1;              // Declare a global variable\ndelete globalThis.x     // => false: can't delete this property\nfunction f() {}         // Declare a global function\ndelete globalThis.f     // => false: can't delete this property either\n```\nWhen deleting configurable properties of the global object in non-strict mode, you can omit the reference to the global object and simply follow the delete operator with the property name:\n\n> 当在非严格模式中删除全局对象的可配值属性时，可以省略对全局对象的引用，直接在 delete 操作符后跟随要删除的属性名即可：\n\n```js\nglobalThis.x = 1;       // Create a configurable global property (no let or var)\ndelete x                // => true: this property can be deleted\n```\nIn strict mode, however, delete raises a SyntaxError if its operand is an unqualified identifier like x, and you have to be explicit about the property access:\n\n> 然而在严格模式中，delete 后跟随一个非法的操作数（比如 x），则会报一个语法错误，因此必须显式指定对象及其属性：\n\n```js\ndelete x;               // SyntaxError in strict mode\ndelete globalThis.x;    // This works\n```\n## 6.5 Testing Properties\nJavaScript objects can be thought of as sets of properties, and it is often useful to be able to test for membership in the set—to check whether an object has a property with a given name. You can do this with the in operator, with the hasOwnProperty() and propertyIsEnumerable() methods, or simply by querying the property. The examples shown here all use strings as property names, but they also work with Symbols (§6.10.3).\n\n> JavaScript 对象可以看作属性的集合，我们经常会检测集合中成员的所属关系——判断某个属性是否存在于某个对象中。可以用 in 运算符、hasOwnPreperty() 和  propertyIsEnumerable() 方法来完成这个工作，甚至仅通过属性查询也可以做到这一点。这节的例子都是用字符串作为属性名称，但是也可以用 Symbol 作为属性名（§6.10.3）。\n\nThe in operator expects a property name on its left side and an object on its right. It returns true if the object has an own property or an inherited property by that name:\n\n> in 运算符的左侧是属性名，右侧是对象。如果对象的自有属性或继承属性中包含这个属性则返回 true：\n\n```js\nlet o = { x: 1 };\n\"x\" in o         // => true: o has an own property \"x\"\n\"y\" in o         // => false: o doesn't have a property \"y\"\n\"toString\" in o  // => true: o inherits a toString property\n```\nThe hasOwnProperty() method of an object tests whether that object has an own property with the given name. It returns false for inherited properties:\n\n> 对象的 hasOwnProperty() 方法用来检测给定的名字是否是对象的自有属性。对于继承属性它将返回 false：\n\n```js\nlet o = { x: 1 };\no.hasOwnProperty(\"x\")        // => true: o has an own property x\no.hasOwnProperty(\"y\")        // => false: o doesn't have a property y\no.hasOwnProperty(\"toString\") // => false: toString is an inherited property\n```\nThe propertyIsEnumerable() refines the hasOwnProperty() test. It returns true only if the named property is an own property and its enumerable attribute is true. Certain built-in properties are not enumerable. Properties created by normal JavaScript code are enumerable unless you’ve used one of the techniques shown in §14.1 to make them non-enumerable.\n\n> propertyIsEnumerable() 是 hasOwnProperty() 的增强版。只有检测到是自有属性且这个属性的可枚举性为 true 时它才返回 true。某些内置属性是不可枚举的。通常由 JavaScript 代码创建的属性都是可枚举的，除非使用  §14.1 中介绍的技术来让它们不可枚举。\n\n```js\nlet o = { x: 1 };\no.propertyIsEnumerable(\"x\")  // => true: o has an own enumerable property x\no.propertyIsEnumerable(\"toString\")  // => false: not an own property\nObject.prototype.propertyIsEnumerable(\"toString\") // => false: not enumerable\n```\nInstead of using the in operator, it is often sufficient to simply query the property and use !== to make sure it is not undefined:\n\n> 除了使用 in 运算符之外，另一种更简便的方法是使用 !== 判断一个属性是否是 undefined：\n\n```js\nlet o = { x: 1 };\no.x !== undefined        // => true: o has a property x\no.y !== undefined        // => false: o doesn't have a property y\no.toString !== undefined // => true: o inherits a toString property\n```\nThere is one thing the in operator can do that the simple property access technique shown here cannot do. in can distinguish between properties that do not exist and properties that exist but have been set to undefined. Consider this code:\n\n> 然而有一种场景只能使用 in 运算符而不能使用上述属性访问的方式。in 可以区分不存在的属性和存在但值为 undefined 的属性。例如下面的代码：\n\n```js\nlet o = { x: undefined };  // Property is explicitly set to undefined\no.x !== undefined          // => false: property exists but is undefined\no.y !== undefined          // => false: property doesn't even exist\n\"x\" in o                   // => true: the property exists\n\"y\" in o                   // => false: the property doesn't exist\ndelete o.x;                // Delete the property x\n\"x\" in o                   // => false: it doesn't exist anymore\n```\n## 6.6 Enumerating Properties\nInstead of testing for the existence of individual properties, we sometimes want to iterate through or obtain a list of all the properties of an object. There are a few different ways to do this.\n\n> 除了检测对象的属性是否存在，我们还会经常遍历对象的属性。有几种不同的方法可以做到这一点。\n\nThe for/in loop was covered in §5.4.5. It runs the body of the loop once for each enumerable property (own or inherited) of the specified object, assigning the name of the property to the loop variable. Built-in methods that objects inherit are not enumerable, but the properties that your code adds to objects are enumerable by default. For example:\n\n> §5.4.5 讨论过 for/in 循环，其可以在循环体中遍历指定对象中所有可枚举的属性（包括自有属性和继承的属性），把属性名称赋值给循环变量。对象继承的 内置方法不可枚举的，但在代码中给对象添加的属性都是可枚举的（除非用下文中提到的一个方法将它们转换为不可枚举的）。例如：\n\n```js\nlet o = {x: 1, y: 2, z: 3};          // Three enumerable own properties\no.propertyIsEnumerable(\"toString\")   // => false: not enumerable\nfor(let p in o) {                    // Loop through the properties\n    console.log(p);                  // Prints x, y, and z, but not toString\n}\n```\nTo guard against enumerating inherited properties with for/in, you can add an explicit check inside the loop body:\n\n> 为了防止 for/in 枚举到继承属性，可以在循环中添加显示检查：\n\n```js\nfor(let p in o) {\n    if (!o.hasOwnProperty(p)) continue;       // Skip inherited properties\n}\n\nfor(let p in o) {\n    if (typeof o[p] === \"function\") continue; // Skip all methods\n}\n```\nAs an alternative to using a for/in loop, it is often easier to get an array of property names for an object and then loop through that array with a for/of loop. There are four functions you can use to get an array of property names:\n\n> 作为使用 for/in 循环的替代方法，通常使用 for/of 循环遍历易获取对象的属性名称数组。可以使用四个函数获取属性名称数组：\n\nObject.keys() returns an array of the names of the enumerable own properties of an object. It does not include non-enumerable properties, inherited properties, or properties whose name is a Symbol (see §6.10.3).\n\n> Object.keys() 返回对象的可枚举自有属性名称数组集合。数组内不包含不可枚举属性、继承属性或属性名称是 Symbol（见 §6.10.3）的属性\n\nObject.getOwnPropertyNames() works like Object.keys() but returns an array of the names of non-enumerable own properties as well, as long as their names are strings.\n\n> Object.getOwnPropertyNames() 用起来和 Object.keys() 类似，但是它返回数组中也包含不可迭代的自有属性，只要它们的名称是字符串。\n\nObject.getOwnPropertySymbols() returns own properties whose names are Symbols, whether or not they are enumerable.\n\n> Object.getOwnPropertySymbols() 返回名称是 Symbol 的自有属性，无论它们是否可枚举。\n\nReflect.ownKeys() returns all own property names, both enumerable and non-enumerable, and both string and Symbol. (See §14.6.)\n\n> Reflect.ownKeys() 返回所有的自由属性名称，包括可枚举和不可枚举类型，也包括字符串和 Symbol（见 §14.6）。\n\nThere are examples of the use of Object.keys() with a for/of loop in §6.7.\n\n> 在 §6.7 中有例子使用 for/of 循环 Object.keys()。\n\n### 6.6.1 Property Enumeration Order\nES6 formally defines the order in which the own properties of an object are enumerated. Object.keys(), Object.getOwnPropertyNames(), Object.getOwnPropertySymbols(), Reflect.ownKeys(), and related methods such as JSON.stringify() all list properties in the following order, subject to their own additional constraints about whether they list non-enumerable properties or properties whose names are strings or Symbols:\n\n> ES6 正式定义元素的自有属性的枚举顺序。Object.keys()、Object.getOwnPropertyNames()、Object.getOwnPropertyNames()、Object.getOwnPropertySymbols()、Reflect.ownKeys() 和相关方法如 JSON.stringify() 属性列表都按以下顺序排列的，受它们自身是否是不可枚举属性列表或者属性是字符串或者 Symbol 影响：\n\nString properties whose names are non-negative integers are listed first, in numeric order from smallest to largest. This rule means that arrays and array-like objects will have their properties enumerated in order.\n\n> 首先列出名称为非负整数的字符串属性，按从最小到最大的数字顺序列出。此规则意味着数组和数组类对象将按顺序枚举其属性。\n\nAfter all properties that look like array indexes are listed, all remaining properties with string names are listed (including properties that look like negative numbers or floating-point numbers). These properties are listed in the order in which they were added to the object. For properties defined in an object literal, this order is the same order they appear in the literal.\n\n> 列出所有看起来像数组索引的属性后，将列出所有具有字符串名称的剩余属性（包括看起来像负数或浮点数字的属性）。这些属性按添加到对象的顺序列出。对于在对象字面量中定义的属性，此顺序与它们在文本中显示的顺序相同。\n\nFinally, the properties whose names are Symbol objects are listed in the order in which they were added to the object.\n\n> 最后，其名称为 Symbol 对象的属性按添加到对象的顺序列出。\n\nThe enumeration order for the for/in loop is not as tightly specified as it is for these enumeration functions, but implementations typically enumerate own properties in the order just described, then travel up the prototype chain enumerating properties in the same order for each prototype object. Note, however, that a property will not be enumerated if a property by that same name has already been enumerated, or even if a non-enumerable property by the same name has already been considered.\n\n> for/in 循环的枚举顺序不像这些枚举函数那样严格指定，但实现通常按刚才描述的顺序枚举自己的属性，然后向上移动原型链按相同顺序枚举每个原型对象的属性。但是请注意，如果已枚举具有相同名称的属性，或者不可枚举属性已经被检测过再次枚举到相同名称的属性，都不会再次枚举。\n\n## 6.7 Extending Objects\nA common operation in JavaScript programs is needing to copy the properties of one object to another object. It is easy to do that with code like this:\n\n> 在 JavaScript 代码中有一个很常见的操作，需要将一个对象中的属性拷贝到另外一个对象。以下面的代码很容易实现：\n\n```js\nlet target = {x: 1}, source = {y: 2, z: 3};\nfor(let key of Object.keys(source)) {\n    target[key] = source[key];\n}\ntarget  // => {x: 1, y: 2, z: 3}\n```\nBut because this is a common operation, various JavaScript frameworks have defined utility functions, often named extend(), to perform this copying operation. Finally, in ES6, this ability comes to the core JavaScript language in the form of Object.assign().\n\n> 但是因为这个是个常用的操作，各种 JavaScript 框架定义公用函数，经常将其命名为 extend() 来执行这个拷贝操作。最后在 ES6 中，这个功能以 Object.assign() 的形式被添加到 JavaScript 核心语言中。\n\nObject.assign() expects two or more objects as its arguments. It modifies and returns the first argument, which is the target object, but does not alter the second or any subsequent arguments, which are the source objects. For each source object, it copies the enumerable own properties of that object (including those whose names are Symbols) into the target object. It processes the source objects in argument list order so that properties in the first source object override properties by the same name in the target object and properties in the second source object (if there is one) override properties with the same name in the first source object.\n\n> Object.assign() 需要两个或多个对象作为其实参。它修改并返回第一个实参，即目标对象，但不会改变第二个或任何后续参数，这些参数是源对象。对于每个源对象，它将该对象的可枚举自有属性（包括名称为 Symbol 的属性）复制到目标对象中。它按源对象在实参列表顺序中的顺序处理，所以第一个源对象中的属性会重写在目标对象中的同名属性，然后以第二个源对象中的同名属性（如果有第二个源对象）再次重写第一个源对象重写后的属性。\n\nObject.assign() copies properties with ordinary property get and set operations, so if a source object has a getter method or the target object has a setter method, they will be invoked during the copy, but they will not themselves be copied.\n\n> Object.assign() 通过普通属性的 get 和 set 操作复制属性，因此，如果源对象具有 getter 方法或目标对象具有 setter 方法，则将在复制期间调用它们，但不会复制方法本身。\n\nOne reason to assign properties from one object into another is when you have an object that defines default values for many properties and you want to copy those default properties into another object if a property by that name does not already exist in that object. Using Object.assign() naively will not do what you want:\n\n> 看这样一个场景，有一个对象定义许多属性的默认值，希望将这些默认属性中不存在于目标对象中的属性复制到目标对象中，使用 Object.assign() 不会得到想要的结果：\n\n```js\nObject.assign(o, defaults);  // overwrites everything in o with defaults\n```\nInstead, what you can do is to create a new object, copy the defaults into it, and then override those defaults with the properties in o:\n\n> 想得到这个效果需要创建一个新的对象，将默认值拷贝到其中，然后用 o 的属性重写默认值中的属性：\n\n```js\no = Object.assign({}, defaults, o);\n```\nWe’ll see in §6.10.4 that you can also express this object copy-and-override operation using the ... spread operator like this:\n\n> 我们会在 §6.10.4 见到，可以用 ... 展开操作符如下操作这个对象拷贝并重写：\n\n```js\no = {...defaults, ...o};\n```\nWe could also avoid the overhead of the extra object creation and copying by writing a version of Object.assign() that copies properties only if they are missing:\n\n> 为了避免对象创建和复制的额外开销，我们还可以通过编写一个 Object.assign() 仅在缺少属性时复制属性：\n\n```js\n// Like Object.assign() but doesn't override existing properties\n// (and also doesn't handle Symbol properties)\nfunction merge(target, ...sources) {\n    for(let source of sources) {\n        for(let key of Object.keys(source)) {\n            if (!(key in target)) { // This is different than Object.assign()\n                target[key] = source[key];\n            }\n        }\n    }\n    return target;\n}\nObject.assign({x: 1}, {x: 2, y: 2}, {y: 3, z: 4})  // => {x: 2, y: 3, z: 4}\nmerge({x: 1}, {x: 2, y: 2}, {y: 3, z: 4})          // => {x: 1, y: 2, z: 4}\n```\nIt is straightforward to write other property manipulation utilities like this merge() function. A restrict() function could delete properties of an object if they do not appear in another template object, for example. Or a subtract() function could remove all of the properties of one object from another object.\n\n> 编写其他属性操作公共函数很简单，就是像这个 merge() 函数。例如，如果对象的属性不出现在另一个模板对象中，则 restrict() 函数会删除这些属性。或者，subtract() 函数可以从其他对象中删除一个对象的所有属性。\n\n## 6.8 Serializing Objects\nObject serialization is the process of converting an object’s state to a string from which it can later be restored. The functions JSON.stringify() and JSON.parse() serialize and restore JavaScript objects. These functions use the JSON data interchange format. JSON stands for “JavaScript Object Notation,” and its syntax is very similar to that of JavaScript object and array literals:\n\n> 对象序列化（serialization）是指将对象的状态转换为字符串，也可将字符串还原为对象。函数 JSON.stringify() 和 JSON.parse() 用来序列化和还原 JavaScript 对象。这些方法都使用 JSON 作为数据交换格式，JSON 的全称是“JavaScript Object Notation”——JavaScript 对象表示法，它的语法和 JavaScript 对象与数组字面量的语法非常相近：\n\n```js\nlet o = {x: 1, y: {z: [false, null, \"\"]}}; // Define a test object\nlet s = JSON.stringify(o);   // s == '{\"x\":1,\"y\":{\"z\":[false,null,\"\"]}}'\nlet p = JSON.parse(s);       // p == {x: 1, y: {z: [false, null, \"\"]}}\n```\nJSON syntax is a subset of JavaScript syntax, and it cannot represent all JavaScript values. Objects, arrays, strings, finite numbers, true, false, and null are supported and can be serialized and restored. NaN, Infinity, and -Infinity are serialized to null. Date objects are serialized to ISO-formatted date strings (see the Date.toJSON() function), but JSON.parse() leaves these in string form and does not restore the original Date object. Function, RegExp, and Error objects and the undefined value cannot be serialized or restored. JSON.stringify() serializes only the enumerable own properties of an object. If a property value cannot be serialized, that property is simply omitted from the stringified output. Both JSON.stringify() and JSON.parse() accept optional second arguments that can be used to customize the serialization and/or restoration process by specifying a list of properties to be serialized, for example, or by converting certain values during the serialization or stringification process. Complete documentation for these functions is in §11.6.\n\n> JSON 的语法是 JavaScript 语法的子集，它并不能表示 JavaScript 里的所有值。支持对象、数组、字符串、无穷大数字、true、false 和 null，并且它们可以序列化和还原。NaN、Infinity 和 -Infinity 序列化的结果是 null，日期对象序列化的结果是 ISO 格式的日期字符串（参照 Date.toJSON() 函数），但 JSON.parse() 依然保留它们的字符串形态，而不会将它们还原为原始日期对象。函数、RegExp、Error 对象和 undefined 值不能序列化和还原。JSON.stringify() 只能序列化对象可枚举的自有属性。对于一个不能序列化的属性来说，在序列化后的输出字符串中会将这个属性省略掉。JSON.stringify() 和 JSON.parse() 都可以接收第二个可选实参，通过传入需要序列化或还原的属性列表来定制自定义的序列化或还原操作。§11.6 有关于这些函数的详细文档。\n\n## 6.9 Object Methods\nAs discussed earlier, all JavaScript objects (except those explicitly created without a prototype) inherit properties from Object.prototype. These inherited properties are primarily methods, and because they are universally available, they are of particular interest to JavaScript programmers. We’ve already seen the hasOwnProperty() and propertyIsEnumerable() methods, for example. (And we’ve also already covered quite a few static functions defined on the Object constructor, such as Object.create() and Object.keys().) This section explains a handful of universal object methods that are defined on Object.prototype, but which are intended to be replaced by other, more specialized implementations. In the sections that follow, we show examples of defining these methods on a single object. In Chapter 9, you’ll learn how to define these methods more generally for an entire class of objects.\n\n> 上文已经讨论过，所有的 JavaScript 对象都从 Object.prototype 继承属性（除了那些不通过原型显式创建的对象）。这些继承属性主要是方法，因为 JavaScript 程序员普遍对继承方法更感兴趣。例如我们已经见过的 hasOwnProperty() 和 propertyIsEnumerable() 方法。（并且我们也已经提到了一小部分定义在对象构造函数中的静态函数，像 Object.create() 和 Object.keys()。）本节介绍在 Object.prototype 上定义的少数通用对象方法，但是这些方法经常会被更专业的实现所取代。在下面的各节中，我们将展示在单个对象上定义这些方法的示例。在第 9 章中，将学习如何更常规化地为整个对象类定义这些方法。\n\n### 6.9.1 The toString() Method\nThe toString() method takes no arguments; it returns a string that somehow represents the value of the object on which it is invoked. JavaScript invokes this method of an object whenever it needs to convert the object to a string. This occurs, for example, when you use the + operator to concatenate a string with an object or when you pass an object to a method that expects a string.\n\n> toString() 方法没有实参，它将返回一个表示调用这个方法的对象值的字符串。在需要将对象转换为字符串的时候，JavaScript 都会调用这个方法。比如，当使用 + 运算符连接一个字符串和一个对象时或者在希望使用字符串的方法中使用了对象时都会调用 toString()。\n\nThe default toString() method is not very informative (though it is useful for determining the class of an object, as we will see in §14.4.3). For example, the following line of code simply evaluates to the string “[object Object]”:\n\n> 默认的 toString() 方法的返回值带有的信息量很少（尽管它在检测对象的类型时非常有用，参照 §14.4.3），例如，下面这行代码的计算结果为字符串”[object Object]”：\n\n```js\nlet s = { x: 1, y: 1 }.toString();  // s == \"[object Object]\"\n```\nBecause this default method does not display much useful information, many classes define their own versions of toString(). For example, when an array is converted to a string, you obtain a list of the array elements, themselves each converted to a string, and when a function is converted to a string, you obtain the source code for the function. You might define your own toString() method like this:\n\n> 由于默认的 toString() 方法并不会输出很多有用的信息，因此很多类都带有自定义的 toString()。例如，当数组转换为字符串的时候，结果是一个数组元素列表，只是每个元素都转换成了字符串，再比如，当函数转换为字符串的时候，得到函数的源代码。可以像下面这样自定义 toString() 方法：\n\n```js\nlet point = {\n    x: 1,\n    y: 2,\n    toString: function() { return `(${this.x}, ${this.y})`; }\n};\nString(point)    // => \"(1, 2)\": toString() is used for string conversions\n```\n### 6.9.2 The toLocaleString() Method\nIn addition to the basic toString() method, objects all have a toLocaleString(). The purpose of this method is to return a localized string representation of the object. The default toLocaleString() method defined by Object doesn’t do any localization itself: it simply calls toString() and returns that value. The Date and Number classes define customized versions of toLocaleString() that attempt to format numbers, dates, and times according to local conventions. Array defines a toLocaleString() method that works like toString() except that it formats array elements by calling their toLocaleString() methods instead of their toString() methods. You might do the same thing with a point object like this:\n\n> 除了基本的 toString() 方法之外，对象都包含 toLocaleString() 方法，这个方法返回一个表示这个对象的本地化字符串。Object 中默认的 toLocaleString() 方法并不做任何本地化自身的操作，它仅调用 toString() 方法并返回对应值。Date 和 Number 类对 toLocaleString() 方法做了定制，可以用它对数字、日期和时间做本地化的转换。 Array 类的 toLocaleString() 方法和 toString() 方法很像，唯一的不同是每个数组元素会调用 toLocaleString() 方法转换为字符串，而不是调用各自的 toString() 方法。可以像这样使用 point 对象做到同样的效果：\n\n```js\nlet point = {\n    x: 1000,\n    y: 2000,\n    toString: function() { return `(${this.x}, ${this.y})`; },\n    toLocaleString: function() {\n        return `(${this.x.toLocaleString()}, ${this.y.toLocaleString()})`;\n    }\n};\npoint.toString()        // => \"(1000, 2000)\"\npoint.toLocaleString()  // => \"(1,000, 2,000)\": note thousands separators\n```\nThe internationalization classes documented in §11.7 can be useful when implementing a toLocaleString() method.\n\n> 在 §11.7 的国际化类中，toLocaleString() 方法的实现是非常有用的。\n\n### 6.9.3 The valueOf() Method\nThe valueOf() method is much like the toString() method, but it is called when JavaScript needs to convert an object to some primitive type other than a string—typically, a number. JavaScript calls this method automatically if an object is used in a context where a primitive value is required. The default valueOf() method does nothing interesting, but some of the built-in classes define their own valueOf() method. The Date class defines valueOf() to convert dates to numbers, and this allows Date objects to be chronologically compared with < and >. You could do something similar with a point object, defining a valueOf() method that returns the distance from the origin to the point:\n\n> valueOf() 方法和 toString() 方法非常类似，但往往当 JavaScript 需要将对象转换为某种原始值而非字符串的时候才会调用它，尤其是转换为数字的时候。如果在需要使用原始值的上下文中使用了对象，JavaScript 就会自动调用这个方法。默认的 valueOf() 方法不足为奇，但有些内置类自定义了 valueOf() 方法. Date 类定义 valueOf() 将日期转化成数值型，并且这允许 Date 对象使用 < 和 > 按时间先手顺序比较。可以对 point 对象做同样的事，定义一个 valueOf() 方法返回原点到点的距离：\n\n```js\nlet point = {\n    x: 3,\n    y: 4,\n    valueOf: function() { return Math.hypot(this.x, this.y); }\n};\nNumber(point)  // => 5: valueOf() is used for conversions to numbers\npoint > 4      // => true\npoint > 5      // => false\npoint < 6      // => true\n```\n### 6.9.4 The toJSON() Method\nObject.prototype does not actually define a toJSON() method, but the JSON.stringify() method (see §6.8) looks for a toJSON() method on any object it is asked to serialize. If this method exists on the object to be serialized, it is invoked, and the return value is serialized, instead of the original object. The Date class (§11.4) defines a toJSON() method that returns a serializable string representation of the date. We could do the same for our Point object like this:\n\n> Object.prototype 实际上没有定义 toJSON() 方法，但对于需要执行序列化的对象来说，JSON.stringify() 方法（见 §6.8）会调用 toJSON() 方法。如果在待序列化的对象中存在这 个方法，则调用它，返回值即是序列化的结果，而不是原始的对象。Date 类（§11.4）定义了 toJSON() 方法返回日期的序列化字符串。我们可以这样对 point 对象做同样的事：\n\n```js\nlet point = {\n    x: 1,\n    y: 2,\n    toString: function() { return `(${this.x}, ${this.y})`; },\n    toJSON: function() { return this.toString(); }\n};\nJSON.stringify([point])   // => '[\"(1, 2)\"]'\n```\n## 6.10 Extended Object Literal Syntax\nRecent versions of JavaScript have extended the syntax for object literals in a number of useful ways. The following subsections explain these extensions.\n\n> JavaScript 的最新版本扩展了许多有用的对象字面量相关的语法。以下小节解释这些扩展。\n\n### 6.10.1 Shorthand Properties\nSuppose you have values stored in variables x and y and want to create an object with properties named x and y that hold those values. With basic object literal syntax, you’d end up repeating each identifier twice:\n\n> 假设值存储在变量 x 和 y 中，并且想要创建具有名为 x 和 y 的属性的对象，这些属性包含这些值。使用基本对象字面量语法，最终会重复每个标识符两次：\n\n```js\nlet x = 1, y = 2;\nlet o = {\n    x: x,\n    y: y\n};\n```\nIn ES6 and later, you can drop the colon and one copy of the identifier and end up with much simpler code:\n\n> 在 ES6 之后，可以删除标识符的冒号和一个副本，最终使用更简单的代码：\n\n```js\nlet x = 1, y = 2;\nlet o = { x, y };\no.x + o.y  // => 3\n```\n### 6.10.2 Computed Property Names\nSometimes you need to create an object with a specific property, but the name of that property is not a compile-time constant that you can type literally in your source code. Instead, the property name you need is stored in a variable or is the return value of a function that you invoke. You can’t use a basic object literal for this kind of property. Instead, you have to create an object and then add the desired properties as an extra step:\n\n> 有时需要创建具有特定属性的对象，但该属性的名称不是可以在源代码中键入的编译时常量。相反，需要的属性名称存储在变量中，或者是调用的函数的返回值。不能对此类属性使用基本对象字面量。而必须创建一个对象，通过额外的步骤，添加所需的属性：\n\n```js\nconst PROPERTY_NAME = \"p1\";\nfunction computePropertyName() { return \"p\" + 2; }\n\nlet o = {};\no[PROPERTY_NAME] = 1;\no[computePropertyName()] = 2;\n```\nIt is much simpler to set up an object like this with an ES6 feature known as computed properties that lets you take the square brackets from the preceding code and move them directly into the object literal:\n\n> 使用称为计算属性的 ES6 特性设置这样的对象要简单得多，该功能允许从前面的代码写入方括内并直接移动到对象字面量中：\n\n```js\nconst PROPERTY_NAME = \"p1\";\nfunction computePropertyName() { return \"p\" + 2; }\n\nlet p = {\n    [PROPERTY_NAME]: 1,\n    [computePropertyName()]: 2\n};\n\np.p1 + p.p2 // => 3\n```\nWith this new syntax, the square brackets delimit an arbitrary JavaScript expression. That expression is evaluated, and the resulting value (converted to a string, if necessary) is used as the property name.\n\n> 使用这种新语法，方括号将其分隔成 JavaScript 表达式。计算该表达式，并将结果值（如有必要转换为字符串）用作属性名称。\n\nOne situation where you might want to use computed properties is when you have a library of JavaScript code that expects to be passed objects with a particular set of properties, and the names of those properties are defined as constants in that library. If you are writing code to create the objects that will be passed to that library, you could hardcode the property names, but you’d risk bugs if you type the property name wrong anywhere, and you’d risk version mismatch issues if a new version of the library changes the required property names. Instead, you might find that it makes your code more robust to use computed property syntax with the property name constants defined by the library.\n\n> 可能想要使用计算属性的一个情况是，有一个 JavaScript 代码库，该库希望传递具有一组特定属性的对象，并且这些属性的名称定义为该库中的常量。如果要编写代码以创建将传递给该库的对象，可以硬编码属性名称，但如果在任何地方键入错误的属性名称，则存在错误风险；如果库的新版本更改了所需的属性名称，则存在版本不匹配问题的风险。相反，可能会发现，使用计算属性语法与库定义的属性名称常量时，它使代码更加健壮。\n\n### 6.10.3 Symbols as Property Names\nThe computed property syntax enables one other very important object literal feature. In ES6 and later, property names can be strings or symbols. If you assign a symbol to a variable or constant, then you can use that symbol as a property name using the computed property syntax:\n\n> 计算属性语法启用了另一个非常重要的对象字面量特性。在 ES6 之后，属性名称可以是字符串或 Symbol。如果将 Symbol 分配给变量或常量，则可以使用计算属性语法将该 Symbol 用作属性名称：\n\n```js\nconst extension = Symbol(\"my extension symbol\");\nlet o = {\n    [extension]: { /* extension data stored in this object */ }\n};\no[extension].x = 0; // This won't conflict with other properties of o\n```\nAs explained in §3.6, Symbols are opaque values. You can’t do anything with them other than use them as property names. Every Symbol is different from every other Symbol, however, which means that Symbols are good for creating unique property names. Create a new Symbol by calling the Symbol() factory function. (Symbols are primitive values, not objects, so Symbol() is not a constructor function that you invoke with new.) The value returned by Symbol() is not equal to any other Symbol or other value. You can pass a string to Symbol(), and this string is used when your Symbol is converted to a string. But this is a debugging aid only: two Symbols created with the same string argument are still different from one another.\n\n> 如 §3.6 中所述，符号是不透明值。除了将它们用作属性名称，不能对它们进行任何其他处理。但是，每个 Symbol 都不同于所有其他 Symbol，这意味着 Symbol 适合创建唯一的属性名称。通过调用 Symbol() 工厂函数创建新 Symbol。（Symbol 是原始值，而不是对象，因此 Symbol() 不是使用 new 调用的构造函数。）Symbol() 返回的值不等于任何其他 Symbol 或其他值。可以将字符串传递给 Symbol()，当 Symbol 转换为字符串时，将使用此字符串。但是，这只是一个调试帮助：使用同一字符串参数创建的两个 Symbol 仍然彼此不同。\n\nThe point of Symbols is not security, but to define a safe extension mechanism for JavaScript objects. If you get an object from third-party code that you do not control and need to add some of your own properties to that object but want to be sure that your properties will not conflict with any properties that may already exist on the object, you can safely use Symbols as your property names. If you do this, you can also be confident that the third-party code will not accidentally alter your symbolically named properties. (That third-party code could, of course, use Object.getOwnPropertySymbols() to discover the Symbols you’re using and could then alter or delete your properties. This is why Symbols are not a security mechanism.)\n\n> Symbol 的要点不是安全性，而是为 JavaScript 对象定义一个安全的扩展机制。如果从第三方代码获取对象，您无法控制该对象，并且需要向该对象添加自己的一些属性，但希望确保属性不会与对象上可能存在的任何属性冲突，可以安全地使用 Symbol 作为属性名称。如果这样做，还可以确信第三方代码不会意外更改 Symbol 命名的属性。（当然，该第三方代码可以使用 Object.getOwnPropertySymbols() 来发现你使用的 Symbol，然后可以更改或删除你的属性。这就是为什么符号不是安全机制。）\n\n### 6.10.4 Spread Operator\nIn ES2018 and later, you can copy the properties of an existing object into a new object using the “spread operator” ... inside an object literal:\n\n> 在 ES2018 之后，可以使用展开运算符 ... 将现有的对象中的属性复制到新的对象中：\n\n```js\nlet position = { x: 0, y: 0 };\nlet dimensions = { width: 100, height: 75 };\nlet rect = { ...position, ...dimensions };\nrect.x + rect.y + rect.width + rect.height // => 175\n```\nIn this code, the properties of the position and dimensions objects are “spread out” into the rect object literal as if they had been written literally inside those curly braces. Note that this ... syntax is often called a spread operator but is not a true JavaScript operator in any sense. Instead, it is a special-case syntax available only within object literals. (Three dots are used for other purposes in other JavaScript contexts, but object literals are the only context where the three dots cause this kind of interpolation of one object into another one.)\n\n> 在此代码中，position 和 dimensions 对象的属性被展开到 rect 对象字面量中，就像它们以字面量的方式写入这些大括号中一样。请注意，... 语法通常称为展开运算符，但在任何情况下都不是真正的 JavaScript 运算符。相反，它是一种特殊情况下语法，仅在对象文本中可用。（... 在别的 JavaScript 上下文中有其他用途，但是对象字面量上下文中只有这一种用法。）\n\nIf the object that is spread and the object it is being spread into both have a property with the same name, then the value of that property will be the one that comes last:\n\n> 如果展开的目标对象和源对象中具有相同的名称，则该属性的值将是位置处于后面的值：\n\n```js\nlet o = { x: 1 };\nlet p = { x: 0, ...o };\np.x   // => 1: the value from object o overrides the initial value\nlet q = { ...o, x: 2 };\nq.x   // => 2: the value 2 overrides the previous value from o.\n```\nAlso note that the spread operator only spreads the own properties of an object, not any inherited ones:\n\n> 还有注意展开运算符只展开对象的自有属性，不展开继承属性：\n\n```js\nlet o = Object.create({x: 1}); // o inherits the property x\nlet p = { ...o };\np.x                            // => undefined\n```\nFinally, it is worth noting that, although the spread operator is just three little dots in your code, it can represent a substantial amount of work to the JavaScript interpreter. If an object has n properties, the process of spreading those properties into another object is likely to be an O(n) operation. This means that if you find yourself using ... within a loop or recursive function as a way to accumulate data into one large object, you may be writing an inefficient O(n2) algorithm that will not scale well as n gets larger.\n\n> 最后，值得注意的是，虽然展开运算符在代码中只是三个小点，但它对 JavaScript 解释器来说可以代表大量的工作。如果对象具有 n 个属性，则将这些属性分散到另一个对象的过程很可能是 O(n) 操作。这意味着，如果你发现自己在使用 ... 在循环或递归函数中，类似将数据累积到一个大对象中的方法，您可能正在编写一个低效的 O(n2) 算法，该算法不会随着 n 变大而扩展。\n\n### 6.10.5 Shorthand Methods\nWhen a function is defined as a property of an object, we call that function a method (we’ll have a lot more to say about methods in Chapters 8 and 9). Prior to ES6, you would define a method in an object literal using a function definition expression just as you would define any other property of an object:\n\n> 当函数被定义为对象的属性时，我们称该函数为方法（我们将在第 8 章和第 9 章中对方法有更多的描述）。在 ES6 之前，在对象字面量中用函数定义表达式定义一个方法和在对象中定义其他属性一样：\n\n```js\nlet square = {\n    area: function() { return this.side * this.side; },\n    side: 10\n};\nsquare.area() // => 100\n```\nIn ES6, however, the object literal syntax (and also the class definition syntax we’ll see in Chapter 9) has been extended to allow a shortcut where the function keyword and the colon are omitted, resulting in code like this:\n\n> 但是，在 ES6 中，对象字面量语法（以及我们将在第 9 章中看到的类定义语法）已扩展成允许省略函数关键字和冒号的快捷方式，可以写成这样的代码：\n\n```js\nlet square = {\n    area() { return this.side * this.side; },\n    side: 10\n};\nsquare.area() // => 100\n```\nBoth forms of the code are equivalent: both add a property named area to the object literal, and both set the value of that property to the specified function. The shorthand syntax makes it clearer that area() is a method and not a data property like side.\n\n> 两种形式是相同的：在对象字面量中添加一个名为 area 的属性，并指定一个函数为这个属性的值。速记语法更清晰的看出 area() 是一个方法而不是一个像 side 一样的数据属性。\n\nWhen you write a method using this shorthand syntax, the property name can take any of the forms that are legal in an object literal: in addition to a regular JavaScript identifier like the name area above, you can also use string literals and computed property names, which can include Symbol property names:\n\n> 使用此速记语法编写方法时，属性名称可以采用对象字面量中的任何合法形式：除了像上面的名称 area 这样的常规 JavaScript 标识符外，还可以使用字符串字面量和计算属性名称，包括 Symbol 属性名称：\n\n```js\nconst METHOD_NAME = \"m\";\nconst symbol = Symbol();\nlet weirdMethods = {\n    \"method With Spaces\"(x) { return x + 1; },\n    [METHOD_NAME](x) { return x + 2; },\n    [symbol](x) { return x + 3; }\n};\nweirdMethods[\"method With Spaces\"](1)  // => 2\nweirdMethods[METHOD_NAME](1)           // => 3\nweirdMethods[symbol](1)                // => 4\n```\nUsing a Symbol as a method name is not as strange as it seems. In order to make an object iterable (so it can be used with a for/of loop), you must define a method with the symbolic name Symbol.iterator, and there are examples of doing exactly that in Chapter 12.\n\n> 使用 Symbol 作为方法名称并不像看起来那么奇怪。为了使对象可迭代（因此它可以与 for/of 循环一起使用），必须定义一个具有符号名称 Symbol.iterator 的方法，并且在第 12 章中有这样做的示例。\n\n### 6.10.6 Property Getters and Setters\nAll of the object properties we’ve discussed so far in this chapter have been data properties with a name and an ordinary value. JavaScript also supports accessor properties, which do not have a single value but instead have one or two accessor methods: a getter and/or a setter.\n\n> 到目前为止，本节我们所讨论的所有的对象都是具有名称和普通值的数据属性。JavaScript 还支持存取器属性，这些属性没有单个值，而是具有一个或两个存取器方法：getter 和或是或 setter。\n\nWhen a program queries the value of an accessor property, JavaScript invokes the getter method (passing no arguments). The return value of this method becomes the value of the property access expression. When a program sets the value of an accessor property, JavaScript invokes the setter method, passing the value of the righthand side of the assignment. This method is responsible for “setting,” in some sense, the property value. The return value of the setter method is ignored.\n\n> 当程序查询存取器属性的值时，JavaScript 调用 getter 方法（无实参）。这个方法的返回值就是属性存取表达式的值。当程序设置一个存取器属性的值时， JavaScript 调用 setter 方法，将赋值表达式右侧的值当做参数传入 setter。从某种意义上讲，这个方法负责“设置”属性值。可以忽略 setter 方法的返回值。\n\nIf a property has both a getter and a setter method, it is a read/write property. If it has only a getter method, it is a read-only property. And if it has only a setter method, it is a write-only property (something that is not possible with data properties), and attempts to read it always evaluate to undefined.\n\n> 如果属性同时有 getter 和 setter 方法，则它是一个可读写属性。如果它只含有 getter 方法，它是一个只读属性。如果它只有 setter 方法，它是一个只可写属性（这对一个数据属性来说是不可能的），如果尝试去读它，计算结果永远是 undefined。\n\nAccessor properties can be defined with an extension to the object literal syntax (unlike the other ES6 extensions we’ve seen here, getters and setters were introduced in ES5):\n\n> 存储器属性可以通过表达式在对象字面量语法中定义（不像我们在这里看到的其他 ES6 扩展，getter 和 setter 是 ES5 中的内容）：\n\n```js\nlet o = {\n    // An ordinary data property\n    dataProp: value,\n\n    // An accessor property defined as a pair of functions.\n    get accessorProp() { return this.dataProp; },\n    set accessorProp(value) { this.dataProp = value; }\n};\n```\nAccessor properties are defined as one or two methods whose name is the same as the property name. These look like ordinary methods defined using the ES6 shorthand except that getter and setter definitions are prefixed with get or set. (In ES6, you can also use computed property names when defining getters and setters. Simply replace the property name after get or set with an expression in square brackets.)\n\n> 存储器属性定义为名称与属性名称相同的一个或两个方法。这些方法看起来像使用 ES6 速记定义的普通方法，只不过 getter 和 setter 定义使用 get 或 set 前缀。（在 ES6 中，在定义 getter 和 setter 时还可以使用计算属性名称。只需将带方括号的表达式替换属性名称即可。\n\nThe accessor methods defined above simply get and set the value of a data property, and there is no reason to prefer the accessor property over the data property. But as a more interesting example, consider the following object that represents a 2D Cartesian point. It has ordinary data properties to represent the x and y coordinates of the point, and it has accessor properties that give the equivalent polar coordinates of the point:\n\n> 上面定义的存储器方法只需获取并设置数据属性的值，没有理由将存储器属性替换数据属性。但作为一个更有趣的示例，请考虑以下表示 2D 笛卡尔点的对象。它具有表示点的 x 和 y 坐标的普通数据属性，并且具有提供点等效极坐标的存储器属性：\n\n```js\nlet p = {\n    // x and y are regular read-write data properties.\n    x: 1.0,\n    y: 1.0,\n\n    // r is a read-write accessor property with getter and setter.\n    // Don't forget to put a comma after accessor methods.\n    get r() { return Math.hypot(this.x, this.y); },\n    set r(newvalue) {\n        let oldvalue = Math.hypot(this.x, this.y);\n        let ratio = newvalue/oldvalue;\n        this.x *= ratio;\n        this.y *= ratio;\n    },\n\n    // theta is a read-only accessor property with getter only.\n    get theta() { return Math.atan2(this.y, this.x); }\n};\np.r     // => Math.SQRT2\np.theta // => Math.PI / 4\n```\nNote the use of the keyword this in the getters and setter in this example. JavaScript invokes these functions as methods of the object on which they are defined, which means that within the body of the function, this refers to the point object p. So the getter method for the r property can refer to the x and y properties as this.x and this.y. Methods and the this keyword are covered in more detail in §8.2.2.\n\n> 请注意，在本示例中的 getter 和 setter 中使用 this 关键字。JavaScript 以对象的方法的方式调用这些函数，这意味着在函数体中，this 指的是点对象 p。因此，r 属性的 getter 方法通过 this.x 和 this.y 获取到 x 和 y 属性的引用。方法以及 this 关键字在 §8.2.2 中详细介绍。\n\nAccessor properties are inherited, just as data properties are, so you can use the object p defined above as a prototype for other points. You can give the new objects their own x and y properties, and they’ll inherit the r and theta properties:\n\n> 存储器属性是可继承的，就像数据属性一样，因此可以使用上面定义的对象 p 作为其他点的原型。可以为新对象提供自有的 x 和 y 属性，它们将继承 r 和 theta 属性：\n\n```js\nlet q = Object.create(p); // A new object that inherits getters and setters\nq.x = 3; q.y = 4;         // Create q's own data properties\nq.r                       // => 5: the inherited accessor properties work\nq.theta                   // => Math.atan2(4, 3)\n```\nThe code above uses accessor properties to define an API that provides two representations (Cartesian coordinates and polar coordinates) of a single set of data. Other reasons to use accessor properties include sanity checking of property writes and returning different values on each property read:\n\n> 上述代码使用存储器属性定义一个 API，该 API 提供一组数据的两种表示形式（笛卡尔坐标和极坐标）。使用存储器属性的其他场景包括属性写入的稳健性检测以及在每个属性上返回不同的值：\n\n```js\n// This object generates strictly increasing serial numbers\nconst serialnum = {\n    // This data property holds the next serial number.\n    // The _ in the property name hints that it is for internal use only.\n    _n: 0,\n\n    // Return the current value and increment it\n    get next() { return this._n++; },\n\n    // Set a new value of n, but only if it is larger than current\n    set next(n) {\n        if (n > this._n) this._n = n;\n        else throw new Error(\"serial number can only be set to a larger value\");\n    }\n};\nserialnum.next = 10;    // Set the starting serial number\nserialnum.next          // => 10\nserialnum.next          // => 11: different value each time we get next\n```\nFinally, here is one more example that uses a getter method to implement a property with “magical” behavior:\n\n> 最后，下面是使用 getter 方法实现具有\"魔幻\"行为的属性的示例：\n\n```js\n// This object has accessor properties that return random numbers.\n// The expression \"random.octet\", for example, yields a random number\n// between 0 and 255 each time it is evaluated.\nconst random = {\n    get octet() { return Math.floor(Math.random()*256); },\n    get uint16() { return Math.floor(Math.random()*65536); },\n    get int16() { return Math.floor(Math.random()*65536)-32768; }\n};\n```\n## 6.11 Summary\nThis chapter has documented JavaScript objects in great detail, covering topics that include:\n\nBasic object terminology, including the meaning of terms like enumerable and own property.\n\nObject literal syntax, including the many new features in ES6 and later.\n\nHow to read, write, delete, enumerate, and check for the presence of the properties of an object.\n\nHow prototype-based inheritance works in JavaScript and how to create an object that inherits from another object with Object.create().\n\nHow to copy properties from one object into another with Object.assign().\n\nAll JavaScript values that are not primitive values are objects. This includes both arrays and functions, which are the topics of the next two chapters.\n\n> 本章非常详细地记录了 JavaScript 对象，涵盖的主题包括：\n\n> 基本对象术语，包括可枚举和自有属性等术语的含义。\n\n> 对象字面量语法，包括 ES6 及以后的许多新特性。\n\n> 如何读取、写入、删除、枚举和检查对象属性是否存在。\n\n> 基于原型的继承是如何在 JavaScript 中工作，以及如何使用 Object.create() 创建一个从另一个对象继承的对象。\n\n> 如何使用 Object.assign() 将属性从一个对象复制到另一个对象。\n\n> 所有 JavaScript 的非原始值都是对象。这包括数组和函数，这是接下来两章的主题。\n\n---\n\n1. Remember; almost all objects have a prototype but most do not have a property named prototype. JavaScript inheritance works even if you can’t access the prototype object directly. But see §14.3 if you want to learn how to do that.\n\n> 1. 记住，几乎所有对象都有一个原型，但大多数对象没有名为 prototype 的属性。即使不能直接访问原型对象，JavaScript 继承依然工作。但是，如果你想学习如何做到这一点，请参阅 §14.3。"
  },
  {
    "path": "content/posts/ch7.md",
    "content": "---\ntitle: \"第 7 章 数组\"\ndate: 2020-11-02T22:18:36+08:00\n---\n\nThis chapter documents arrays, a fundamental datatype in JavaScript and in most other programming languages. An array is an ordered collection of values. Each value is called an element, and each element has a numeric position in the array, known as its index. JavaScript arrays are untyped: an array element may be of any type, and different elements of the same array may be of different types. Array elements may even be objects or other arrays, which allows you to create complex data structures, such as arrays of objects and arrays of arrays. JavaScript arrays are zero-based and use 32-bit indexes: the index of the first element is 0, and the highest possible index is 4294967294 (2<sup>32</sup>−2), for a maximum array size of 4,294,967,295 elements. JavaScript arrays are dynamic: they grow or shrink as needed, and there is no need to declare a fixed size for the array when you create it or to reallocate it when the size changes. JavaScript arrays may be sparse: the elements need not have contiguous indexes, and there may be gaps. Every JavaScript array has a length property. For nonsparse arrays, this property specifies the number of elements in the array. For sparse arrays, length is larger than the highest index of any element.\n \n> 本章记录了数组、一个在 JavaScript 和大多数其他编程语言中的基本数据类型。数组是值的有序集合。每个值叫做一个元素，而每个元素在数组中有一个位置，以数字表示，称为索引。JavaScript 数组是无类型的：数组元素可以是任意类型，并且同一个数组中的不同元素也可能有不同的类型。数组的元素甚至也可能是对象或其他数组，这允许创建复杂的数据结构，如对象的数组和数组的数组。JavaScript 数组的索引是基于零的 32 位数值：第一个元素的索引为 0，最大可能的索引为 4,294,967,294（2<sup>32</sup>-2），数组最大能容纳 4,294,967,295 个元素。JavaScript 数组是动态的：根据需要它们会增长或缩减，并且在创建数组时无须声明一个固定的大小或者在数组大小变化时无须重新分配空间。JavaScript 数组可能是稀疏的：数组元素的索引不一定要连续的，它们之间可以有空缺。每个 JavaScript 数组都有一个 length 属性。针对非稀疏数组，该属性就是数组元素的个数。针对稀疏数组，length 大于任何元素的最高索引。\n\nJavaScript arrays are a specialized form of JavaScript object, and array indexes are really little more than property names that happen to be integers. We’ll talk more about the specializations of arrays elsewhere in this chapter. Implementations typically optimize arrays so that access to numerically indexed array elements is generally significantly faster than access to regular object properties.\n\n> JavaScript 数组是 JavaScript 对象的特殊形式，数组索引实际上和碰巧是整数的属性名差不多。我们将在本章的其他地方更多地讨论特殊化的数组。通常，数组的实现是经过优化的，用数字索引来访问数组元素一般来说比访问常规的对象属性要快很多。\n\nArrays inherit properties from Array.prototype, which defines a rich set of array manipulation methods, covered in §7.8. Most of these methods are generic, which means that they work correctly not only for true arrays, but for any “array-like object.” We’ll discuss array-like objects in §7.9. Finally, JavaScript strings behave like arrays of characters, and we’ll discuss this in §7.10.\n\n> 数组继承自 Array.prototype 中的属性，它定义了一套丰富的数组操作方法，§7.8 涵盖这方面内容。大多数这些方法是通用的，这意味着它们不仅对真正的数组有效，而且对“类数组对象”同样有效。§7.9 讨论类数组对象。最后，JavaScript 字符串的行为与字符数组类似，我们将在 §7.10 讨论。\n\nES6 introduces a set of new array classes known collectively as “typed arrays.” Unlike regular JavaScript arrays, typed arrays have a fixed length and a fixed numeric element type. They offer high performance and byte-level access to binary data and are covered in §11.2.\n\n> ES6 引入了一组新的数组类，这些类统称为“类型化数组”。与常规的 JavaScript 数组不同，类型化数组有固定的长度和固定的数值元素类型。它们提供高性能和对二进制数据的字节级访问，在 §11.2 中有介绍。\n\n## 7.1 Creating Arrays\n\nThere are several ways to create arrays. The subsections that follow explain how to create arrays with:\n\n> 有很多种创建数组的方法。以下小节将说明如何使用以下方式创建数组：\n\nArray literals\n\n> 数组字面量\n\nThe ... spread operator on an iterable object\n\n> 可迭代数组 ... 展开运算符\n\nThe Array() constructor\n\n> Array() 构造函数\n\nThe Array.of() and Array.from() factory methods\n\n> Array.of() 和 Array.from() 工厂方法\n\n### 7.1.1 Array Literals\nBy far the simplest way to create an array is with an array literal, which is simply a comma-separated list of array elements within square brackets. For example:\n\n> 到目前为止使用数组字面量是创建数组最简单的方法，在方括号中将数组元素用逗号隔开即可。例如：\n\n```js\nlet empty = [];                 // An array with no elements\nlet primes = [2, 3, 5, 7, 11];  // An array with 5 numeric elements\nlet misc = [ 1.1, true, \"a\", ]; // 3 elements of various types + trailing comma\n```\nThe values in an array literal need not be constants; they may be arbitrary expressions:\n\n> 数组字面量中的值不一定要是常量；它们可以是任意的表达式：\n\n```js\nlet base = 1024;\nlet table = [base, base+1, base+2, base+3];\n```\nArray literals can contain object literals or other array literals:\n\n> 数组字面量可以包含对象字面量或其他数组字面量：\n\n```js\nlet b = [[1, {x: 1, y: 2}], [2, {x: 3, y: 4}]];\n```\nIf an array literal contains multiple commas in a row, with no value between, the array is sparse (see §7.3). Array elements for which values are omitted do not exist but appear to be undefined if you query them:\n\n> 如果数组字面量在一行中包含多个逗号，之间没有值，则数组是稀疏的（请参阅 §7.3）。省略值的数组元素不存在，但如果查询它们则返回 undefined：\n\n```js\nlet count = [1,,3]; // Elements at indexes 0 and 2. No element at index 1\nlet undefs = [,,];  // An array with no elements but a length of 2\n```\nArray literal syntax allows an optional trailing comma, so [,,] has a length of 2, not 3.\n\n> 数组字面量语法允许可选的尾部逗号，所以 [,,] 的长度是 2，不是3。\n\n### 7.1.2 The Spread Operator\nIn ES6 and later, you can use the “spread operator,” ..., to include the elements of one array within an array literal:\n\n> ES6 之后，可以使用展开操作符 ... 将一个数组中的元素展开在数组字面量中：\n\n```js\nlet a = [1, 2, 3];\nlet b = [0, ...a, 4];  // b == [0, 1, 2, 3, 4]\n```\nThe three dots “spread” the array a so that its elements become elements within the array literal that is being created. It is as if the ...a was replaced by the elements of the array a, listed literally as part of the enclosing array literal. (Note that, although we call these three dots a spread operator, this is not a true operator because it can only be used in array literals and, as we’ll see later in the book, function invocations.)\n\n> 三个点展开数组 a，所以它的元素变成了数组字面量，并被创建在数组中。就像 ...a 被数组 a 的元素所替换，被列出作为未闭合的数组字面量的一部分。（注意，尽管我们称三点是展开运算符，但这并不是一个操作，因为它只能用于数组字面量和本书后面提到的函数调用。）\n\nThe spread operator is a convenient way to create a (shallow) copy of an array:\n\n> 展开运算符可以方便的创建一个数组的拷贝（浅拷贝）：\n\n```js\nlet original = [1,2,3];\nlet copy = [...original];\ncopy[0] = 0;  // Modifying the copy does not change the original\noriginal[0]   // => 1\n```\nThe spread operator works on any iterable object. (Iterable objects are what the for/of loop iterates over; we first saw them in §5.4.4, and we’ll see much more about them in Chapter 12.) Strings are iterable, so you can use a spread operator to turn any string into an array of single-character strings:\n\n> 展开运算符可以作用于任何可迭代对象。（可迭代对象是可以用 for/of 进行循环的对象；第一次在 §5.4.4 中提到，在第 12 章会看到更多关于它们的描述。）字符串是可迭代对象，所以可以使用展开操作符将字符串转换成单个字符的数组。\n\n```js\nlet digits = [...\"0123456789ABCDEF\"];\ndigits // => [\"0\",\"1\",\"2\",\"3\",\"4\",\"5\",\"6\",\"7\",\"8\",\"9\",\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"]\n```\nSet objects (§11.1.1) are iterable, so an easy way to remove duplicate elements from an array is to convert the array to a set and then immediately convert the set back to an array using the spread operator:\n\n> Set 对象（§11.1.1）是可迭代对象，所以数组去重有一种简单的方法是用展开运算符将数组转换成 set 然后再转成数组：\n\n```js\nlet letters = [...\"hello world\"];\n[...new Set(letters)]  // => [\"h\",\"e\",\"l\",\"o\",\" \",\"w\",\"r\",\"d\"]\n```\n### 7.1.3 The Array() Constructor\nAnother way to create an array is with the Array() constructor. You can invoke this constructor in three distinct ways:\n\n> 另一种创建数组的方法是使用 Array() 构造函数。可以用三种不同的方式调用这个构造函数：\n\nCall it with no arguments:\n\n> 调用时没有实参：\n\n```js\nlet a = new Array();\n```\nThis method creates an empty array with no elements and is equivalent to the array literal [].\n\n> 这个方法创建了一个没有元素的空数组，它等价于 [] 数组字面量。\n\nCall it with a single numeric argument, which specifies a length:\n\n> 调用时有一个数值实参，它指定了数组的长度：\n\n```js\nlet a = new Array(10);\n```\nThis technique creates an array with the specified length. This form of the Array() constructor can be used to preallocate an array when you know in advance how many elements will be required. Note that no values are stored in the array, and the array index properties “0”, “1”, and so on are not even defined for the array.\n\n> 该技术创建指定长度的数组。当预先知道所需元素个数时，这种形式的 Array() 构造函数可以用来预分配一个数组空间。注意，数组中没有存储值，甚至数组的索引属性“0”、“1”等还未定义。\n\nExplicitly specify two or more array elements or a single non-numeric element for the array:\n\n> 为数组显式指定两个或多个数组元素或者非数值元素：\n\n```js\nlet a = new Array(5, 4, 3, 2, 1, \"testing, testing\");\n```\nIn this form, the constructor arguments become the elements of the new array. Using an array literal is almost always simpler than this usage of the Array() constructor.\n\n> 以这种形式，构造函数的实参将会成为新数组的元素。使用数组字面量比这样使用 Array() 构造函数要简单多了。\n\n### 7.1.4 Array.of()\nWhen the Array() constructor function is invoked with one numeric argument, it uses that argument as an array length. But when invoked with more than one numeric argument, it treats those arguments as elements for the array to be created. This means that the Array() constructor cannot be used to create an array with a single numeric element.\n\n> 当 Array() 构造函数调用时有一个数值型实参，它会将实参作为数组的长度。但当调用时不止一个数值型实参时，它会将那些实参作为数组的元素创建。这意味着 Array() 构造函数不能创建只有一个数值型元素的数组。\n\nIn ES6, the Array.of() function addresses this problem: it is a factory method that creates and returns a new array, using its argument values (regardless of how many of them there are) as the array elements:\n\n> 在 ES6 中，Array.of() 函数修复了这个问题：它是一个将其实参值（无论有多少个实参）作为数组元素创建并返回一个新数组的工厂方法：\n\n```js\nArray.of()        // => []; returns empty array with no arguments\nArray.of(10)      // => [10]; can create arrays with a single numeric argument\nArray.of(1,2,3)   // => [1, 2, 3]\n```\n### 7.1.5 Array.from()\nArray.from is another array factory method introduced in ES6. It expects an iterable or array-like object as its first argument and returns a new array that contains the elements of that object. With an iterable argument, Array.from(iterable) works like the spread operator [...iterable] does. It is also a simple way to make a copy of an array:\n\n> Array.from 是 ES6 中另外一个数组工厂方法。它期望一个可迭代或类数组对象作为它的第一个实参，并返回一个包含对象中元素的新数组。使用一个可迭代实参，Array.from(iterable) 工作方式类似于展开运算符 [...iterable]。它也可以简单的拷贝一个数组：\n\n```js\nlet copy = Array.from(original);\n```\nArray.from() is also important because it defines a way to make a true-array copy of an array-like object. Array-like objects are non-array objects that have a numeric length property and have values stored with properties whose names happen to be integers. When working with client-side JavaScript, the return values of some web browser methods are array-like, and it can be easier to work with them if you first convert them to true arrays:\n\n> Array.from() 也很重要，因为它定义了一个将类数组对象拷贝成数组的方法。类数组对象是一个不是数组的对象，它有一个数值型的 length 属性，并且它的值碰巧保存在属性名为整数的属性中。当使用客户端 JavaScript 时，一些浏览器方法的返回值是类数组的，并且当将其转化成真正的数组后会更容易操作它们：\n\n```js\nlet truearray = Array.from(arraylike);\n```\nArray.from() also accepts an optional second argument. If you pass a function as the second argument, then as the new array is being built, each element from the source object will be passed to the function you specify, and the return value of the function will be stored in the array instead of the original value. (This is very much like the array map() method that will be introduced later in the chapter, but it is more efficient to perform the mapping while the array is being built than it is to build the array and then map it to another new array.)\n\n> Array.from() 第二个实参为可选实参。如果传递一个函数作为第二个实参，那么当新数组被创建，每一个元素都会被作为实参传入这个指定函数中，并且这个函数的每个返回值保存在数组中代替原来的值。（这很像后面会介绍的数组 map() 方法，但是，它会更加高效的执行映射，因其为没有创建数组，而是直接进行映射到另外一个数组。）\n\n## 7.2 Reading and Writing Array Elements\nYou access an element of an array using the [] operator. A reference to the array should appear to the left of the brackets. An arbitrary expression that has a non-negative integer value should be inside the brackets. You can use this syntax to both read and write the value of an element of an array. Thus, the following are all legal JavaScript statements:\n\n> 使用 [] 运算符来访问数组中的一个元素。数组的引用位于方括号的左边。方括号中是一个返回非负整数值的任意表达式。使用该语法既可以读又可以写数组的一个元素。因此，如下代码都是合法的 JavaScript 语句：\n\n```js\nlet a = [\"world\"];     // Start with a one-element array\nlet value = a[0];      // Read element 0\na[1] = 3.14;           // Write element 1\nlet i = 2;\na[i] = 3;              // Write element 2\na[i + 1] = \"hello\";    // Write element 3\na[a[i]] = a[0];        // Read elements 0 and 2, write element 3\n```\nWhat is special about arrays is that when you use property names that are non-negative integers less than 2<sup>32</sup>–1, the array automatically maintains the value of the length property for you. In the preceding, for example, we created an array a with a single element. We then assigned values at indexes 1, 2, and 3. The length property of the array changed as we did, so:\n\n> 数组特殊的是，当使用小于 2<sup>32</sup>–1 的非负整数属性名时，数组会自动维护 length 属性。例如，上文中我们创建了只有一个元素的数组 a。然后我们为其序列为 1、2 和 3 的元素进行赋值。数组 length 属性会自动改变：\n\n```js\na.length       // => 4\n```\n\nRemember that arrays are a specialized kind of object. The square brackets used to access array elements work just like the square brackets used to access object properties. JavaScript converts the numeric array index you specify to a string—the index 1 becomes the string \"1\"—then uses that string as a property name. There is nothing special about the conversion of the index from a number to a string: you can do that with regular objects, too:\n\n> 请记住，数组是对象的特殊形式。使用方括号访问数组元素就像用方括号访问对象的属性一样。JavaScript 将指定的数字索引值转换成字符串（索引值 1 变成“1”）然后将其作为属性名来使用。关于索引值从数字转换为字符串没什么特别之处：对常规对象也可以这么做：\n\n```js\nlet o = {};    // Create a plain object\no[1] = \"one\";  // Index it with an integer\no[\"1\"]         // => \"one\"; numeric and string property names are the same\n```\nIt is helpful to clearly distinguish an array index from an object property name. All indexes are property names, but only property names that are integers between 0 and 2<sup>32</sup>–2 are indexes. All arrays are objects, and you can create properties of any name on them. If you use properties that are array indexes, however, arrays have the special behavior of updating their length property as needed.\n\n> 清晰地区分数组的索引和对象的属性名是非常有用的。所有的索引都是属性名，但只有在 0～2<sup>32</sup>-2 之间的整数属性名才是索引。所有的数组都是对象，可以为其创建任意名字的属性。但如果使用的属性是数组的索引，数组的特殊行为就是将根据需要更新它们的 length 属性值。\n\nNote that you can index an array using numbers that are negative or that are not integers. When you do this, the number is converted to a string, and that string is used as the property name. Since the name is not a non-negative integer, it is treated as a regular object property, not an array index. Also, if you index an array with a string that happens to be a non-negative integer, it behaves as an array index, not an object property. The same is true if you use a floating-point number that is the same as an integer:\n\n> 注意，可以使用负数或非整数来索引数组。这种情况下，数值转换为字符串，字符串作为属性名来用。既然名字不是非负整数，它就只能当做常规的对象属性，而非数组的索引。同样，如果凑巧使用了是非负整数的字符串，它就当做数组索引，而非对象属性。当使用的一个浮点数和一个整数相等时情况也是一样的：\n\n```js\na[-1.23] = true;  // This creates a property named \"-1.23\"\na[\"1000\"] = 0;    // This the 1001st element of the array\na[1.000] = 1;     // Array index 1. Same as a[1] = 1;\n```\nThe fact that array indexes are simply a special type of object property name means that JavaScript arrays have no notion of an “out of bounds” error. When you try to query a nonexistent property of any object, you don’t get an error; you simply get undefined. This is just as true for arrays as it is for objects:\n\n> 事实上数组索引仅仅是对象属性名的一种特殊类型，这意味着 JavaScript 数组没有“越界”错误的概念。当试图查询任何对象中不存在的属性时，都不会报错，只会得到 undefined 值。类似于对象，对于对象同样存在这种情况。\n\n```js\nlet a = [true, false]; // This array has elements at indexes 0 and 1\na[2]                   // => undefined; no element at this index.\na[-1]                  // => undefined; no property with this name.\n```\n## 7.3 Sparse Arrays\nA sparse array is one in which the elements do not have contiguous indexes starting at 0. Normally, the length property of an array specifies the number of elements in the array. If the array is sparse, the value of the length property is greater than the number of elements. Sparse arrays can be created with the Array() constructor or simply by assigning to an array index larger than the current array length.\n\n> 稀疏数组就是包含从 0 开始的不连续索引的数组。通常，数组的 length 属性值代表数组中元素的个数。如果数组是稀疏的，length 属性值大于元素的个数。可以用 Array() 构造函数或简单地指定数组的索引值大于当前的数组长度来创建稀疏数组。\n\n```js\nlet a = new Array(5); // No elements, but a.length is 5.\na = [];               // Create an array with no elements and length = 0.\na[1000] = 0;          // Assignment adds one element but sets length to 1001.\n```\nWe’ll see later that you can also make an array sparse with the delete operator.\n\n> 后面会看到你也可以用 delete 运算符来生产稀疏数组。\n\nArrays that are sufficiently sparse are typically implemented in a slower, more memory-efficient way than dense arrays are, and looking up elements in such an array will take about as much time as regular object property lookup.\n\n> 足够稀疏的数组通常在实现上比稠密的数组更慢、内存利用率更高，在这样的数组中查找元素的时间与常规对象属性的查找时间一样长。\n\nNote that when you omit a value in an array literal (using repeated commas as in [1,,3]), the resulting array is sparse, and the omitted elements simply do not exist:\n\n> 注意，当在数组字面量中省略值时（像 [1,,3] 中使用重复的逗号）返回的是稀疏数组，省略掉的值是不存在的：\n\n```js\nlet a1 = [,];           // This array has no elements and length 1\nlet a2 = [undefined];   // This array has one undefined element\n0 in a1                 // => false: a1 has no element with index 0\n0 in a2                 // => true: a2 has the undefined value at index 0\n```\nUnderstanding sparse arrays is an important part of understanding the true nature of JavaScript arrays. In practice, however, most JavaScript arrays you will work with will not be sparse. And, if you do have to work with a sparse array, your code will probably treat it just as it would treat a nonsparse array with undefined elements.\n\n> 了解稀疏数组是了解 JavaScript 数组的真实本质的一部分。尽管如此，实际上你所碰到的绝大多数 JavaScript 数组不是稀疏数组。并且，如果你确实碰到了稀疏数组，你的代码很可能像对待非稀疏数组一样来对待它们，只不过它们包含一些 undefined 元素。\n\n## 7.4 Array Length\nEvery array has a length property, and it is this property that makes arrays different from regular JavaScript objects. For arrays that are dense (i.e., not sparse), the length property specifies the number of elements in the array. Its value is one more than the highest index in the array:\n\n> 每个数组有一个 length 属性，就是这个属性使其区别于常规的 JavaScript 对象。针对稠密（也就是非稀疏）数组，length 属性值代表数组中元素的个数。其值比数组中最大的索引大 1：\n\n```js\n[].length             // => 0: the array has no elements\n[\"a\",\"b\",\"c\"].length  // => 3: highest index is 2, length is 3\n```\nWhen an array is sparse, the length property is greater than the number of elements, and all we can say about it is that length is guaranteed to be larger than the index of every element in the array. Or, put another way, an array (sparse or not) will never have an element whose index is greater than or equal to its length. In order to maintain this invariant, arrays have two special behaviors. The first we described above: if you assign a value to an array element whose index i is greater than or equal to the array’s current length, the value of the length property is set to i+1.\n\n> 当数组是稀疏的时，length 属性值大于元素的个数。而且关于此我们可以说数组长度保证大于它每个元素的索引值。或者，换一种说法，在数组中（无论稀疏与否）肯定找不到一个元素的索引值大于或等于它的长度。为了维持此规则不变化，数组有两个特殊的行为。第一个如同上面的描述：如果为一个数组元素赋值，它的索引 i 大于或等于现有数组的长度时，length 属性的值将设置为 i+1。\n\nThe second special behavior that arrays implement in order to maintain the length invariant is that, if you set the length property to a non-negative integer n smaller than its current value, any array elements whose index is greater than or equal to n are deleted from the array:\n\n> 第二个特殊的行为就是设置 length 属性为一个小于当前长度的非负整数n时，当前数组中那些索引值大于或等于 n 的元素将从中删除：\n\n```js\na = [1,2,3,4,5];     // Start with a 5-element array.\na.length = 3;        // a is now [1,2,3].\na.length = 0;        // Delete all elements.  a is [].\na.length = 5;        // Length is 5, but no elements, like new Array(5)\n```\nYou can also set the length property of an array to a value larger than its current value. Doing this does not actually add any new elements to the array; it simply creates a sparse area at the end of the array.\n\n> 还可以将数组的 length 属性值设置为大于其当前的长度。实际上这不会向数组中添加新的元素，它只是在数组尾部创建一个稀疏区域。\n\n## 7.5 Adding and Deleting Array Elements\nWe’ve already seen the simplest way to add elements to an array: just assign values to new indexes:\n\n> 我们已经见过添加数组元素最简单的方法：为新索引赋值：\n\n```js\nlet a = [];      // Start with an empty array.\na[0] = \"zero\";   // And add elements to it.\na[1] = \"one\";\n```\nYou can also use the push() method to add one or more values to the end of an array:\n\n> 也可以使用push()方法在数组末尾增加一个或多个元素：\n\n```js\nlet a = [];           // Start with an empty array\na.push(\"zero\");       // Add a value at the end.  a = [\"zero\"]\na.push(\"one\", \"two\"); // Add two more values.  a = [\"zero\", \"one\", \"two\"]\n```\nPushing a value onto an array a is the same as assigning the value to a[a.length]. You can use the unshift() method (described in §7.8) to insert a value at the beginning of an array, shifting the existing array elements to higher indexes. The pop() method is the opposite of push(): it removes the last element of the array and returns it, reducing the length of an array by 1. Similarly, the shift() method removes and returns the first element of the array, reducing the length by 1 and shifting all elements down to an index one lower than their current index. See §7.8 for more on these methods.\n\n> 在数组尾部压入一个元素与给 a[a.length] 赋值是一样的。可以使用 unshift() 方法（§7.8 有描述）在数组的首部插入一个元素，并且将其他元素依次移到更高的索引处。pop() 方法与 push() 相反：它移除数组最后一个元素并返回这个元素，使数组 length 减 1。同样，shift() 方法移除并返回数组的第一个元素，使数组 length 减 1，并将其他元素依次移到低 1 的索引处。§7.8 有更多关于这些方法的描述。\n\nYou can delete array elements with the delete operator, just as you can delete object properties:\n\n> 可以像删除对象属性一样使用 delete 运算符来删除数组元素：\n\n```js\nlet a = [1,2,3];\ndelete a[2];   // a now has no element at index 2\n2 in a         // => false: no array index 2 is defined\na.length       // => 3: delete does not affect array length\n```\nDeleting an array element is similar to (but subtly different than) assigning undefined to that element. Note that using delete on an array element does not alter the length property and does not shift elements with higher indexes down to fill in the gap that is left by the deleted property. If you delete an element from an array, the array becomes sparse.\n\n> 删除数组元素与为其赋 undefined 值是类似的（但有一些微妙的区别）。注意，对一个数组元素使用 delete 不会修改数组的 length 属性，也不会将元素从高索引处移下来填充已删除属性留下的空白。如果从数组中删除一个元素，它就变成稀疏数组。\n\nAs we saw above, you can also remove elements from the end of an array simply by setting the length property to the new desired length.\n\n> 正如上面所看到的，也可以通过设置新的所需长度，即可从数组尾部删除元素。\n\nFinally, splice() is the general-purpose method for inserting, deleting, or replaci ng array elements. It alters the length property and shifts array elements to higher or lower indexes as needed. See §7.8 for details.\n\n> 最后，splice() 是一个通用的方法来插入、删除或替换数组元素。它会根据需要修改 length 属性并移动元素到更高或较低的索引处。详细内容见 §7.8。\n\n## 7.6 Iterating Arrays\nAs of ES6, the easiest way to loop through each of the elements of an array (or any iterable object) is with the for/of loop, which was covered in detail in §5.4.4:\n\n> 在 ES6 中，最容易遍历数组元素（或可迭代对象）的方法是 for/of 循环，在 §5.4.4 中详细介绍：\n\n```js\nlet letters = [...\"Hello world\"];  // An array of letters\nlet string = \"\";\nfor(let letter of letters) {\n    string += letter;\n}\nstring  // => \"Hello world\"; we reassembled the original text\n```\nThe built-in array iterator that the for/of loop uses returns the elements of an array in ascending order. It has no special behavior for sparse arrays and simply returns undefined for any array elements that do not exist.\n\n> 内置数组迭代器 for/of 循环按照升序返回数组元素。对于稀疏数组它没有特殊的行为，数组中不存在的元素只是单纯的返回 undefined。\n\nIf you want to use a for/of loop for an array and need to know the index of each array element, use the entries() method of the array, along with destructuring assignment, like this:\n\n> 如果使用 for/of 循环一个数组时还需要知道每个元素的索引，可以像这样将数组的 entries() 方法和解构语句一同使用：\n\n```js\nlet everyother = \"\";\nfor(let [index, letter] of letters.entries()) {\n    if (index % 2 === 0) everyother += letter;  // letters at even indexes\n}\neveryother  // => \"Hlowrd\"\n```\nAnother good way to iterate arrays is with forEach(). This is not a new form of the for loop, but an array method that offers a functional approach to array iteration. You pass a function to the forEach() method of an array, and forEach() invokes your function once on each element of the array:\n\n> 另一种不错的遍历数组方法是用 forEach()。这不是 for 循环的新形式，而是提供数组遍历功能方法的数组方法。可以给数组的 forEach() 方法传递一个函数，forEach() 会对数组中每一个元素调用这个方法：\n\n```js\nlet uppercase = \"\";\nletters.forEach(letter => {  // Note arrow function syntax here\n    uppercase += letter.toUpperCase();\n});\nuppercase  // => \"HELLO WORLD\"\n```\nAs you would expect, forEach() iterates the array in order, and it actually passes the array index to your function as a second argument, which is occasionally useful. Unlike the for/of loop, the forEach() is aware of sparse arrays and does not invoke your function for elements that are not there.\n\n> 正如期望的，forEach() 按顺序对数组进行计算，实际上它将数组索引作为第二个实参传递到函数，这有时很有用。与 for/of 循环不同，forEach() 能意识到稀疏数组，并且不会为不存在的元素调用函数。\n\n§7.8.1 documents the forEach() method in more detail. That section also covers related methods such as map() and filter() that perform specialized kinds of array iteration.\n\n> §7.8.1 更详细地记录了 forEach() 方法。该部分还介绍演示了特定类型的数组遍历方法，如 map() 和 filter()。\n\nYou can also loop through the elements of an array with a good old-fashioned for loop (§5.4.3):\n\n> 也可以用一种非常老旧方式遍历数组的元素（§5.4.3）：\n\n```js\nlet vowels = \"\";\nfor(let i = 0; i < letters.length; i++) { // For each index in the array\n    let letter = letters[i];              // Get the element at that index\n    if (/[aeiou]/.test(letter)) {         // Use a regular expression test\n        vowels += letter;                 // If it is a vowel, remember it\n    }\n}\nvowels  // => \"eoo\"\n```\nIn nested loops, or other contexts where performance is critical, you may sometimes see this basic array iteration loop written so that the array length is only looked up once rather than on each iteration. Both of the following for loop forms are idiomatic, though not particularly common, and with modern JavaScript interpreters, it is not at all clear that they have any performance impact:\n\n> 在嵌套循环或其他性能至关重要的上下文中，有时可能会看到这样的数组遍历，以便数组长度仅被查一次，而不是在每次循环都去查询。以下两种形式都是符合习惯的 for 循环，虽然不是特别常用，而且对于现代 JavaScript 解释器，它们是否对性能有任何影响尚不清楚：\n\n```js\n// Save the array length into a local variable\nfor(let i = 0, len = letters.length; i < len; i++) {\n    // loop body remains the same\n}\n\n// Iterate backwards from the end of the array to the start\nfor(let i = letters.length-1; i >= 0; i--) {\n    // loop body remains the same\n}\n```\nThese examples assume that the array is dense and that all elements contain valid data. If this is not the case, you should test the array elements before using them. If you want to skip undefined and nonexistent elements, you might write:\n\n> 这些示例假定数组是稠密的，并且所有元素都包含有效的数据。如果不是这样，应该在使用数组元素之前测试它们。如果要跳过 undefined 和不存在的元素，可以编写：\n\n```js\nfor(let i = 0; i < a.length; i++) {\n    if (a[i] === undefined) continue; // Skip undefined + nonexistent elements\n    // loop body here\n}\n```\n## 7.7 Multidimensional Arrays\nJavaScript does not support true multidimensional arrays, but you can approximate them with arrays of arrays. To access a value in an array of arrays, simply use the [] operator twice. For example, suppose the variable matrix is an array of arrays of numbers. Every element in matrix[x] is an array of numbers. To access a particular number within this array, you would write matrix[x][y]. Here is a concrete example that uses a two-dimensional array as a multiplication table:\n\n> JavaScript 不支持真正的多维数组，但可以用数组的数组来近似。访问数组的数组中的元素，只要简单地使用两次 [] 操作符即可。例如，假设变量 matrix 是一个数组的数组，它的基本元素是数值，那么 matrix[x] 的每个元素是包含一个数值数组，访问数组中特定数值的代码为 matrix[x][y]。这里有一个具体的例子，它使用二维数组作为一个九九乘法表：\n\n```js\n// Create a multidimensional array\nlet table = new Array(10);               // 10 rows of the table\nfor(let i = 0; i < table.length; i++) {\n    table[i] = new Array(10);            // Each row has 10 columns\n}\n\n// Initialize the array\nfor(let row = 0; row < table.length; row++) {\n    for(let col = 0; col < table[row].length; col++) {\n        table[row][col] = row*col;\n    }\n}\n\n// Use the multidimensional array to compute 5*7\ntable[5][7]  // => 35\n```\n## 7.8 Array Methods\nThe preceding sections have focused on basic JavaScript syntax for working with arrays. In general, though, it is the methods defined by the Array class that are the most powerful. The next sections document these methods. While reading about these methods, keep in mind that some of them modify the array they are called on and some of them leave the array unchanged. A number of the methods return an array: sometimes, this is a new array, and the original is unchanged. Other times, a method will modify the array in place and will also return a reference to the modified array.\n\n> 前面几节重点介绍了用于处理数组的基本 JavaScript 语法。但通常，由 Array 类定义的方法是最强大的。下一节将记录这些方法。在阅读有关这些方法时，请记住，其中一些方法修改了调用的数组，而其中一些方法使数组保持不变。许多方法返回数组：有时，这是一个新数组，原始数组保持不变。其他时候，方法将修改数组，并且返回对修改后数组的引用。\n\nEach of the subsections that follows covers a group of related array methods:\n\n> 以下每个小节都涵盖一组相关的数组方法：\n\nIterator methods loop over the elements of an array, typically invoking a function that you specify on each of those elements.\n\n> 迭代器方法循环遍历数组的元素，通常调用在每个元素上指定的函数。\n\nStack and queue methods add and remove array elements to and from the beginning and the end of an array.\n\n> 堆栈和队列方法在数组的开头和结尾添加和删除数组元素。\n\nSubarray methods are for extracting, deleting, inserting, filling, and copying contiguous regions of a larger array.\n\n> 子数组方法用于提取、删除、插入、填充和复制一个更大数组中相邻的区域。\n\nSearching and sorting methods are for locating elements within an array and for sorting the elements of an array.\n\n> 搜索和排序方法用于查找数组中的元素和排序数组的元素。\n\nThe following subsections also cover the static methods of the Array class and a few miscellaneous methods for concatenating arrays and converting arrays to strings.\n\n> 以下小节还介绍 Array 类的静态方法和一些用于连接数组和将数组转换为字符串的各种方法。\n\n### 7.8.1 Array Iterator Methods\nThe methods described in this section iterate over arrays by passing array elements, in order, to a function you supply, and they provide convenient ways to iterate, map, filter, test, and reduce arrays.\n\n> 本节中介绍的方法通过将数组元素按顺序传递到所指定的函数来遍历数组，它们提供了迭代、映射、筛选、测试和减少数组的便捷方法。\n\nBefore we explain the methods in detail, however, it is worth making some generalizations about them. First, all of these methods accept a function as their first argument and invoke that function once for each element (or some elements) of the array. If the array is sparse, the function you pass is not invoked for nonexistent elements. In most cases, the function you supply is invoked with three arguments: the value of the array element, the index of the array element, and the array itself. Often, you only need the first of these argument values and can ignore the second and third values.\n\n> 然而，在详细解释这些方法之前，值得对它们进行一些概括。首先，所有这些方法都接受函数作为其第一个实参，并使用调用数组的每个元素（或某些元素）作为实参调用该函数。如果数组是稀疏的，则不会为不存在的元素调用传递的函数。在大多数情况下，提供的函数被调用时有三个实参：数组元素的值、数组元素的索引和数组本身。通常，只需要这些实参值中的第一个，并且可以忽略第二个和第三个值。\n\nMost of the iterator methods described in the following subsections accept an optional second argument. If specified, the function is invoked as if it is a method of this second argument. That is, the second argument you pass becomes the value of the this keyword inside of the function you pass as the first argument. The return value of the function you pass is usually important, but different methods handle the return value in different ways. None of the methods described here modify the array on which they are invoked (though the function you pass can modify the array, of course).\n\n> 以下小节中描述的大多数迭代器方法都接受可选的第二个实参。如果指定，则调用函数就像它是第二个实参的方法一样。也就是说，传递的第二个实参将成为第一个函数实参内部的 this 值。传递的函数的返回值通常很重要，但不同的方法以不同的方式处理返回值。此处描述的方法都没有修改调用它们的数组（当然，传递的函数可以修改这个数组）。\n\nEach of these functions is invoked with a function as its first argument, and it is very common to define that function inline as part of the method invocation expression instead of using an existing function that is defined elsewhere. Arrow function syntax (see §8.1.3) works particularly well with these methods, and we will use it in the examples that follow.\n\n> 这节的每个函数都调用它的第一个函数实参，并且通常将该函数内联定义为方法调用表达式的一部分，而不是使用在其他地方显示定义的函数。箭头函数语法（参见 §8.1.3）在这些方法中特别有效，我们将在下面的示例中使用它。\n\n#### FOREACH()\nThe forEach() method iterates through an array, invoking a function you specify for each element. As we’ve described, you pass the function as the first argument to forEach(). forEach() then invokes your function with three arguments: the value of the array element, the index of the array element, and the array itself. If you only care about the value of the array element, you can write a function with only one parameter—the additional arguments will be ignored:\n\n> forEach() 方法遍历数组，调用为每个元素指定的函数。正如我们已经描述的那样，将函数作为第一个实参传递给 forEach()。forEach() 然后使用三个实参调用函数：数组元素的值、数组元素的索引和数组本身。如果只关心数组元素的值，则编写一个只有一个实参的函数（将忽略其他实参）：\n\n```js\nlet data = [1,2,3,4,5], sum = 0;\n// Compute the sum of the elements of the array\ndata.forEach(value => { sum += value; });          // sum == 15\n\n// Now increment each array element\ndata.forEach(function(v, i, a) { a[i] = v + 1; }); // data == [2,3,4,5,6]\n```\nNote that forEach() does not provide a way to terminate iteration before all elements have been passed to the function. That is, there is no equivalent of the break statement you can use with a regular for loop.\n\n> 请注意，forEach() 不提供在所有元素传递给函数之前终止迭代的方法。也就是说，没有等效于常规 for 循环的 break 语句可以使用。\n\n#### MAP()\nThe map() method passes each element of the array on which it is invoked to the function you specify and returns an array containing the values returned by your function. For example:\n\n> map() 方法将调用数组的每个元素传递到指定的函数，并返回一个包含函数返回的值的数组。例如：\n\n```js\nlet a = [1, 2, 3];\na.map(x => x*x)   // => [1, 4, 9]: the function takes input x and returns x*x\n```\nThe function you pass to map() is invoked in the same way as a function passed to forEach(). For the map() method, however, the function you pass should return a value. Note that map() returns a new array: it does not modify the array it is invoked on. If that array is sparse, your function will not be called for the missing elements, but the returned array will be sparse in the same way as the original array: it will have the same length and the same missing elements.\n\n> 传递到 map() 的函数的调用方式与传递给 forEach() 的函数相同。但是，对于 map() 方法，传递的函数应返回一个值。请注意，map() 返回一个新数组：它不会修改调用它的数组。如果该数组是稀疏的，则不会为缺失的元素调用函数，但返回的数组将稀疏，其确实元素与原始数组的位置相同：它将具有相同的长度和相同的缺失元素。\n\n#### FILTER()\nThe filter() method returns an array containing a subset of the elements of the array on which it is invoked. The function you pass to it should be predicate: a function that returns true or false. The predicate is invoked just as for forEach() and map(). If the return value is true, or a value that converts to true, then the element passed to the predicate is a member of the subset and is added to the array that will become the return value. Examples:\n\n> filter() 方法返回一个数组，其中包含调用该数组的数组元素的子集。传递给它的函数应该是断言：返回真或假的函数。断言函数的调用就像 forEach() 和 map() 调用一样。如果返回值为 true，或者能转换为 true 的值，则传递给断言的元素是子集的成员，并将添加到将成为返回值的数组中。例子：\n\n```js\nlet a = [5, 4, 3, 2, 1];\na.filter(x => x < 3)         // => [2, 1]; values less than 3\na.filter((x,i) => i%2 === 0) // => [5, 3, 1]; every other value\n```\nNote that filter() skips missing elements in sparse arrays and that its return value is always dense. To close the gaps in a sparse array, you can do this:\n\n> 注意 filter() 跳过稀疏数组中的丢失元素并且返回值也总是稠密的。要缩小稀疏数组的间距，可以这样做：\n\n```js\nlet dense = sparse.filter(() => true);\n```\nAnd to close gaps and remove undefined and null elements, you can use filter, like this:\n\n> 要缩小间隙并移除 undefined 和 null 元素，可以用 filter 这样做：\n\n```js\na = a.filter(x => x !== undefined && x !== null);\n```\n#### FIND() AND FINDINDEX()\nThe find() and findIndex() methods are like filter() in that they iterate through your array looking for elements for which your predicate function returns a truthy value. Unlike filter(), however, these two methods stop iterating the first time the predicate finds an element. When that happens, find() returns the matching element, and findIndex() returns the index of the matching element. If no matching element is found, find() returns undefined and findIndex() returns -1:\n\n> find() 和 findIndex() 方法就像 filter()，因为它们在数组中迭代，查找断言函数返回真实值的元素。但是，与 filter()不同，这两种方法在断言首次查找元素到时停止遍历。发生这种情况时，find() 返回匹配元素，而 findIndex() 返回匹配元素的索引。如果未找到匹配元素，find() 返回 undefined，findIndex() 返回 -1：\n\n```js\nlet a = [1,2,3,4,5];\na.findIndex(x => x === 3)  // => 2; the value 3 appears at index 2\na.findIndex(x => x < 0)    // => -1; no negative numbers in the array\na.find(x => x % 5 === 0)   // => 5: this is a multiple of 5\na.find(x => x % 7 === 0)   // => undefined: no multiples of 7 in the array\n```\n#### EVERY() AND SOME()\nThe every() and some() methods are array predicates: they apply a predicate function you specify to the elements of the array, then return true or false.\n\n> every() 和 some() 方法是数组断言：它们将指定的断言函数应用于数组的元素，然后返回 true 或 false。\n\nThe every() method is like the mathematical “for all” quantifier ∀: it returns true if and only if your predicate function returns true for all elements in the array:\n\n> every() 方法与数学全称量化符号 ∀ 相似：如果数组中所有元素执行断言函数返回值都为 true，则返回 true：\n\n```js\nlet a = [1,2,3,4,5];\na.every(x => x < 10)      // => true: all values are < 10.\na.every(x => x % 2 === 0) // => false: not all values are even.\n```\nThe some() method is like the mathematical “there exists” quantifier ∃: it returns true if there exists at least one element in the array for which the predicate returns true and returns false if and only if the predicate returns false for all elements of the array:\n\n> some() 方法与数学存在限定符 ∃ 相同：如果数组中存在至少有一个元素调用断言函数返回 true 的返回 true，仅在断言全部返回 false 时返回 false：\n\n```js\nlet a = [1,2,3,4,5];\na.some(x => x%2===0)  // => true; a has some even numbers.\na.some(isNaN)         // => false; a has no non-numbers.\n```\nNote that both every() and some() stop iterating array elements as soon as they know what value to return. some() returns true the first time your predicate returns true and only iterates through the entire array if your predicate always returns false. every() is the opposite: it returns false the first time your predicate returns false and only iterates all elements if your predicate always returns true. Note also that, by mathematical convention, every() returns true and some returns false when invoked on an empty array.\n\n> 请注意，every() 和 some() 只要它们知道要返回的值，都停止对数组元素的遍历。some() 在断言函数第一次返回 true 时返回 true，并且只有在每个元素调用断言函数都返回 false 时，才会遍历整个数组 。every() 正好相反：它返回 false 时，您的谓词返回 false，并且仅在谓词始终返回 true 时，才会回注所有元素。另请注意，根据数学约定，every() 返回 true，有些返回 false，当在空数组上调用时，某些返回 false。\n\n#### REDUCE() AND REDUCERIGHT()\nThe reduce() and reduceRight() methods combine the elements of an array, using the function you specify, to produce a single value. This is a common operation in functional programming and also goes by the names “inject” and “fold.” Examples help illustrate how it works:\n\n> reduce() 和 reduceRight() 方法使用指定的函数将数组元素进行组合，生成单个值。这在函数式编程中是常见的操作，也可以称为“注入”和“折叠”。举例说明它是如何工作的：\n\n```js\nlet a = [1,2,3,4,5];\na.reduce((x,y) => x+y, 0)          // => 15; the sum of the values\na.reduce((x,y) => x*y, 1)          // => 120; the product of the values\na.reduce((x,y) => (x > y) ? x : y) // => 5; the largest of the values\n```\nreduce() takes two arguments. The first is the function that performs the reduction operation. The task of this reduction function is to somehow combine or reduce two values into a single value and to return that reduced value. In the examples we’ve shown here, the functions combine two values by adding them, multiplying them, and choosing the largest. The second (optional) argument is an initial value to pass to the function.\n\n> reduce() 需要两个实参。第一个是执行化简操作的函数。化简函数的任务就是用某种方法把两个值组合或化简为一个值，并返回化简后的值。在上述例子中，函数通过加法、乘法或取最大值的方法组合两个值。第二个（可选）的实参是一个传递给函数的初始值。\n\nFunctions used with reduce() are different than the functions used with forEach() and map(). The familiar value, index, and array values are passed as the second, third, and fourth arguments. The first argument is the accumulated result of the reduction so far. On the first call to the function, this first argument is the initial value you passed as the second argument to reduce(). On subsequent calls, it is the value returned by the previous invocation of the function. In the first example, the reduction function is first called with arguments 0 and 1. It adds these and returns 1. It is then called again with arguments 1 and 2 and returns 3. Next, it computes 3+3=6, then 6+4=10, and finally 10+5=15. This final value, 15, becomes the return value of reduce().\n\n> reduce() 使用的函数与 forEach() 和 map() 使用的函数不同。比较熟悉的是，数组元素、元素的索引和数组本身将作为第 2～4 个实参传递给函数。第一个实参是到目前为止的化简操作累积的结果。第一次调用函数时，第一个实参是一个初始值，它就是传递给 reduce() 的第二个实参。在接下来的调用中，这个值就是上一次化简函数的返回值。在上面的第一个例子中，第一次调用化简函数时的实参是 0 和 1。将两者相加并返回 1。再次调用时的实参是 1 和 2，它返回 3。然后它计算 3+3=6、6+4=10， 最后计算 10+5=15。最后的值是 15，reduce() 返回这个值。\n\nYou may have noticed that the third call to reduce() in this example has only a single argument: there is no initial value specified. When you invoke reduce() like this with no initial value, it uses the first element of the array as the initial value. This means that the first call to the reduction function will have the first and second array elements as its first and second arguments. In the sum and product examples, we could have omitted the initial value argument.\n\n> 可能已经注意到了，上面第三次调用 reduce() 时只有一个实参：没有指定初始值。当不指定初始值调用 reduce() 时，它将使用数组的第一个元素作为其初始值。这意味着第一次调用化简函数就使用了第一个和第二个数组元素作为其第一个和第二个实参。在上面求和与求积的例子中，可以省略初始值实参。\n\nCalling reduce() on an empty array with no initial value argument causes a TypeError. If you call it with only one value—either an array with one element and no initial value or an empty array and an initial value—it simply returns that one value without ever calling the reduction function.\n\n> 在空数组上，不带初始值实参调用 reduce() 将导致类型错误异常。如果调用它的时候只有一个值（数组只有一个元素并且没有指定初始值，或者有一个空数组并且指定一个初始值）reduce() 只是简单地返回那个值而不会调用化简函数。\n\nreduceRight() works just like reduce(), except that it processes the array from highest index to lowest (right-to-left), rather than from lowest to highest. You might want to do this if the reduction operation has right-to-left associativity, for example:\n\n> reduceRight() 的工作原理和 reduce() 一样，不同的是它按照数组索引从高到低（从右到左）处理数组，而不是从低到高。如果 reduction 操作的优先顺序是从右到左，你可能想使用它，例如：\n\n```js\n// Compute 2^(3^4).  Exponentiation has right-to-left precedence\nlet a = [2, 3, 4];\na.reduceRight((acc,val) => Math.pow(val,acc)) // => 2.4178516392292583e+24\n```\nNote that neither reduce() nor reduceRight() accepts an optional argument that specifies the this value on which the reduction function is to be invoked. The optional initial value argument takes its place. See the Function.bind() method (§8.7.5) if you need your reduction function invoked as a method of a particular object.\n\n> 注意，reduce() 和 reduceRight() 都能接收一个可选的实参，它指定了化简函数调用时的 this 关键字的值。可选的初始值实参仍然需要占一个位置。如果想让化简函数作为一个特殊对象的方法调用，请参看 Function.bind() 方法（§8.7.5）。\n\nThe examples shown so far have been numeric for simplicity, but reduce() and reduceRight() are not intended solely for mathematical computations. Any function that can combine two values (such as two objects) into one value of the same type can be used as a reduction function. On the other hand, algorithms expressed using array reductions can quickly become complex and hard to understand, and you may find that it is easier to read, write, and reason about your code if you use regular looping constructs to process your arrays.\n\n> 为了简单起见，到目前位置所展示的例子都是数值的，但数学计算不是 reduce() 和 reduceRight() 的唯一意图。任何想要将两个相同类型的值（例如两个对象）合并到一个值的函数都可以用化简函数。另一方面，使用数组化简的算法可能很快变得复杂且难以理解，可能会发现，如果使用常规循环构造来处理数组则更容易读、写和推理。\n\n### 7.8.2 Flattening arrays with flat() and flatMap()\nIn ES2019, the flat() method creates and returns a new array that contains the same elements as the array it is called on, except that any elements that are themselves arrays are “flattened” into the returned array. For example:\n\n> 在 ES2019 中，flat() 方法创建并返回一个新的数组，该数组包含与调用的数组相同的元素，只不过作为数组的任何元素都\"展平\"到返回的数组中。例如：\n\n```js\n[1, [2, 3]].flat()    // => [1, 2, 3]\n[1, [2, [3]]].flat()  // => [1, 2, [3]]\n```\nWhen called with no arguments, flat() flattens one level of nesting. Elements of the original array that are themselves arrays are flattened, but array elements of those arrays are not flattened. If you want to flatten more levels, pass a number to flat():\n\n> 当调用时没有实参，flat() 将平展一个级别的嵌套。作为数组的原始数组的元素被展平，但这些数组的数组元素不会展平。如果要展平更多级别，需要传递数字给 flat()：\n\n```js\nlet a = [1, [2, [3, [4]]]];\na.flat(1)   // => [1, 2, [3, [4]]]\na.flat(2)   // => [1, 2, 3, [4]]\na.flat(3)   // => [1, 2, 3, 4]\na.flat(4)   // => [1, 2, 3, 4]\n```\nThe flatMap() method works just like the map() method (see “map()”) except that the returned array is automatically flattened as if passed to flat(). That is, calling a.flatMap(f) is the same as (but more efficient than) a.map(f).flat():\n\n> flatMap() 方法的工作方式与 map() 方法（见“map()”）类似，只不过返回的数组会自动展平，就像传递到 flat()。也就是说，调用 a.flatMap(f) 与 a.map(f).flat()（但更高效）相同：\n\n```js\nlet phrases = [\"hello world\", \"the definitive guide\"];\nlet words = phrases.flatMap(phrase => phrase.split(\" \"));\nwords // => [\"hello\", \"world\", \"the\", \"definitive\", \"guide\"];\n```\nYou can think of flatMap() as a generalization of map() that allows each element of the input array to map to any number of elements of the output array. In particular, flatMap() allows you to map input elements to an empty array, which flattens to nothing in the output array:\n\n> 可以将 flatMap() 视为 map() 的泛化，它允许输入数组的每个元素映射到输出数组的多个元素。特别的是，flatMap() 允许将输入元素映射到空数组，该数组在平展后不输出到数组中：\n\n```js\n// Map non-negative numbers to their square roots\n[-2, -1, 1, 2].flatMap(x => x < 0 ? [] : Math.sqrt(x)) // => [1, 2**0.5]\n```\n### 7.8.3 Adding arrays with concat()\nThe concat() method creates and returns a new array that contains the elements of the original array on which concat() was invoked, followed by each of the arguments to concat(). If any of these arguments is itself an array, then it is the array elements that are concatenated, not the array itself. Note, however, that concat() does not recursively flatten arrays of arrays. concat() does not modify the array on which it is invoked:\n\n> concat() 方法创建并返回一个新数组，它的元素包括调用 concat() 的原始数组的元素和 concat() 的每个实参。如果这些实参中的任何一个自身是数组，则连接的是数组的元素，而非数组本身。但要注意，concat() 不会递归扁平化数组的数组。concat() 也不会修改调用的数组：\n\n```js\nlet a = [1,2,3];\na.concat(4, 5)          // => [1,2,3,4,5]\na.concat([4,5],[6,7])   // => [1,2,3,4,5,6,7]; arrays are flattened\na.concat(4, [5,[6,7]])  // => [1,2,3,4,5,[6,7]]; but not nested arrays\na                       // => [1,2,3]; the original array is unmodified\n```\nNote that concat() makes a new copy of the array it is called on. In many cases, this is the right thing to do, but it is an expensive operation. If you find yourself writing code like `a = a.concat(x)`, then you should think about modifying your array in place with push() or splice() instead of creating a new one.\n\n> 请注意，concat() 创建调用数组的新副本。在许多情况下，这是正确的做法，但它是一个昂贵的操作。如果您发现自己编写代码像 `a = a.concat(x)`，那么您应该考虑使用 push() 或 splice() 修改数组，而不是创建新的数组。\n\n### 7.8.4 Stacks and Queues with push(), pop(), shift(), and unshift()\nThe push() and pop() methods allow you to work with arrays as if they were stacks. The push() method appends one or more new elements to the end of an array and returns the new length of the array. Unlike concat(), push() does not flatten array arguments. The pop() method does the reverse: it deletes the last element of an array, decrements the array length, and returns the value that it removed. Note that both methods modify the array in place. The combination of push() and pop() allows you to use a JavaScript array to implement a first-in, last-out stack. For example:\n\n> push() 和 pop() 方法允许将数组当做栈来使用。push() 方法在数组的尾部添加一个或多个元素，并返回数组新的长度。pop() 方法则相反：它删除数组的最后一个元素，减小数组长度并返回它删除的值。注意，两个方法都修改并替换原始数组而非生成一个修改版的新数组。组合使用 push() 和 pop() 能够用 JavaScript 数组实现先进后出的栈。例如：\n\n```js\nlet stack = [];       // stack == []\nstack.push(1,2);      // stack == [1,2];\nstack.pop();          // stack == [1]; returns 2\nstack.push(3);        // stack == [1,3]\nstack.pop();          // stack == [1]; returns 3\nstack.push([4,5]);    // stack == [1,[4,5]]\nstack.pop()           // stack == [1]; returns [4,5]\nstack.pop();          // stack == []; returns 1\n```\nThe push() method does not flatten an array you pass to it, but if you want to push all of the elements from one array onto another array, you can use the spread operator (§8.3.4) to flatten it explicitly:\n\n> push() 方法不展平传入的数组，但如果想要将数组的元素全部压入另外一个数组，可以使用展开运算符（§8.3.4）来显示展开：\n\n```js\na.push(...values);\n```\nThe unshift() and shift() methods behave much like push() and pop(), except that they insert and remove elements from the beginning of an array rather than from the end. unshift() adds an element or elements to the beginning of the array, shifts the existing array elements up to higher indexes to make room, and returns the new length of the array. shift() removes and returns the first element of the array, shifting all subsequent elements down one place to occupy the newly vacant space at the start of the array. You could use unshift() and shift() to implement a stack, but it would be less efficient than using push() and pop() because the array elements need to be shifted up or down every time an element is added or removed at the start of the array. Instead, though, you can implement a queue data structure by using push() to add elements at the end of an array and shift() to remove them from the start of the array:\n\n> unshift() 和 shift() 方法的行为非常类似于 push() 和 pop()，不一样的是前者是在数组的头部而非尾部进行元素的插入和删除操作。unshift() 在数组的头部添加一个或多个元素，并将已存在的元素移动到更高索引的位置来获得足够的空间，最后返回数组新的长度。shift() 删除数组的第一个元素并将其返回，然后把所有随后的元素下移一个位置来填补数组头部的空缺。可以使用 unshift() 和 shift() 实现栈，但它比使用 push() 和 pop() 的效率低，因为每次在数组头部添加或删除元素时，都需要向上或向下移动数组元素。但是，您可以使用 push() 在数组末尾添加元素并 shift() 从数组的头部删除它们来实现队列数据解构：\n\n```js\nlet q = [];            // q == []\nq.push(1,2);           // q == [1,2]\nq.shift();             // q == [2]; returns 1\nq.push(3)              // q == [2, 3]\nq.shift()              // q == [3]; returns 2\nq.shift()              // q == []; returns 3\n```\nThere is one feature of unshift() that is worth calling out because you may find it surprising. When passing multiple arguments to unshift(), they are inserted all at once, which means that they end up in the array in a different order than they would be if you inserted them one at a time:\n\n> unshift() 有一个特性是值得一提的，你可能会觉得它令人惊讶。将多个实参传入 unshift() 时，它们将一次全部插入，这意味着它们最终在数组中的顺序与一次插入一个实参的顺序时不同的：\n\n```js\nlet a = [];            // a == []\na.unshift(1)           // a == [1]\na.unshift(2)           // a == [2, 1]\na = [];                // a == []\na.unshift(1,2)         // a == [1, 2]\n```\n### 7.8.5 Subarrays with slice(), splice(), fill(), and copyWithin()\nArrays define a number of methods that work on contiguous regions, or subarrays or “slices” of an array. The following sections describe methods for extracting, replacing, filling, and copying slices.\n\n> 数组定义了许多在连续区域，子数组或数组的“片段”上工作的方法。 以下各节描述了提取，替换，填充和复制片段的方法。\n\n#### SLICE()\nThe slice() method returns a slice, or subarray, of the specified array. Its two arguments specify the start and end of the slice to be returned. The returned array contains the element specified by the first argument and all subsequent elements up to, but not including, the element specified by the second argument. If only one argument is specified, the returned array contains all elements from the start position to the end of the array. If either argument is negative, it specifies an array element relative to the length of the array. An argument of –1, for example, specifies the last element in the array, and an argument of –2 specifies the element before that one. Note that slice() does not modify the array on which it is invoked. Here are some examples:\n\n> slice() 方法返回指定数组的一个片段或子数组。它的两个实参分别指定了片段的开始和结束的位置。返回的数组包含第一个实参指定的位置到（但不包含）第二个实参指定的位置之间的所有数组元素。如果只指定一个实参，返回的数组将包含从开始位置到数组结尾的所有元素。如实参中出现负数，它表示相对于数组 length 的位置。例如，实参 -1 指定了最后一个元素，而 -2 指定了它前面的元素。注意，slice() 不会修改调用的数组。下面有一些示例：\n\n```js\nlet a = [1,2,3,4,5];\na.slice(0,3);    // Returns [1,2,3]\na.slice(3);      // Returns [4,5]\na.slice(1,-1);   // Returns [2,3,4]\na.slice(-3,-2);  // Returns [3]\n```\n#### SPLICE()\nsplice() is a general-purpose method for inserting or removing elements from an array. Unlike slice() and concat(), splice() modifies the array on which it is invoked. Note that splice() and slice() have very similar names but perform substantially different operations.\n\n> splice() 方法是在数组中插入或删除元素的通用方法。不同于 slice() 和 concat()，splice() 会修改调用的数组。注意，splice() 和 slice() 拥有非常相似的名字， 但它们的功能却有本质的区别。\n\nsplice() can delete elements from an array, insert new elements into an array, or perform both operations at the same time. Elements of the array that come after the insertion or deletion point have their indexes increased or decreased as necessary so that they remain contiguous with the rest of the array. The first argument to splice() specifies the array position at which the insertion and/or deletion is to begin. The second argument specifies the number of elements that should be deleted from (spliced out of) the array. (Note that this is another difference between these two methods. The second argument to slice() is an end position. The second argument to splice() is a length.) If this second argument is omitted, all array elements from the start element to the end of the array are removed. splice() returns an array of the deleted elements, or an empty array if no elements were deleted. For example:\n\n> splice() 能够从数组中删除元素、插入元素到数组中或者同时完成这两种操作。在插入或删除点之后的数组元素会根据需要增加或减小它们的索引值，因此数组的其他部分仍然保持连续的。splice() 的第一个实参指定了插入和（或）删除的起始位置。第二个实参指定了应该从数组中删除的元素的个数。（注意这里是这两个方法的另外一个不同。slice() 的第二个实参是结束的位置。splice() 的第二个实参是长度。）如果省略第二个实参，从起始点开始到数组结尾的所有元素都将被删除。splice() 返回一个由删除元素组成的数组，或者如果没有删除元素就返回一个空数组。例如：\n\n```js\nlet a = [1,2,3,4,5,6,7,8];\na.splice(4)    // => [5,6,7,8]; a is now [1,2,3,4]\na.splice(1,2)  // => [2,3]; a is now [1,4]\na.splice(1,1)  // => [4]; a is now [1]\n```\nThe first two arguments to splice() specify which array elements are to be deleted. These arguments may be followed by any number of additional arguments that specify elements to be inserted into the array, starting at the position specified by the first argument. For example:\n\n> splice() 的前两个实参指定了需要删除的数组元素。紧随其后的任意个数的实参指定了需要插入到数组中的元素，从第一个实参指定的位置开始插入。例如：\n\n```js\nlet a = [1,2,3,4,5];\na.splice(2,0,\"a\",\"b\")  // => []; a is now [1,2,\"a\",\"b\",3,4,5]\na.splice(2,2,[1,2],3)  // => [\"a\",\"b\"]; a is now [1,2,[1,2],3,3,4,5]\n```\nNote that, unlike concat(), splice() inserts arrays themselves, not the elements of those arrays.\n\n> 注意，不同于 concat()，splice() 插入数组本身，不是数组的元素。\n\n#### FILL()\nThe fill() method sets the elements of an array, or a slice of an array, to a specified value. It mutates the array it is called on, and also returns the modified array:\n\n> fill() 方法将数组或数组片段的元素填充为指定值。它将对调用它的数组进行突变，并返回修改后的数组：\n\n```js\nlet a = new Array(5);   // Start with no elements and length 5\na.fill(0)               // => [0,0,0,0,0]; fill the array with zeros\na.fill(9, 1)            // => [0,9,9,9,9]; fill with 9 starting at index 1\na.fill(8, 2, -1)        // => [0,9,8,8,9]; fill with 8 at indexes 2, 3\n```\nThe first argument to fill() is the value to set array elements to. The optional second argument specifies the starting index. If omitted, filling starts at index 0. The optional third argument specifies the ending index—array elements up to, but not including, this index will be filled. If this argument is omitted, then the array is filled from the start index to the end. You can specify indexes relative to the end of the array by passing negative numbers, just as you can for slice().\n\n> fill() 的第一个实参是将数组元素填充的值。可选的第二个实参指定起始索引。如果省略，则填充将从索引 0 开始。可选的第三个实参指定结束索引，将填充到（但不包括）该索引的数组元素。 如果省略此实参，则从起始索引到末尾填充数组。可以通过传递负数来指定相对于数组末尾的索引，就像 slice() 一样。\n\n#### COPYWITHIN()\ncopyWithin() copies a slice of an array to a new position within the array. It modifies the array in place and returns the modified array, but it will not change the length of the array. The first argument specifies the destination index to which the first element will be copied. The second argument specifies the index of the first element to be copied. If this second argument is omitted, 0 is used. The third argument specifies the end of the slice of elements to be copied. If omitted, the length of the array is used. Elements from the start index up to, but not including, the end index will be copied. You can specify indexes relative to the end of the array by passing negative numbers, just as you can for slice():\n\n> copyWithin() 将数组的一个片段复制到数组中的新位置。它在适当的位置修改数组并返回修改后的数组，但不会更改数组的长度。第一个实参指定将第一个元素复制到的目标索引。第二个实参指定被复制的第一个元素的索引。如果省略此第二个实参，则使用 0。第三个实参指定被复制的元素片段的结尾。如果省略，则使用数组的长度。从开始索引到结束索引（但不包括结束索引）的元素将被复制。可以通过传递负数来指定相对于数组末尾的索引，就像 slice() 一样：\n\n```js\nlet a = [1,2,3,4,5];\na.copyWithin(1)       // => [1,1,2,3,4]: copy array elements up one\na.copyWithin(2, 3, 5) // => [1,2,3,4,4]: copy last 2 elements to index 2\na.copyWithin(0, -2)   // => [4,4,3,4,4]: negative offsets work, too\n```\ncopyWithin() is intended as a high-performance method that is particularly useful with typed arrays (see §11.2). It is modeled after the memmove() function from the C standard library. Note that the copy will work correctly even if there is overlap between the source and destination regions.\n\n> copyWithin() 旨在作为一种高性能方法，对类型化数组特别有用（请参见 §11.2）。它模仿的 C 标准库中 memmove() 函数。 请注意，即使源区域和目标区域之间存在重叠，该拷贝也可以正常工作。\n\n### 7.8.6 Array Searching and Sorting Methods\nArrays implement indexOf(), lastIndexOf(), and includes() methods that are similar to the same-named methods of strings. There are also sort() and reverse() methods for reordering the elements of an array. These methods are described in the subsections that follow.\n\n> 数组实现 indexOf()、lastIndexOf() 和 include() 方法，这些方法类似于名称相同的字符串方法。还有 sort() 和 reverse() 方法，用于对数组元素进行重新排序。这些方法在下面的小节中介绍。\n\n#### INDEXOF() AND LASTINDEXOF()\nindexOf() and lastIndexOf() search an array for an element with a specified value and return the index of the first such element found, or -1 if none is found. indexOf() searches the array from beginning to end, and lastIndexOf() searches from end to beginning:\n\n> indexOf() 和 lastIndexOf() 在数组中搜索具有指定值的元素，并返回找到的第一个元素的索引，如果未找到，则返回 -1。indexOf() 从头到尾搜索数组，lastIndexOf() 从尾到头搜索：\n\n```js\nlet a = [0,1,2,1,0];\na.indexOf(1)       // => 1: a[1] is 1\na.lastIndexOf(1)   // => 3: a[3] is 1\na.indexOf(3)       // => -1: no element has value 3\n```\nindexOf() and lastIndexOf() compare their argument to the array elements using the equivalent of the === operator. If your array contains objects instead of primitive values, these methods check to see if two references both refer to exactly the same object. If you want to actually look at the content of an object, try using the find() method with your own custom predicate function instead.\n\n> indexOf() 和 lastIndexOf() 使用 === 运算符将其实参与数组元素进行比较。如果数组包含对象而不是原始值，则这些方法将检查两个引用是否都指向完全相同的对象。如果要实际查看对象的内容，尝试将 find() 方法代替自定义的断言函数。\n\nindexOf() and lastIndexOf() take an optional second argument that specifies the array index at which to begin the search. If this argument is omitted, indexOf() starts at the beginning and lastIndexOf() starts at the end. Negative values are allowed for the second argument and are treated as an offset from the end of the array, as they are for the slice() method: a value of –1, for example, specifies the last element of the array.\n\n> indexOf() 和 lastIndexOf() 采用可选的第二个实参，该实参指定开始搜索的数组索引。如果省略此参数，则 indexOf() 从开头开始，lastIndexOf() 从结尾开始。第二个参数允许使用负值，并将其视为距数组末端的偏移量，就像 slice() 方法一样：例如，值 –1 指定数组的最后一个元素。\n\nThe following function searches an array for a specified value and returns an array of all matching indexes. This demonstrates how the second argument to indexOf() can be used to find matches beyond the first.\n\n> 以下函数在数组中搜索指定的值，并返回所有匹配索引的数组。这演示了如何使用 indexOf() 的第二个参数来查找第一个参数之外的匹配项。\n\n```js\n// Find all occurrences of a value x in an array a and return an array\n// of matching indexes\nfunction findall(a, x) {\n    let results = [],            // The array of indexes we'll return\n        len = a.length,          // The length of the array to be searched\n        pos = 0;                 // The position to search from\n    while(pos < len) {           // While more elements to search...\n        pos = a.indexOf(x, pos); // Search\n        if (pos === -1) break;   // If nothing found, we're done.\n        results.push(pos);       // Otherwise, store index in array\n        pos = pos + 1;           // And start next search at next element\n    }\n    return results;              // Return array of indexes\n}\n```\nNote that strings have indexOf() and lastIndexOf() methods that work like these array methods, except that a negative second argument is treated as zero.\n\n> 请注意，字符串具有 indexOf() 和 lastIndexOf() 方法，它们与这些数组方法一样工作，不同之处在于第二个实参是负数时被视为零。\n\n#### INCLUDES()\nThe ES2016 includes() method takes a single argument and returns true if the array contains that value or false otherwise. It does not tell you the index of the value, only whether it exists. The includes() method is effectively a set membership test for arrays. Note, however, that arrays are not an efficient representation for sets, and if you are working with more than a few elements, you should use a real Set object (§11.1.1).\n\n> ES2016 的 includes() 方法采用单个实参，如果数组包含该值返回 true 否则 false。它不会告诉你值的索引，只告诉你该值是否存在。includes() 方法实际上是数组集的成员身份测试。但是请注意，数组不是 Set 的高效表示形式，如果使用多个元素，则应使用真正的 Set 对象（§11.1.1）。\n\nThe includes() method is slightly different than the indexOf() method in one important way. indexOf() tests equality using the same algorithm that the === operator does, and that equality algorithm considers the not-a-number value to be different from every other value, including itself. includes() uses a slightly different version of equality that does consider NaN to be equal to itself. This means that indexOf() will not detect the NaN value in an array, but includes() will:\n\n> includes() 方法在一个重要方面与 indexOf() 方法略有不同。indexOf() 与 === 运算符使用相同的算法测试相等性，并且这个相等算法认为非数字值与所有其他值（包括自身）不同。includes() 使用略有不同的相等算法，它认为 NaN 等于自身。这意味着 indexOf() 不会检测数组中的 NaN 值，但 includes() 可以：\n\n```js\nlet a = [1,true,3,NaN];\na.includes(true)            // => true\na.includes(2)               // => false\na.includes(NaN)             // => true\na.indexOf(NaN)              // => -1; indexOf can't find NaN\n```\n#### SORT()\nsort() sorts the elements of an array in place and returns the sorted array. When sort() is called with no arguments, it sorts the array elements in alphabetical order (temporarily converting them to strings to perform the comparison, if necessary):\n\n> sort() 对数组的元素直接进行排序，并返回排序后的数组。当调用 sort() 时，它会按字母顺序对数组元素进行排序（如有必要，暂时将它们转换为字符串以执行比较）：\n\n```js\nlet a = [\"banana\", \"cherry\", \"apple\"];\na.sort(); // a == [\"apple\", \"banana\", \"cherry\"]\n```\nIf an array contains undefined elements, they are sorted to the end of the array.\n\n> 如果数组中包含 undefined 元素，它们会被放在数组的结尾。\n\nTo sort an array into some order other than alphabetical, you must pass a comparison function as an argument to sort(). This function decides which of its two arguments should appear first in the sorted array. If the first argument should appear before the second, the comparison function should return a number less than zero. If the first argument should appear after the second in the sorted array, the function should return a number greater than zero. And if the two values are equivalent (i.e., if their order is irrelevant), the comparison function should return 0. So, for example, to sort array elements into numerical rather than alphabetical order, you might do this:\n\n> 若要将数组按字母顺序以外的顺序排序，必须将比较函数作为实参传递给 sort()。该函数决定了它的两个实参在排好序的数组中的先后顺序。假设第一个实参应该在前，比较函数应该返回一个小于 0 的数值。反之，假设第一个参数应该在后，函数应该返回一个大于 0 的数值。并且，假设两个值相等（也就是说，它们的顺序无关紧要），函数应该返回 0。例如，用数值大小而非字母表顺序进行数组排序，代码如下：\n\n```js\nlet a = [33, 4, 1111, 222];\na.sort();               // a == [1111, 222, 33, 4]; alphabetical order\na.sort(function(a,b) {  // Pass a comparator function\n    return a-b;         // Returns < 0, 0, or > 0, depending on order\n});                     // a == [4, 33, 222, 1111]; numerical order\na.sort((a,b) => b-a);   // a == [1111, 222, 33, 4]; reverse numerical order\n```\nAs another example of sorting array items, you might perform a case-insensitive alphabetical sort on an array of strings by passing a comparison function that converts both of its arguments to lowercase (with the toLowerCase() method) before comparing them:\n\n> 另外一个数组元素排序的例子，也许需要对一个字符串数组执行不区分大小写的字母表排序，比较函数首先将实参都转化为小写字符串（使用 toLowerCase() 方法），再开始比较：\n\n```js\nlet a = [\"ant\", \"Bug\", \"cat\", \"Dog\"];\na.sort();    // a == [\"Bug\",\"Dog\",\"ant\",\"cat\"]; case-sensitive sort\na.sort(function(s,t) {\n    let a = s.toLowerCase();\n    let b = t.toLowerCase();\n    if (a < b) return -1;\n    if (a > b) return 1;\n    return 0;\n});   // a == [\"ant\",\"Bug\",\"cat\",\"Dog\"]; case-insensitive sort\n```\n#### REVERSE()\nThe reverse() method reverses the order of the elements of an array and returns the reversed array. It does this in place; in other words, it doesn’t create a new array with the elements rearranged but instead rearranges them in the already existing array:\n\n> reverse() 方法反转数组中元素的顺序并返回反转后的数组。它直接在数组中操作，换一种说法，它不创建一个新的数组，它不创建一个新的带有排序后的元素的数组，而是直接在已存在的数组中进行排序。\n\n```js\nlet a = [1,2,3];\na.reverse();   // a == [3,2,1]\n```\n### 7.8.7 Array to String Conversions\nThe Array class defines three methods that can convert arrays to strings, which is generally something you might do when creating log and error messages. (If you want to save the contents of an array in textual form for later reuse, serialize the array with JSON.stringify() [§6.8] instead of using the methods described here.)\n\n> Array 类定义了三个方法来将数组转化为字符串，通常在创建日志和错误信息时会用到。（如果要以文本形式保存数组的内容供以后重用，请使用 JSON.stringify()（§6.8）序列化数组，而不是使用此处描述的方法。）\n\nThe join() method converts all the elements of an array to strings and concatenates them, returning the resulting string. You can specify an optional string that separates the elements in the resulting string. If no separator string is specified, a comma is used:\n\n> join() 方法将数组的所有元素转换为字符串并连接它们，返回生成的字符串。可以指定一个可选字符串来分隔生成的字符串中的元素。如果未指定分隔符字符串，则使用逗号：\n\n```js\nlet a = [1, 2, 3];\na.join()               // => \"1,2,3\"\na.join(\" \")            // => \"1 2 3\"\na.join(\"\")             // => \"123\"\nlet b = new Array(10); // An array of length 10 with no elements\nb.join(\"-\")            // => \"---------\": a string of 9 hyphens\n```\nThe join() method is the inverse of the String.split() method, which creates an array by breaking a string into pieces.\n\n> join() 方法是 String.split() 方法的反向方法，该方法通过将字符串拆分为多个片段来创建数组。\n\nArrays, like all JavaScript objects, have a toString() method. For an array, this method works just like the join() method with no arguments:\n\n> 数组与所有 JavaScript 对象一样，具有 toString() 方法。对于数组，此方法的工作方式与没有参数的 join() 方法相同：\n\n```js\n[1,2,3].toString()          // => \"1,2,3\"\n[\"a\", \"b\", \"c\"].toString()  // => \"a,b,c\"\n[1, [2,\"c\"]].toString()     // => \"1,2,c\"\n```\nNote that the output does not include square brackets or any other sort of delimiter around the array value.\n\n> 请注意，输出不包括方括号或数组值周围的任何其他分隔符。\n\ntoLocaleString() is the localized version of toString(). It converts each array element to a string by calling the toLocaleString() method of the element, and then it concatenates the resulting strings using a locale-specific (and implementation-defined) separator string.\n\n> toLocaleString() 是 toString() 的本地化版本。它通过调用元素的 toLocaleString() 方法将每个数组元素转换为字符串，然后使用特定于区域设置（和实现定义）分隔符字符串连接生成的字符串。\n\n### 7.8.8 Static Array Functions\nIn addition to the array methods we’ve already documented, the Array class also defines three static functions that you can invoke through the Array constructor rather than on arrays. Array.of() and Array.from() are factory methods for creating new arrays. They were documented in §7.1.4 and §7.1.5.\n\n> 除了我们已经记录的数组方法之外，Array 类还定义了三个静态函数，可以通过 Array 构造函数而不是数组调用。Array.of() 和 Array.from() 是用于创建新数组的工厂方法。它们记录在 §7.1.4 和 §7.1.5 中。\n\nThe one other static array function is Array.isArray(), which is useful for determining whether an unknown value is an array or not:\n\n> 另外一个静态数组方法是 Array.isArray()，用来判断一个未知值是否是数组：\n\n```js\nArray.isArray([])     // => true\nArray.isArray({})     // => false\n```\n## 7.9 Array-Like Objects\nAs we’ve seen, JavaScript arrays have some special features that other objects do not have:\n\n> 我们已经看到，JavaScript 数组的有一些特性是其他对象所没有的：\n\nThe length property is automatically updated as new elements are added to the list.\n\n> 当有新的元素添加到列表中时，自动更新 length 属性。\n\nSetting length to a smaller value truncates the array.\n\n> length 设置为一个较小值将截断数组。\n\nArrays inherit useful methods from Array.prototype.\n\n> 从 Array.prototype 中继承一些有用的方法。\n\nArray.isArray() returns true for arrays.\n\n> 数组传入 Array.isArray() 方法返回 true。\n\nThese are the features that make JavaScript arrays distinct from regular objects. But they are not the essential features that define an array. It is often perfectly reasonable to treat any object with a numeric length property and corresponding non-negative integer properties as a kind of array.\n\n> 这些特性让 JavaScript 数组和常规的对象有明显的区别。但是它们不是定义数组的本质特性。一种常常完全合理的看法是把拥有一个数值型 length 属性和对应非负整数属性的对象看作数组的同类。\n\nThese “array-like” objects actually do occasionally appear in practice, and although you cannot directly invoke array methods on them or expect special behavior from the length property, you can still iterate through them with the same code you’d use for a true array. It turns out that many array algorithms work just as well with array-like objects as they do with real arrays. This is especially true if your algorithms treat the array as read-only or if they at least leave the array length unchanged.\n\n> 实际上这些“类数组”对象在实践中偶尔出现，虽然不能通过它们直接调用数组方法或者期望 length 属性有什么特殊的行为，但是仍然可以用针对真正数组遍历代码来遍历它们。结论就是很多数组算法针对类数组对象同样奏效，就像针对真正的数组一样。尤其是这种情况，算法把数组看成只读的或者如果保持数组长度不变。\n\nThe following code takes a regular object, adds properties to make it an array-like object, and then iterates through the “elements” of the resulting pseudo-array:\n\n> 以下代码为一个常规对象增加了一些属性使其变成类数组对象，然后遍历生成的伪数组的“元素”：\n\n```js\nlet a = {};  // Start with a regular empty object\n\n// Add properties to make it \"array-like\"\nlet i = 0;\nwhile(i < 10) {\n    a[i] = i * i;\n    i++;\n}\na.length = i;\n\n// Now iterate through it as if it were a real array\nlet total = 0;\nfor(let j = 0; j < a.length; j++) {\n    total += a[j];\n}\n```\nIn client-side JavaScript, a number of methods for working with HTML documents (such as document.querySelectorAll(), for example) return array-like objects. Here’s a function you might use to test for objects that work like arrays:\n\n> 在客户端 JavaScript 中，很多作用于 HTML documents 的方法（例如 document.querySelectorAll()）返回类数组对象。下面这个函数可能会用于测试对象是否可以用作类数组：\n\n```js\n// Determine if o is an array-like object.\n// Strings and functions have numeric length properties, but are\n// excluded by the typeof test. In client-side JavaScript, DOM text\n// nodes have a numeric length property, and may need to be excluded\n// with an additional o.nodeType !== 3 test.\nfunction isArrayLike(o) {\n    if (o &&                            // o is not null, undefined, etc.\n        typeof o === \"object\" &&        // o is an object\n        Number.isFinite(o.length) &&    // o.length is a finite number\n        o.length >= 0 &&                // o.length is non-negative\n        Number.isInteger(o.length) &&   // o.length is an integer\n        o.length < 4294967295) {        // o.length < 2^32 - 1\n        return true;                    // Then o is array-like.\n    } else {\n        return false;                   // Otherwise it is not.\n    }\n}\n```\nWe’ll see in a later section that strings behave like arrays. Nevertheless, tests like this one for array-like objects typically return false for strings—they are usually best handled as strings, not as arrays.\n\n> 我们会在下一节看到字符串的行为像数组一样。尽管如此，对于数组这种测试（对字符串通常返回 false ）它们通常最好作为字符串处理，而不是作为数组处理。\n\nMost JavaScript array methods are purposely defined to be generic so that they work correctly when applied to array-like objects in addition to true arrays. Since array-like objects do not inherit from Array.prototype, you cannot invoke array methods on them directly. You can invoke them indirectly using the Function.call method, however (see §8.7.4 for details):\n\n> 大多数 JavaScript 数组方法都特意定义为泛型，以便它们在应用于除数组之外的类数组可以正常工作。由于类数组对象不会从 Array.prototype 继承，因此不能直接在它们上调用数组方法。但是，可以使用 Function.call 方法间接调用它们（详情请参阅 §8.7.4）：\n\n```js\nlet a = {\"0\": \"a\", \"1\": \"b\", \"2\": \"c\", length: 3}; // An array-like object\nArray.prototype.join.call(a, \"+\")                  // => \"a+b+c\"\nArray.prototype.map.call(a, x => x.toUpperCase())  // => [\"A\",\"B\",\"C\"]\nArray.prototype.slice.call(a, 0)   // => [\"a\",\"b\",\"c\"]: true array copy\nArray.from(a)                      // => [\"a\",\"b\",\"c\"]: easier array copy\n```\nThe second-to-last line of this code invokes the Array slice() method on an array-like object in order to copy the elements of that object into a true array object. This is an idiomatic trick that exists in much legacy code, but is now much easier to do with Array.from().\n\n> 此代码倒数第二行调用数组类对象上的 Array slice() 方法，以便将该对象的元素复制到真正的数组对象中。这是一个惯用的技巧，存在于许多旧代码中，但现在使用 Array.from() 要容易得多。\n\n## 7.10 Strings as Arrays\nJavaScript strings behave like read-only arrays of UTF-16 Unicode characters. Instead of accessing individual characters with the charAt() method, you can use square brackets:\n\n> JavaScript 字符串的行为类似于 UTF-16 Unicode 字符的只读数组。可以使用方括号替代 charAt() 方法访问单个字符：\n\n```js\nlet s = \"test\";\ns.charAt(0)    // => \"t\"\ns[1]           // => \"e\"\n```\nThe typeof operator still returns “string” for strings, of course, and the Array.isArray() method returns false if you pass it a string.\n\n> 当然，字符串使用 typeof 运算符仍然返回 \"string\"，如果将字符串传递给 Array.isArray() 方法，则返回 false。\n\nThe primary benefit of indexable strings is simply that we can replace calls to charAt() with square brackets, which are more concise and readable, and potentially more efficient. The fact that strings behave like arrays also means, however, that we can apply generic array methods to them. For example:\n\n> 可索引字符串的主要好处是，我们可以用方括号替换对 charAt() 的调用，方括号更简洁、更可读，而且可能更高效。但是，字符串的行为类似于数组，也意味着我们可以对它们应用泛型数组方法。例如：\n\n```js\nArray.prototype.join.call(\"JavaScript\", \" \")  // => \"J a v a S c r i p t\"\n```\nKeep in mind that strings are immutable values, so when they are treated as arrays, they are read-only arrays. Array methods like push(), sort(), reverse(), and splice() modify an array in place and do not work on strings. Attempting to modify a string using an array method does not, however, cause an error: it simply fails silently.\n\n> 请记住，字符串是不可变值，因此当字符串被视为数组时，它们是只读数组。数组方法 push()、sort()、reverse() 和 splice() 直接修改数组，它们不能处理字符串。但是，尝试使用数组方法修改字符串不会引发异常：它只是静默失败。\n\n## 7.11 Summary\nThis chapter has covered JavaScript arrays in depth, including esoteric details about sparse arrays and array-like objects. The main points to take from this chapter are:\n\n> 本章深入介绍了 JavaScript 数组，包括有关稀疏数组和类数组对象的深奥细节。本章要点包括：\n\nArray literals are written as comma-separated lists of values within square brackets.\n\n> 数组字面量编写：方括号内逗号分隔值列表。\n\nIndividual array elements are accessed by specifying the desired array index within square brackets.\n\n> 通过在方括号内指定所需的数组索引来访问单个数组元素。\n\nThe for/of loop and ... spread operator introduced in ES6 are particularly useful ways to iterate arrays.\n\n> for/of 循环和 ES6 中引入的 ... 展开运算符是遍历数组的特别有用的方法。\n\nThe Array class defines a rich set of methods for manipulating arrays, and you should be sure to familiarize yourself with the Array API.\n\n> Array 类定义了一组用于操作数组的丰富方法，应该确保熟悉 Array API。"
  },
  {
    "path": "content/posts/ch8.md",
    "content": "---\ntitle: \"第 8 章 函数\"\ndate: 2020-11-02T22:18:35+08:00\n---\n\nThis chapter covers JavaScript functions. Functions are a fundamental building block for JavaScript programs and a common feature in almost all programming languages. You may already be familiar with the concept of a function under a name such as subroutine or procedure.\n\n> 本章介绍了 JavaScript 函数。函数是 JavaScript 程序的基本构建块，也是几乎所有编程语言的共同特性。你可能已经了解函数的概念，如子例程或过程。\n\nA function is a block of JavaScript code that is defined once but may be executed, or invoked, any number of times. JavaScript functions are parameterized: a function definition may include a list of identifiers, known as parameters, that work as local variables for the body of the function. Function invocations provide values, or arguments, for the function’s parameters. Functions often use their argument values to compute a return value that becomes the value of the function-invocation expression. In addition to the arguments, each invocation has another value—the invocation context—that is the value of the this keyword.\n\n> 函数是一个 JavaScript 代码块，只定义一次，但可以执行或调用任意次数。JavaScript 函数是参数化的：一个函数定义可以包含一个标识符列表，称为参数，作为函数体的局部变量。函数调用为函数的参数提供值或实参。函数通常使用它们的实参值来计算一个返回值，该返回值成为函数调用表达式的值。除了参数之外，每次调用都有另一个值——调用上下文——即 this 关键字的值。\n\nIf a function is assigned to a property of an object, it is known as a method of that object. When a function is invoked on or through an object, that object is the invocation context or this value for the function. Functions designed to initialize a newly created object are called constructors. Constructors were described in §6.2 and will be covered again in Chapter 9.\n\n> 如果函数挂载在一个对象上作为其属性，它就被称为方法。当该方法在对象中被调用或通过对象调用时，该对象就是该方法函数的调用上下文或 this 值。用于初始化新创建的对象的函数称为构造函数。构造函数在 §6.2 中有介绍，我们将在第 9 章中再次谈到它。\n\nIn JavaScript, functions are objects, and they can be manipulated by programs. JavaScript can assign functions to variables and pass them to other functions, for example. Since functions are objects, you can set properties on them and even invoke methods on them.\n\n> 在 JavaScript 中，函数是对象，它们可以被程序操作。例如，JavaScript 可以将函数赋给变量，并将它们传递给其他函数。由于函数是对象，所以您可以给它们设置属性，甚至调用它们的方法。\n\nJavaScript function definitions can be nested within other functions, and they have access to any variables that are in scope where they are defined. This means that JavaScript functions are closures, and it enables important and powerful programming techniques.\n\n> JavaScript 函数可以嵌套在其他函数中定义，并且它们可以访问定义它们所处的作用域内任何变量。这意味着 JavaScript 函数是闭包，支持闭包是非常重要的，它是非常强大的编程技巧。\n\n## 8.1 Defining Functions\n\nThe most straightforward way to define a JavaScript function is with the function keyword, which can be used as a declaration or as an expression. ES6 defines an important new way to define functions without the function keyword: “arrow functions” have a particularly compact syntax and are useful when passing one function as an argument to another function. The subsections that follow cover these three ways of defining functions. Note that some details of function definition syntax involving function parameters are deferred to §8.3.\n\n> 定义 JavaScript 函数最直接的方法是使用 function 关键字，它既可以用作声明又可以用作表达式。ES6 定义了一种不使用 function 关键字的重要新方法来定义的函数：“箭头函数”，它具有特别简洁语法，并且在将一个函数作为参数传递给另一个函数的场景中非常实用。接下来的小节将介绍这三种定义函数的方法。注意，关于函数定义语法包含的函数参数相关内容将在 §8.3 中介绍。\n\nIn object literals and class definitions, there is a convenient shorthand syntax for defining methods. This shorthand syntax was covered in §6.10.5 and is equivalent to using a function definition expression and assigning it to an object property using the basic name:value object literal syntax. In another special case, you can use keywords get and set in object literals to define special property getter and setter methods. This function definition syntax was covered in §6.10.6.\n\n> 在对象字面量和类定义中，有一种方便的快捷语法来定义方法。这种简写语法在 §6.10.5 中有介绍，相当于通过对象字面量语法将函数定义表达式用最基本的属性名：属性值的方式赋值给对象的属性。在另一种特殊情况下，可以在对象字面量中使用关键字 get 和 set 来定义特殊的属性 getter 和 setter 方法。这个函数定义语法在 §6.10.6 中介绍过。\n\nNote that functions can also be defined with the Function() constructor, which is the subject of §8.7.7. Also, JavaScript defines some specialized kinds of functions. function* defines generator functions (see Chapter 12) and async function defines asynchronous functions (see Chapter 13).\n\n> 注意，函数也可以用 Function() 构造函数来定义，这是 §8.7.7 的主题。此外，JavaScript 还定义了一些特殊类型的函数。function* 定义函数生成器（见第 12 章）和 async function 定义异步函数（见第 13 章）。\n\n### 8.1.1 Function Declarations\n\nFunction declarations consist of the function keyword, followed by these components:\n- An identifier that names the function. The name is a required part of function declarations: it is used as the name of a variable, and the newly defined function object is assigned to the variable.\n- A pair of parentheses around a comma-separated list of zero or more identifiers. These identifiers are the parameter names for the function, and they behave like local variables within the body of the function.\n- A pair of curly braces with zero or more JavaScript statements inside. These statements are the body of the function: they are executed whenever the function is invoked.\n\n---\n\n函数声明由 function 关键字组成，后面跟着这些组件:\n- 函数名称标识符。名称是函数声明的必要部分:它用作变量的名称，并将新定义的函数对象赋值给该变量。\n- 一对圆括号，其中包含由0个或者多个用逗号分隔的标识符组成的列表。这些标识符是函数的参数名，它们的行为类似于函数体中的局部变量。\n- 一对花括号，其中包含由0条或者多条 JavaScript 语句。这些语句构成了函数体：每当函数调用时，就会执行这些语句。\n\nHere are some example function declarations:\n\n> 下面是一些函数声明的例子:\n\n```js\n// Print the name and value of each property of o.  Return undefined.\nfunction printprops(o) {\n    for(let p in o) {\n        console.log(`${p}: ${o[p]}\\n`);\n    }\n}\n\n// Compute the distance between Cartesian points (x1,y1) and (x2,y2).\nfunction distance(x1, y1, x2, y2) {\n    let dx = x2 - x1;\n    let dy = y2 - y1;\n    return Math.sqrt(dx*dx + dy*dy);\n}\n\n// A recursive function (one that calls itself) that computes factorials\n// Recall that x! is the product of x and all positive integers less than it.\nfunction factorial(x) {\n    if (x <= 1) return 1;\n    return x * factorial(x-1);\n}\n```\n\nOne of the important things to understand about function declarations is that the name of the function becomes a variable whose value is the function itself. Function declaration statements are “hoisted” to the top of the enclosing script, function, or block so that functions defined in this way may be invoked from code that appears before the definition. Another way to say this is that all of the functions declared in a block of JavaScript code will be defined throughout that block, and they will be defined before the JavaScript interpreter begins to execute any of the code in that block.\n\n> 重中之重要理解函数声明是函数名变成一个变量，这个变量的值是函数本身。函数声明语句“被提前”到脚本、函数、块之前，因此这种方式定义的函数可以在它定义之前被调用。另一种说法是所有声明在 Javascript 代码块中的函数，在块内始终是有定义的，它们定义在 JavaScript 解释器开始解释执行块内任何语句之前。\n\nThe distance() and factorial() functions we’ve described are designed to compute a value, and they use return to return that value to their caller. The return statement causes the function to stop executing and to return the value of its expression (if any) to the caller. If the return statement does not have an associated expression, the return value of the function is undefined.\n\n> distance() 和 factorial() 函数计算一个值，它们用 retrun 来将这个值返回给调用者。return 语句导致函数停止执行，并返回它的表达式的值（如果有的话）给调用者。如果 return 语句没有一个与之相关的表达式，则函数返回 undefined 值。\n\nThe printprops() function is different: its job is to output the names and values of an object’s properties. No return value is necessary, and the function does not include a return statement. The value of an invocation of the printprops() function is always undefined. If a function does not contain a return statement, it simply executes each statement in the function body until it reaches the end, and returns the undefined value to the caller.\n\n> printprops() 函数不同：它负责输出对象属性的名称和值。没有返回值的必要，并且该函数也不包含一个 return 语句。 调用 printprops() 函数的返回值永远是 undefined。如果一个函数不包含一个 return 语句，它仅仅执行函数体内每一条语句直到结束，并返回 undefined 给调用者。\n\nPrior to ES6, function declarations were only allowed at the top level within a JavaScript file or within another function. While some implementations bent the rule, it was not technically legal to define functions inside the body of loops, conditionals, or other blocks. In the strict mode of ES6, however, function declarations are allowed within blocks. A function defined within a block only exists within that block, however, and is not visible outside the block.\n\n> 在 ES6 之前，函数只允许在 JavaScript 文件顶层或者其他函数中声明。然而一些实现违反规约，在循环体条件体或者其他块中定义函数。在 ES6 的严格模式下，函数允许在块内进行声明。一个定义在块内的函数只存在于该块内，块外是不可见的。\n\n### 8.1.2 Function Expressions\nFunction expressions look a lot like function declarations, but they appear within the context of a larger expression or statement, and the name is optional.\n\n> 函数表达式看起来很像函数声明，但是它出现在一个它的上层表达式或语句的上下文中，并且函数名称是可选项。\n\n Here are some example function expressions:\n\n> 下面是一些函数表达式的例子：\n\n```js\n// This function expression defines a function that squares its argument.\n// Note that we assign it to a variable\nconst square = function(x) { return x*x; };\n\n// Function expressions can include names, which is useful for recursion.\nconst f = function fact(x) { if (x <= 1) return 1; else return x*fact(x-1); };\n\n// Function expressions can also be used as arguments to other functions:\n[3,2,1].sort(function(a,b) { return a-b; });\n\n// Function expressions are sometimes defined and immediately invoked:\nlet tensquared = (function(x) {return x*x;}(10));\n```\n\nNote that the function name is optional for functions defined as expressions, and most of the preceding function expressions we’ve shown omit it. A function declaration actually declares a variable and assigns a function object to it. A function expression, on the other hand, does not declare a variable: it is up to you to assign the newly defined function object to a constant or variable if you are going to need to refer to it multiple times. It is a good practice to use const with function expressions so you don’t accidentally overwrite your functions by assigning new values.\n\n> 注意函数名称在函数表达式中是可选项，在大部分函数表达式中我们省略了它。函数声明实际上声明了一个变量并且将函数对象赋值给它。按照这个角度来看，函数表达式没有声明一个变量：可以根据它是否会多次调用由你自己决定是将新定义的函数对象赋值给一个常量还是变量。用 const 定义函数表达式是一个非常好的做法，你不会因为意外赋值而重写了你的函数。\n\nA name is allowed for functions, like the factorial function, that need to refer to themselves. If a function expression includes a name, the local function scope for that function will include a binding of that name to the function object. In effect, the function name becomes a local variable within the function. Most functions defined as expressions do not need names, which makes their definition more compact (though not nearly as compact as arrow functions, described below).\n\n> 可以给函数一个名称，就像 factorial 函数，它需要调用它自己。如果一个函数表达式包含一个名称，那这个函数的局部函数作用域内会包含一个属性名为该函数名的对象，其值绑定的是该函数。实际上，函数名变成这个函数的一个局部变量。大多数函数表达式不需要函数名称，这让它们的定义更简洁（但是并没有下面要讲的箭头函数简洁）。\n\nThere is an important difference between defining a function f() with a function declaration and assigning a function to the variable f after creating it as an expression. When you use the declaration form, the function objects are created before the code that contains them starts to run, and the definitions are hoisted so that you can call these functions from code that appears above the definition statement. This is not true for functions defined as expressions, however: these functions do not exist until the expression that defines them are actually evaluated. Furthermore, in order to invoke a function, you must be able to refer to it, and you can’t refer to a function defined as an expression until it is assigned to a variable, so functions defined with expressions cannot be invoked before they are defined.\n\n> 在函数声明和函数表达式之间有一个非常重要的不同。当你用函数声明，该函数对象创建于该函数所在作用域的代码开始执行之前，也就是声明提前，所以你可以在函数定义之前调用他们。如果用函数表达式来定义一个函数，这样使用就是不对的：该函数不会存在，直到函数定义表达式真正被计算。因为，想要执行一个函数，你必须可以引用它，而一个函数表达式定义的函数一直到该函数赋值给一个变量后才能被引用，所以要使用函数表达式需要在函数被调用之前定义。\n\n### 8.1.3 Arrow Functions\nIn ES6, you can define functions using a particularly compact syntax known as “arrow functions.” This syntax is reminiscent of mathematical notation and uses an => “arrow” to separate the function parameters from the function body. The function keyword is not used, and, since arrow functions are expressions instead of statements, there is no need for a function name, either. The general form of an arrow function is a comma-separated list of parameters in parentheses, followed by the => arrow, followed by the function body in curly braces:\n\n> 在 ES6 之后，你可以用一个特别简洁的语法来定义函数，被称为“箭头函数”。这个语法联想到数学符号，用一个 => \"箭头\"来分隔函数的参数和函数体。function 关键字未使用，并且，由于箭头函数是表达式而不是声明语句，也不需要一个函数名称。一般箭头函数用圆括号包含一个逗号分隔的参数列表，接一个 => 箭头，后面是花括号包含的函数体。\n\n```js\nconst sum = (x, y) => { return x + y; };\n```\n\nBut arrow functions support an even more compact syntax. If the body of the function is a single return statement, you can omit the return keyword, the semicolon that goes with it, and the curly braces, and write the body of the function as the expression whose value is to be returned:\n\n> 但是箭头函数支持更加简洁的语法。如果函数体只有一个简单的 return 语句，你可以省略 return 关键字，分号和花括号都一起省略，将函数体写成一个计算返回值的表达式。\n\n```js\nconst sum = (x, y) => x + y;\n```\n\nFurthermore, if an arrow function has exactly one parameter, you can omit the parentheses around the parameter list:\n\n> 而且，如果一个箭头函数只有一个参数，你可以省略参数列表的圆括号。\n\n```js\nconst polynomial = x => x*x + 2*x + 3;\n```\n\nNote, however, that an arrow function with no arguments at all must be written with an empty pair of parentheses:\n\n> 注意，如果箭头函数没有参数，必须写一对空圆括号。\n\n```js\nconst constantFunc = () => 42;\n```\n\nNote that, when writing an arrow function, you must not put a new line between the function parameters and the => arrow. Otherwise, you could end up with a line like const polynomial = x, which is a syntactically valid assignment statement on its own.\n\n> 注意，当写一个箭头函数时，函数参数和箭头之间不能换行。否则，可能会直接在赋值后中止，就像 const polynomial = x，因为它本身是一个语法上合法的赋值语句。\n\nAlso, if the body of your arrow function is a single return statement but the expression to be returned is an object literal, then you have to put the object literal inside parentheses to avoid syntactic ambiguity between the curly braces of a function body and the curly braces of an object literal:\n\n> 此外，如果箭头函数体是一个单一的 return 语句，而且他返回的是一个对象字面量，那必须将对象字面量用圆括号包起来，避免将对象字面量的大括号误解成函数体的大括号。\n\n```js\nconst f = x => { return { value: x }; };  // Good: f() returns an object\nconst g = x => ({ value: x });            // Good: g() returns an object\nconst h = x => { value: x };              // Bad: h() returns nothing\nconst i = x => { v: x, w: x };            // Bad: Syntax Error\n```\n\nIn the third line of this code, the function h() is truly ambiguous: the code you intended as an object literal can be parsed as a labeled statement, so a function that returns undefined is created. On the fourth line, however, the more complicated object literal is not a valid statement, and this illegal code causes a syntax error.\n\n> 这段代码的第三行，函数 h() 就有歧义：这段代码原本返回对象字面量被转化为一个标签语句，所以一个返回 undefined 的函数被创建。第四行，结构更复杂的对象字面量不是一个合法的语句，这段代码会抛出一个语法异常。\n\nThe concise syntax of arrow functions makes them ideal when you need to pass one function to another function, which is a common thing to do with array methods like map(), filter(), and reduce() (see §7.8.1), for example:\n\n> 简洁的箭头函数可以完美的传递一个函数给另外一个函数，比如一些数组的常规操作方法 map()，filter() 和 reduce()（见 §7.8.1），例如：\n\n```js\n// Make a copy of an array with null elements removed.\nlet filtered = [1,null,2,3].filter(x => x !== null); // filtered == [1,2,3]\n// Square some numbers:\nlet squares = [1,2,3,4].map(x => x*x);               // squares == [1,4,9,16]\n```\n\nArrow functions differ from functions defined in other ways in one critical way: they inherit the value of the this keyword from the environment in which they are defined rather than defining their own invocation context as functions defined in other ways do. This is an important and very useful feature of arrow functions, and we’ll return to it again later in this chapter. Arrow functions also differ from other functions in that they do not have a prototype property, which means that they cannot be used as constructor functions for new classes (see §9.2).\n\n> 箭头函数不同于用关键字定义的函数：箭头函数从定义它们的环境继承 this 关键字，而不是像其他定义方式那样定义自己的调用上下文。这是箭头函数一个重要且特别实用的特性，我们会在这一章的后面再次提到它。箭头函数也不同于其他函数，它们没有有原型属性。这意味着它不能被当作一个构造函数去创建一个类（见 §9.2）。\n\n### 8.1.4 Nested Functions\nIn JavaScript, functions may be nested within other functions. For example:\n\n> 在 JavaScript 中，函数可以嵌套在其他函数内。例如：\n\n```js\nfunction hypotenuse(a, b) {\n    function square(x) { return x*x; }\n    return Math.sqrt(square(a) + square(b));\n}\n```\n\nThe interesting thing about nested functions is their variable scoping rules: they can access the parameters and variables of the function (or functions) they are nested within. In the code shown here, for example, the inner function square() can read and write the parameters a and b defined by the outer function hypotenuse(). These scope rules for nested functions are very important, and we will consider them again in §8.6.\n\n> 嵌套函数的有趣之处在于它的变量作用域规则：它们可以访问嵌套它们（或多重嵌套）的函数的参数和变量。例如，在上面的代码里，内部函数 square() 可以读写外部函数 hypotenuse() 定义的参数 a 和 b。这些作用域规则对嵌套函数非常重要，我们会在 §8.6 再深入了解它们。\n\n## 8.2 Invoking Functions\nThe JavaScript code that makes up the body of a function is not executed when the function is defined, but rather when it is invoked. JavaScript functions can be invoked in five ways:\n\n> 构成函数主体的 JavaScript 代码在定义之时并不会执行，只有调用该函数时，它们才会执行。JavaScript 函数可以以五种方式被调用。\n\nAs functions\n\nAs methods\n\nAs constructors\n\nIndirectly through their call() and apply() methods\n\nImplicitly, via JavaScript language features that do not appear like normal function invocations\n\n> 作为函数\n>\n> 作为方法\n>\n> 作为构造函数\n>\n> 通过它们的 call() 和 apply() 方法间接调用\n>\n> 隐式调用，不同于普通函数调，通过 JavaScript 语言特性调用函数。\n\n\n### 8.2.1 Function Invocation\nFunctions are invoked as functions or as methods with an invocation expression (§4.5). An invocation expression consists of a function expression that evaluates to a function object followed by an open parenthesis, a comma-separated list of zero or more argument expressions, and a close parenthesis. If the function expression is a property-access expression—if the function is the property of an object or an element of an array—then it is a method invocation expression. That case will be explained in the following example. The following code includes a number of regular function invocation expressions:\n\n> 函数或方法通过调用表达式（§4.5）被调用。调用表达式由以下部分组成，计算函数对象的函数表达式，一个开放圆括号，逗号分隔的零个或多个实参表达式列表，一个闭合圆括号。如果函数表达式是属性访问表达式（函数是一个对象的属性或者一个数组的元素）那么它是一个方法调用表达式。这种情况会通过下面的例子说明，接下来这个代码包含了一些常规的函数调用表达式：\n\n```js\nprintprops({x: 1});\nlet total = distance(0,0,2,1) + distance(2,1,3,5);\nlet probability = factorial(5)/factorial(13);\n```\nIn an invocation, each argument expression (the ones between the parentheses) is evaluated, and the resulting values become the arguments to the function. These values are assigned to the parameters named in the function definition. In the body of the function, a reference to a parameter evaluates to the corresponding argument value.\n\n> 在调用中，每个实参表达式（圆括号内的）执行计算，返回值作为函数的实参。这些值传给函数定义的参数。在函数体内，参数的引用指向对应实参的值。\n\nFor regular function invocation, the return value of the function becomes the value of the invocation expression. If the function returns because the interpreter reaches the end, the return value is undefined. If the function returns because the interpreter executes a return statement, then the return value is the value of the expression that follows the return or is undefined if the return statement has no value.\n\n> 对于常规的函数调用，函数返回值变成函数调用表达式的值。如果因解释器执行到函数结尾而返回，返回值就是 undefined。如果函数返回是因为解释器执行一个 return 语句，那么返回值是 return 后面的表达式的计算结果，如果 return 语句没有值也返回 undefined。\n\n#### CONDITIONAL INVOCATION\nIn ES2020 you can insert ?. after the function expression and before the open parenthesis in a function invocation in order to invoke the function only if it is not null or undefined. That is, the expression f?.(x) is equivalent (assuming no side effects) to:\n\n> 在 ES2020 中你可以通过在函数表达式和圆括号之间插入 ?. 符号，使函数只有在不为 null 和 undefined 时候再调用。表达式 f?.(x) 等价（假设没有副作用）于：\n\n```js\n(f !== null && f !== undefined) ? f(x) : undefined\n```\nFull details on this conditional invocation syntax are in §4.5.1.\n\n> 详细的条件执行语法描述在 §4.5.1。\n\nFor function invocation in non-strict mode, the invocation context (the this value) is the global object. In strict mode, however, the invocation context is undefined. Note that functions defined using the arrow syntax behave differently: they always inherit the this value that is in effect where they are defined.\n\n> 函数调用在非严格模式下，调用上下文（ this ）是全局对象。然而在严格模式下，调用上下文是 undefined。注意箭头语法定义的函数行为是不同的：实际上它们总是继承它们定义位置的 this 值。\n\nFunctions written to be invoked as functions (and not as methods) do not typically use the this keyword at all. The keyword can be used, however, to determine whether strict mode is in effect:\n\n> 以函数形式调用的函数通常不使用 this 关键字。不过，this 关键字可以用来判断当前是否是严格模式。\n\n```js\n// Define and invoke a function to determine if we're in strict mode.\nconst strict = (function() { return !this; }());\n```\n#### RECURSIVE FUNCTIONS AND THE STACK\nA recursive function is one, like the factorial() function at the start of this chapter, that calls itself. Some algorithms, such as those involving tree-based data structures, can be implemented particularly elegantly with recursive functions. When writing a recursive function, however, it is important to think about memory constraints. When a function A calls function B, and then function B calls function C, the JavaScript interpreter needs to keep track of the execution contexts for all three functions. When function C completes, the interpreter needs to know where to resume executing function B, and when function B completes, it needs to know where to resume executing function A. You can imagine these execution contexts as a stack. When a function calls another function, a new execution context is pushed onto the stack. When that function returns, its execution context object is popped off the stack. If a function calls itself recursively 100 times, the stack will have 100 objects pushed onto it, and then have those 100 objects popped off. This call stack takes memory. On modern hardware, it is typically fine to write recursive functions that call themselves hundreds of times. But if a function calls itself ten thousand times, it is likely to fail with an error such as “Maximum call-stack size exceeded.”\n\n> 递归函数就像本章开始的 factorial() 函数，它调用它自己。某些算法（如涉及基于树的数据结构）可以使用递归函数特别优雅地实现。在写递归函数时，考虑内存分配是很重要的。当函数 A 调用函数 B，然后函数 B 又调用函数 C 时，Javascript 编译器需要知道在哪里重新执行函数 B，当函数 B 执行完成后它需要知道在哪里执行函数 A。你可以将执行上下文想象成一个栈。当一个函数调用另外一个函数时，一个新的执行上下文被压入栈中。当被调用函数返回，它的执行上下文对象从栈中弹出。如果一个函数递归调用100次，那么会有100个对象被压入栈中，然后这100个对象再依次从栈中弹出。这种调用非常耗内存。以现代的硬件递归调用100次通常没什么问题。但是如果一个函数递归上千次，它可能会失败并报错“Maximum call-stack size exceeded.”。\n\n### 8.2.2 Method Invocation\nA method is nothing more than a JavaScript function that is stored in a property of an object. If you have a function f and an object o, you can define a method named m of o with the following line:\n\n> 方法只不过是对象属性函数。如果有一个函数 f 和一个对象 o，可以用下面的代码给对象 o 定义一个名为 m 的方法：\n\n```js\no.m = f;\n```\nHaving defined the method m() of the object o, invoke it like this:\n\n> 给对象 o 定义了方法 m()，用这种方式调用它：\n\n```js\no.m();\n```\nOr, if m() expects two arguments, you might invoke it like this:\n\n> 或者 m() 需要两个实参，可以这样调用它：\n\n```js\no.m(x, y);\n```\nThe code in this example is an invocation expression: it includes a function expression o.m and two argument expressions, x and y. The function expression is itself a property access expression, and this means that the function is invoked as a method rather than as a regular function.\n\n> 示例中的代码是一个调用表达式：它包含一个函数表达式 o.m 和两个实参表达式 x 和 y。函数表达式本身是一个属性访问表达式，这意味着该函数被当作一个方法调用，而不是一个普通的函数。\n\nThe arguments and return value of a method invocation are handled exactly as described for regular function invocation. Method invocations differ from function invocations in one important way, however: the invocation context. Property access expressions consist of two parts: an object (in this case o) and a property name (m). In a method-invocation expression like this, the object o becomes the invocation context, and the function body can refer to that object by using the keyword this. Here is a concrete example:\n\n> 对方法调用的参数和返回值的处理，和上面所描述的普通函数调用完全一致。但是，方法调用和函数调用有一个重要的区别，即：调用上下文。属性访问表达式由两部分组成：一个对象（本例中的 o）和属性名称（m）。在像这样的方法调用表达式里，对象 o 成为调用上下文，函数体可以使用关键字this引用该对象。下面是一个具体的例子：\n\n```js\nlet calculator = { // An object literal\n    operand1: 1,\n    operand2: 1,\n    add() {        // We're using method shorthand syntax for this function\n        // Note the use of the this keyword to refer to the containing object.\n        this.result = this.operand1 + this.operand2;\n    }\n};\ncalculator.add();  // A method invocation to compute 1+1.\ncalculator.result  // => 2\n```\nMost method invocations use the dot notation for property access, but property access expressions that use square brackets also cause method invocation. The following are both method invocations, for example:\n\n> 大多数方法调用使用点符号来访问属性，使用方括号（属性访问表达式）也可以进行属性访问操作。下面两个例子都是函数调用：\n\n```js\no[\"m\"](x,y);   // Another way to write o.m(x,y).\na[0](z)        // Also a method invocation (assuming a[0] is a function).\n```\nMethod invocations may also involve more complex property access expressions:\n\n> 方法调用可能包括更复杂的属性访问表达式：\n\n```js\ncustomer.surname.toUpperCase(); // Invoke method on customer.surname\nf().m();                        // Invoke method m() on return value of f()\n```\nMethods and the this keyword are central to the object-oriented programming paradigm. Any function that is used as a method is effectively passed an implicit argument—the object through which it is invoked. Typically, a method performs some sort of operation on that object, and the method-invocation syntax is an elegant way to express the fact that a function is operating on an object. Compare the following two lines:\n\n> 方法和 this 关键字是面向对象编程范式的核心。任何函数只要作为方法调用实际上都会传入一个隐式的实参对象，就是调用这个方法对象本身。通常来讲，方法执行就是对象的某种操作，方法调用的语法也清晰的表达了它是操作对象的函数，比较下面两行代码：\n\n```js\nrect.setSize(width, height);\nsetRectSize(rect, width, height);\n```\nThe hypothetical functions invoked in these two lines of code may perform exactly the same operation on the (hypothetical) object rect, but the method-invocation syntax in the first line more clearly indicates the idea that it is the object rect that is the primary focus of the operation.\n\n> 我们假设这两行代码的功能完全一样，它们都作用于一个假定的对象 rect。可以看出，第一行的方法调用语法非常清晰地表明这个函数执行的载体是 rect 对象，函数中的所有操作都将基于这个对象。\n\n#### METHOD CHAINING\nWhen methods return objects, you can use the return value of one method invocation as part of a subsequent invocation. This results in a series (or “chain”) of method invocations as a single expression. When working with Promise-based asynchronous operations (see Chapter 13), for example, it is common to write code structured like this:\n\n> 当方法返回一个对象，这个对象还可以再调用它的方法。这种方法调用序列中（或“链”）每次的调用结果都是另外一个表达式的组成部分。比如，基于 Promise 的异步操作（参见第 13 章），我们常常会这样写代码：\n\n```js\n// Run three asynchronous operations in sequence, handling errors.\ndoStepOne().then(doStepTwo).then(doStepThree).catch(handleErrors);\n```\nWhen you write a method that does not have a return value of its own, consider having the method return this. If you do this consistently throughout your API, you will enable a style of programming known as method chaining1 in which an object can be named once and then multiple methods can be invoked on it:\n\n> 当方法并不需要返回值时，最好直接返回 this。如果在设计的 API 中一直采用这种方式，使用 API 就可以用方法链 ^1 风格的编程，在这种编程风格中，只要指定一次要调用的对象即可，余下的方法都可以基于此进行调用：\n\n```js\nnew Square().x(100).y(100).size(50).outline(\"red\").fill(\"blue\").draw();\n```\nNote that this is a keyword, not a variable or property name. JavaScript syntax does not allow you to assign a value to this.\n\n> 需要注意的是，this 是一个关键字，不是变量，也不是属性名。JavaScript 的语法不允许给 this 赋值。\n\nThe this keyword is not scoped the way variables are, and, except for arrow functions, nested functions do not inherit the this value of the containing function. If a nested function is invoked as a method, its this value is the object it was invoked on. If a nested function (that is not an arrow function) is invoked as a function, then its this value will be either the global object (non-strict mode) or undefined (strict mode). It is a common mistake to assume that a nested function defined within a method and invoked as a function can use this to obtain the invocation context of the method. The following code demonstrates the problem:\n\n> 关键字 this 没有变量作用域的限制，除了箭头函数，嵌套函数不会从包含它的函数中继承 this。如果嵌套函数作为方法调用，其 this 的值指向调用它的对象。如果嵌套的函数作为函数调用（不包含箭头函数），其 this 值不是全局对象（非严格模式下）就是 undefined（严格模式下）。很多人误以为在一个方法中的函数声明并以函数调用的方式去执行可以用 this 来获取方法的执行上下文。下面这个例子说明了这个问题：\n\n```js\nlet o = {                 // An object o.\n    m: function() {       // Method m of the object.\n        let self = this;  // Save the \"this\" value in a variable.\n        this === o        // => true: \"this\" is the object o.\n        f();              // Now call the helper function f().\n\n        function f() {    // A nested function f\n            this === o    // => false: \"this\" is global or undefined\n            self === o    // => true: self is the outer \"this\" value.\n        }\n    }\n};\no.m();                    // Invoke the method m on the object o.\n```\nInside the nested function f(), the this keyword is not equal to the object o. This is widely considered to be a flaw in the JavaScript language, and it is important to be aware of it. The code above demonstrates one common workaround. Within the method m, we assign the this value to a variable self, and within the nested function f, we can use self instead of this to refer to the containing object.\n\n> 嵌套函数 f() 中，this 关键之不等于对象 o。这被广泛的认为是 JavaScript 的一个缺陷，了解这一点是很是很重要的。上面的代码演示了一种常用的解决方案。在方法 m 中，将 this 值赋值给一个变量 self，在签到函数 f 中，可以用 self 代替 this 来引用包含它的对象。\n\nIn ES6 and later, another workaround to this issue is to convert the nested function f into an arrow function, which will properly inherit the this value:\n\n> 在 ES6 之后，有另外一种解决方案来解决这个问题，将嵌套函数 f 转换成箭头函数，它会正确的继承 this 值：\n\n```js\nconst f = () => {\n    this === o  // true, since arrow functions inherit this\n};\n```\nFunctions defined as expressions instead of statements are not hoisted, so in order to make this code work, the function definition for f will need to be moved within the method m so that it appears before it is invoked.\n\n> 函数表达式不像函数声明，声明提前，所以为了让这种解决方案定义的函数可以被调用，需要将 f 函数定义表达式放在方法 m 中，这样它才可以在它被调用时存在。\n\nAnother workaround is to invoke the bind() method of the nested function to define a new function that is implicitly invoked on a specified object:\n\n> 另外还可以用嵌套函数的 bind() 方法，在指定对象上隐式调用一个新的函数：\n\n```js\nconst f = (function() {\n    this === o  // true, since we bound this function to the outer this\n}).bind(this);\n```\nWe’ll talk more about bind() in §8.7.5.\n\n> 关于 bind() 方法将在 §8.7.5 中讲解。\n\n### 8.2.3 Constructor Invocation\nIf a function or method invocation is preceded by the keyword new, then it is a constructor invocation. (Constructor invocations were introduced in §4.6 and §6.2.2, and constructors will be covered in more detail in Chapter 9.) Constructor invocations differ from regular function and method invocations in their handling of arguments, invocation context, and return value.\n\n> 如果函数或者方法调用之前带有关键字 new，它就构成构造函数调用（构造函数调用在 §4.6 和 §6.2.2 节有简单介绍，第 9 章会对构造函数做更详细的讨论）。构造函数调用和普通的函数调用以及方法调用在实参处理、调用上下文和返回值方面都有不同。\n\nIf a constructor invocation includes an argument list in parentheses, those argument expressions are evaluated and passed to the function in the same way they would be for function and method invocations. It is not common practice, but you can omit a pair of empty parentheses in a constructor invocation. The following two lines, for example, are equivalent:\n\n> 如果构造函数调用在圆括号内包含一组实参列表，先计算这些实参表达式，然后传入函数内，这和函数调用和方法调用是一致的。但如果构造函数没有形参，JavaScript 构造函数调用的语法是允许省略实参列表和圆括号的。凡是没有形参的构造函数调用都可以省略圆括号，比如，下面这两行代码就是等价的：\n\n```js\no = new Object();\no = new Object;\n```\nA constructor invocation creates a new, empty object that inherits from the object specified by the prototype property of the constructor. Constructor functions are intended to initialize objects, and this newly created object is used as the invocation context, so the constructor function can refer to it with the this keyword. Note that the new object is used as the invocation context even if the constructor invocation looks like a method invocation. That is, in the expression new o.m(), o is not used as the invocation context.\n\n> 构造函数调用创建一个新的空对象，这个对象继承自构造函数的 prototype 属性。构造函数试图初始化这个新创建的对象，并将这个对象用做其调用上下文，因此构造函数内可以使用 this 关键字来引用这个新创建的对象。注意，尽管构造函数看起来像一个方法调用，它依然会使用这个新对象作为调用上下文。也就是说，在表达式 new o.m() 中，调用上下文并不是 o。\n\nConstructor functions do not normally use the return keyword. They typically initialize the new object and then return implicitly when they reach the end of their body. In this case, the new object is the value of the constructor invocation expression. If, however, a constructor explicitly uses the return statement to return an object, then that object becomes the value of the invocation expression. If the constructor uses return with no value, or if it returns a primitive value, that return value is ignored and the new object is used as the value of the invocation.\n\n> 构造函数通常不使用 return 关键字，它们通常初始化新对象，当构造函数的函数体执行完毕时，它会隐式返回。在这种情况下，构造函数调用表达式的计算结果就是这个新对象的值。然而如果构造函数显式地使用 return 语句返回一个对象，那么调用表达式的值就是这个对象。如果构造函数使用 return 语句但没有指定返回值，或者返回一个原始值，那么这时将忽略返回值，同时使用这个新对象作为调用结果。\n\n### 8.2.4 Indirect Invocation\nJavaScript functions are objects, and like all JavaScript objects, they have methods. Two of these methods, call() and apply(), invoke the function indirectly. Both methods allow you to explicitly specify the this value for the invocation, which means you can invoke any function as a method of any object, even if it is not actually a method of that object. Both methods also allow you to specify the arguments for the invocation. The call() method uses its own argument list as arguments to the function, and the apply() method expects an array of values to be used as arguments. The call() and apply() methods are described in detail in §8.7.4.\n\n> JavaScript 中的函数也是对象，和其他 JavaScript 对象没什么两样，函数对象也可以包含方法。其中的两个方法 call() 和 apply() 可以用来间接地调用函数。两个方法都允许显式指定调用所需的 this 值，也就是说，任何函数可以作为任何对象的方法来调用，哪怕这个函数不是那个对象的方法。两个方法都可以指定调用的实参。call() 方法使用它自有的实参列表作为函数的实参，apply() 方法则要求以数组的形式传入实参。§8.7.4 节会有关于 call() 和apply () 方法的详细讨论。\n\n### 8.2.5 Implicit Function Invocation\nThere are various JavaScript language features that do not look like function invocations but that cause functions to be invoked. Be extra careful when writing functions that may be implicitly invoked, because bugs, side effects, and performance issues in these functions are harder to diagnose and fix than in regular functions for the simple reason that it may not be obvious from a simple inspection of your code when they are being called.\n\n> 有各种各样的 JavaScript 语言特性，它们看起来不像函数调用但是却能调用函数。额外小心编写函数时可能会隐式调用，因为在隐式函数调用中 bug、副作用和性能问题都比普通的函数更难诊断和修复。\n\nThe language features that can cause implicit function invocation include:\n\n> 可能引起函数隐式调用的语言特性包括：\n\nIf an object has getters or setters defined, then querying or setting the value of its properties may invoke those methods. See §6.10.6 for more information.\n\n> 如果一个对象定义了 getter 或者 setter 方法，获取或者设置它的属性值可能调用这些方法。见 §6.10.6 有更多相关描述。\n\nWhen an object is used in a string context (such as when it is concatenated with a string), its toString() method is called. Similarly, when an object is used in a numeric context, its valueOf() method is invoked. See §3.9.3 for details.\n\n> 当对象用作一个字符串文本时（例如对象和一个字符串连接），它的 toString() 方法会被调用。同样的，对象用作一个数值型文本时，它的 valueOf() 方法被调用。详见 §3.9.3。\n\nWhen you loop over the elements of an iterable object, there are a number of method calls that occur. Chapter 12 explains how iterators work at the function call level and demonstrates how to write these methods so that you can define your own iterable types.\n\n> 当循环可迭代对象的元素时会产生很多方法调用。第 12 章介绍了迭代器在函数调用级别如何工作，并演示如何编写方法来定义自己的可迭代类型。\n\nA tagged template literal is a function invocation in disguise. §14.5 demonstrates how to write functions that can be used in conjunction with template literal strings.\n\n> 可以伪装在模板字面量中 。在 §14.5 中演示如何在模板字符串中调用函数。\n\nProxy objects (described in §14.7) have their behavior completely controlled by functions. Just about any operation on one of these objects will cause a function to be invoked.\n\n> Proxy 对象（在 §14.7 中描述）的行为完全由函数控制。它的任何一个操作都会导致函数调用。\n\n## 8.3 Function Arguments and Parameters\nJavaScript function definitions do not specify an expected type for the function parameters, and function invocations do not do any type checking on the argument values you pass. In fact, JavaScript function invocations do not even check the number of arguments being passed. The subsections that follow describe what happens when a function is invoked with fewer arguments than declared parameters or with more arguments than declared parameters. They also demonstrate how you can explicitly test the type of function arguments if you need to ensure that a function is not invoked with inappropriate arguments.\n\n> JavaScript 中的函数定义并未指定函数形参的类型，函数调用也未对传入的实参值做任何类型检查。实际上，JavaScript 函数调用甚至不检查传入形参的个数。下面几节将会讨论当调用函数时的实参个数和声明的形参个数不匹配时出现的状况，同样介绍了如何显式测试函数实参的类型，以避免非法的实参传入函数。\n\n### 8.3.1 Optional Parameters and Defaults\nWhen a function is invoked with fewer arguments than declared parameters, the additional parameters are set to their default value, which is normally undefined. It is often useful to write functions so that some arguments are optional. Following is an example:\n\n> 当调用函数的时候传入的实参比函数声明时指定的形参个数要少，剩下的形参都将设置为 undefined 值。所以一些参数设置成可选的是非常实用的。看下面这个例子：\n\n```js\n// Append the names of the enumerable properties of object o to the\n// array a, and return a.  If a is omitted, create and return a new array.\nfunction getPropertyNames(o, a) {\n    if (a === undefined) a = [];  // If undefined, use a new array\n    for(let property in o) a.push(property);\n    return a;\n}\n\n// getPropertyNames() can be invoked with one or two arguments:\nlet o = {x: 1}, p = {y: 2, z: 3};  // Two objects for testing\nlet a = getPropertyNames(o); // a == [\"x\"]; get o's properties in a new array\ngetPropertyNames(p, a);      // a == [\"x\",\"y\",\"z\"]; add p's properties to it\n```\nInstead of using an if statement in the first line of this function, you can use the || operator in this idiomatic way:\n\n> 第一行代码中可以使用 || 运算符来代替一个 if 语句，这是一种习惯用法：\n\n```js\na = a || [];\n```\nRecall from §4.10.2 that the || operator returns its first argument if that argument is truthy and otherwise returns its second argument. In this case, if any object is passed as the second argument, the function will use that object. But if the second argument is omitted (or null or another falsy value is passed), a newly created empty array will be used instead.\n\n> 回忆一下，§4.10.2 介绍了“||”运算符，如果第一个实参是真值的话就返回第一个实参；否则返回第二个实参。在这个场景下，如果作为第二个实参传入任意对象，那么函数就会使用这个对象。如果省略掉第二个实参（或者传递 null 以及其他任何假值），那么就新创建一个空数组，并赋值给 a。\n\nNote that when designing functions with optional arguments, you should be sure to put the optional ones at the end of the argument list so that they can be omitted. The programmer who calls your function cannot omit the first argument and pass the second: they would have to explicitly pass undefined as the first argument.\n\n> 需要注意的是，当用这种可选实参来实现函数时，需要将可选实参放在实参列表的最后。那些调用你的函数的程序员是没办法省略第一个实参并传入第二个实参，他们必须显地的将 undefined 传入作为第一个实参 。\n\nIn ES6 and later, you can define a default value for each of your function parameters directly in the parameter list of your function. Simply follow the parameter name with an equals sign and the default value to use when no argument is supplied for that parameter:\n\n> 在 ES6 之后，可以直接在函数的参数列表中为每个函数参数定义默认值。直接在参数名后面接一个等号再接一个默认值（用于没有实参提供给参数时参数的值）：\n\n```js\n// Append the names of the enumerable properties of object o to the\n// array a, and return a.  If a is omitted, create and return a new array.\nfunction getPropertyNames(o, a = []) {\n    for(let property in o) a.push(property);\n    return a;\n}\n```\nParameter default expressions are evaluated when your function is called, not when it is defined, so each time this getPropertyNames() function is invoked with one argument, a new empty array is created and passed.2 It is probably easiest to reason about functions if the parameter defaults are constants (or literal expressions like [] and {}). But this is not required: you can use variables, or function invocations, for example, to compute the default value of a parameter. One interesting case is that, for functions with multiple parameters, you can use the value of a previous parameter to define the default value of the parameters that follow it:\n\n> 默认参数表达式只有在函数调用时进行计算，而不是在它定义时，所以每一次 getPropertyNames() 函数只传一个实参调用时，一个新的空数组被创建并传给参数。2 最容易理解的就是参数默认值是常量（或者字面量表达式 [] 和 {}）。但这并不是必须的：举个例子，你可以用变量或者函数调用，计算一个默认参数的值。一个很有趣的情况是，对于有多个参数的函数，可以用前面的参数值来定义后面的参数默认值。\n\n```js\n// This function returns an object representing a rectangle's dimensions.\n// If only width is supplied, make it twice as high as it is wide.\nconst rectangle = (width, height=width*2) => ({width, height});\nrectangle(1)  // => { width: 1, height: 2 }\n```\nThis code demonstrates that parameter defaults work with arrow functions. The same is true for method shorthand functions and all other forms of function definitions.\n\n> 这段代码描述了箭头函数中的参数默认值。方法函数和其他形式的函数定义也是如此。\n\n### 8.3.2 Rest Parameters and Variable-Length Argument Lists\nParameter defaults enable us to write functions that can be invoked with fewer arguments than parameters. Rest parameters enable the opposite case: they allow us to write functions that can be invoked with arbitrarily more arguments than parameters. Here is an example function that expects one or more numeric arguments and returns the largest one:\n\n> 调用函数时允许传入的实参比函数声明时指定的形参个数少。剩余参数允许相反的情况：它允许我们在调用函数时，传入比型参多任意个数的实参。下面是一个可以传入一个或多个数值型实参的例子，并且返回其中最大的数：\n\n```js\nfunction max(first=-Infinity, ...rest) {\n    let maxValue = first; // Start by assuming the first arg is biggest\n    // Then loop through the rest of the arguments, looking for bigger\n    for(let n of rest) {\n        if (n > maxValue) {\n            maxValue = n;\n        }\n    }\n    // Return the biggest\n    return maxValue;\n}\n\nmax(1, 10, 100, 2, 3, 1000, 4, 5, 6)  // => 1000\n```\nA rest parameter is preceded by three periods, and it must be the last parameter in a function declaration. When you invoke a function with a rest parameter, the arguments you pass are first assigned to the non-rest parameters, and then any remaining arguments (i.e., the “rest” of the arguments) are stored in an array that becomes the value of the rest parameter. This last point is important: within the body of a function, the value of a rest parameter will always be an array. The array may be empty, but a rest parameter will never be undefined. (It follows from this that it is never useful—and not legal—to define a parameter default for a rest parameter.)\n\n> 剩余参数由三个 . 开始，必须是函数声明的最后一个参数。调用有剩余参数的函数时，传递的实参先赋值给非剩余参数，然后其余所有的实参（也就是“剩余”实参）存储在一个数组中变成剩余参数的值，最后一点非常重要：在一个函数体中，剩余参数的值总是一个数组。这个数组可能是空的，但是剩余参数永远不会是 undefined。（因此，从不会给剩余参数设置默认值，并且这也是不合法的。）\n\nFunctions like the previous example that can accept any number of arguments are called variadic functions, variable arity functions, or vararg functions. This book uses the most colloquial term, varargs, which dates to the early days of the C programming language.\n\n> 类似这种函数可以接收任意个数的实参，这种函数也称为“不定实参函数”，这个术语源自古老的C语言。\n\nDon’t confuse the ... that defines a rest parameter in a function definition with the ... spread operator, described in §8.3.4, which can be used in function invocations.\n\n> 不要混淆 ... 定义函数的剩余参数和 ... 展开运算符，将在 §8.3.4 描述展开运算符在函数调用中的应用。\n\n### 8.3.3 The Arguments Object\nRest parameters were introduced into JavaScript in ES6. Before that version of the language, varargs functions were written using the Arguments object: within the body of any function, the identifier arguments refers to the Arguments object for that invocation. The Arguments object is an array-like object (see §7.9) that allows the argument values passed to the function to be retrieved by number, rather than by name. Here is the max() function from earlier, rewritten to use the Arguments object instead of a rest parameter:\n\n> 剩余参数是在 ES6 中加入的概念。在这之前，不定实参函数是用 Arguments 对象实现的：在函数体中，标识符 Arguments 是指向实参对象的引用。Arguments 对象是一个类数组对象（参照 §7.9），这样可以通过数字下标就能访问传入函数的实参值，而不用非要通过名字来得到实参。下面的 max() 函数就是以前用 Arguments 对象代替剩余参数的例子：\n\n```js\nfunction max(x) {\n    let maxValue = -Infinity;\n    // Loop through the arguments, looking for, and remembering, the biggest.\n    for(let i = 0; i < arguments.length; i++) {\n        if (arguments[i] > maxValue) maxValue = arguments[i];\n    }\n    // Return the biggest\n    return maxValue;\n}\n\nmax(1, 10, 100, 2, 3, 1000, 4, 5, 6)  // => 1000\n```\nThe Arguments object dates back to the earliest days of JavaScript and carries with it some strange historical baggage that makes it inefficient and hard to optimize, especially outside of strict mode. You may still encounter code that uses the Arguments object, but you should avoid using it in any new code you write. When refactoring old code, if you encounter a function that uses arguments, you can often replace it with a ...args rest parameter. Part of the unfortunate legacy of the Arguments object is that, in strict mode, arguments is treated as a reserved word, and you cannot declare a function parameter or a local variable with that name.\n\n> Arguments 对象可追溯到 JavaScript 的最早时代，并带有一些奇怪的历史包袱，这使得它效率低下且难以优化，尤其是不在严格模式下。可能还会遇到一些代码使用 Arguments 对象，但是在编写新代码时要避免使用，可以用 ... 剩余函数来替代。Arguments 对象还有部分令人遗憾的遗产，在严格模式下，arguments 被视为保留字，不能声明具有该名称的局部变量来定义函数的参数。\n\n### 8.3.4 The Spread Operator for Function Calls\nThe spread operator ... is used to unpack, or “spread out,” the elements of an array (or any other iterable object, such as strings) in a context where individual values are expected. We’ve seen the spread operator used with array literals in §7.1.2. The operator can be used, in the same way, in function invocations:\n\n> 当需要单个值时，... 展开运算符用来拆包，或者说将元素从数组（或者其他的任何和迭代对象，例如字符串）中“展开”到上下文。我们已经在 §7.1.2 见到了展开运算符在数组字面量上的使用。展开运算符可以在函数调用中以同样方式使用：\n\n```js\nlet numbers = [5, 2, 10, -1, 9, 100, 1];\nMath.min(...numbers)  // => -1\n```\nNote that ... is not a true operator in the sense that it cannot be evaluated to produce a value. Instead, it is a special JavaScript syntax that can be used in array literals and function invocations.\n\n> 注意 ... 不是一个真正的运算符，因为它不能通过计算来提供一个值。它是一个可以用在数组字面量和函数调用中的特殊的 JavaScript 语法。\n\nWhen we use the same ... syntax in a function definition rather than a function invocation, it has the opposite effect to the spread operator. As we saw in §8.3.2, using ... in a function definition gathers multiple function arguments into an array. Rest parameters and the spread operator are often useful together, as in the following function, which takes a function argument and returns an instrumented version of the function for testing: \n\n> 在函数定义和函数调用中使用相同的 ... 语法时，和展开运算符有着相仿的效果。在 §8.3.2 中我们看到函数定义使用 ... 将复数个函数实参合并到一个数组中。剩余参数和展开运算符经常一同使用，就像下面这个函数：\n\n```js\n// This function takes a function and returns a wrapped version\nfunction timed(f) {\n    return function(...args) {  // Collect args into a rest parameter array\n        console.log(`Entering function ${f.name}`);\n        let startTime = Date.now();\n        try {\n            // Pass all of our arguments to the wrapped function\n            return f(...args);  // Spread the args back out again\n        }\n        finally {\n            // Before we return the wrapped return value, print elapsed time.\n            console.log(`Exiting ${f.name} after ${Date.now()-startTime}ms`);\n        }\n    };\n}\n\n// Compute the sum of the numbers between 1 and n by brute force\nfunction benchmark(n) {\n    let sum = 0;\n    for(let i = 1; i <= n; i++) sum += i;\n    return sum;\n}\n\n// Now invoke the timed version of that test function\ntimed(benchmark)(1000000) // => 500000500000; this is the sum of the numbers\n```\n### 8.3.5 Destructuring Function Arguments into Parameters\nWhen you invoke a function with a list of argument values, those values end up being assigned to the parameters declared in the function definition. This initial phase of function invocation is a lot like variable assignment. So it should not be surprising that we can use the techniques of destructuring assignment (see §3.10.3) with functions.\n\n> 用实参列表调用函数时，实参的值最终赋值给函数定义的参数。函数调用初始化阶段非常像变量赋值。所以我们不必惊讶于可以将解构赋值（见 §3.10.3）用于函数。\n\nIf you define a function that has parameter names within square brackets, you are telling the function to expect an array value to be passed for each pair of square brackets. As part of the invocation process, the array arguments will be unpacked into the individually named parameters. As an example, suppose we are representing 2D vectors as arrays of two numbers, where the first element is the X coordinate and the second element is the Y coordinate. With this simple data structure, we could write the following function to add two vectors:\n\n> 如果一个函数的参数带有方括号，就说明函数要给每一个方括号传一个数组。在一个调用进程中，数组实参会被拆包传递给对应的参数。例如，假设我们将 2D 矢量表示为两个数字的数组，其中第一个元素是 X 坐标，第二个元素是 Y 坐标。用这个简单的数据结构，编写下面这个函数计算两个矢量的和：\n\n```js\nfunction vectorAdd(v1, v2) {\n    return [v1[0] + v2[0], v1[1] + v2[1]];\n}\nvectorAdd([1,2], [3,4])  // => [4,6]\n```\nThe code would be easier to understand if we destructured the two vector arguments into more clearly named parameters:\n\n> 如果下面这种方式解构这两个矢量实参，这段代码将更容易理解：\n\n```js\nfunction vectorAdd([x1,y1], [x2,y2]) { // Unpack 2 arguments into 4 parameters\n    return [x1 + x2, y1 + y2];\n}\nvectorAdd([1,2], [3,4])  // => [4,6]\n```\nSimilarly, if you are defining a function that expects an object argument, you can destructure parameters of that object. Let’s use a vector example again, except this time, let’s suppose that we represent vectors as objects with x and y parameters:\n\n> 同样，如果定义一个函数时需要对象实参，你能对这个对象进行参数解构。再次实用矢量的例子，这一次，我们用 x 和 y 参数包装成对象来描述矢量：\n\n```js\n// Multiply the vector {x,y} by a scalar value\nfunction vectorMultiply({x, y}, scalar) {\n    return { x: x*scalar, y: y*scalar };\n}\nvectorMultiply({x: 1, y: 2}, 2)  // => {x: 2, y: 4}\n```\nThis example of destructuring a single object argument into two parameters is a fairly clear one because the parameter names we use match the property names of the incoming object. The syntax is more verbose and more confusing when you need to destructure properties with one name into parameters with different names. Here’s the vector addition example, implemented for object-based vectors:\n\n> 这个例子将一个简单的对象实参解构成两个参数是很简单的，因为参数的名字和我们在对象中使用的属性名是匹配的。当您需要将同一个名称的属性解构为具有不同名称的参数时，语法更加冗长和难懂。下面是一个矢量加法的例子，基于对象矢量的实现：\n\n```js\nfunction vectorAdd(\n    {x: x1, y: y1}, // Unpack 1st object into x1 and y1 params\n    {x: x2, y: y2}  // Unpack 2nd object into x2 and y2 params\n)\n{\n    return { x: x1 + x2, y: y1 + y2 };\n}\nvectorAdd({x: 1, y: 2}, {x: 3, y: 4})  // => {x: 4, y: 6}\n```\nThe tricky thing about destructuring syntax like {x:x1, y:y1} is remembering which are the property names and which are the parameter names. The rule to keep in mind for destructuring assignment and destructuring function calls is that the variables or parameters being declared go in the spots where you’d expect values to go in an object literal. So property names are always on the lefthand side of the colon, and the parameter (or variable) names are on the right.\n\n> 像 {x:x1, y:y1} 的解构语法棘手的是记住哪一个是属性名哪一个是参数名。牢记解构赋值和解构函数调用的规则，声明的变量或参数在对象字面量中的位置固定。属性名总是在冒号的左边，参数（或变量）名在右边。\n\nYou can define parameter defaults with destructured parameters. Here’s vector multiplication that works with 2D or 3D vectors:\n\n> 可以使用解构参数定义参数默认值。下面是适用于 2D 或 3D 矢量的矢量乘法：\n\n```js\n// Multiply the vector {x,y} or {x,y,z} by a scalar value\nfunction vectorMultiply({x, y, z=0}, scalar) {\n    return { x: x*scalar, y: y*scalar, z: z*scalar };\n}\nvectorMultiply({x: 1, y: 2}, 2)  // => {x: 2, y: 4, z: 0}\n```\nSome languages (like Python) allow the caller of a function to invoke a function with arguments specified in name=value form, which is convenient when there are many optional arguments or when the parameter list is long enough that it is hard to remember the correct order. JavaScript does not allow this directly, but you can approximate it by destructuring an object argument into your function parameters. Consider a function that copies a specified number of elements from one array into another array with optionally specified starting offsets for each array. Since there are five possible parameters, some of which have defaults, and it would be hard for a caller to remember which order to pass the arguments in, we can define and invoke the arraycopy() function like this:\n\n> 一些语言（像 Python）允许函数的调用者以 name=value 型式指定实参，这在有很多可选实参或者参数列表长到难以记住正确的顺序时是非常方便的。JavaScript 不允许直接这样做，但可以通过解构对象实参到函数参数中。构思一个函数将指定数量的元素从一个数组复制到另一个数组中，可以随意地为每个数组指定起始偏移量。如下有五个可传入参数，其中一些有默认值，并且调用者很难记住参数的顺序来传递实参，可以像这样定义和调用 arraycopy() 方法：\n\n```js\nfunction arraycopy({from, to=from, n=from.length, fromIndex=0, toIndex=0}) {\n    let valuesToCopy = from.slice(fromIndex, fromIndex + n);\n    to.splice(toIndex, 0, ...valuesToCopy);\n    return to;\n}\nlet a = [1,2,3,4,5], b = [9,8,7,6,5];\narraycopy({from: a, n: 3, to: b, toIndex: 4}) // => [9,8,7,6,1,2,3,5]\n```\nWhen you destructure an array, you can define a rest parameter for extra values within the array that is being unpacked. That rest parameter within the square brackets is completely different than the true rest parameter for the function:\n\n> 当解构一个数组，在其被拆包时，可以定义一个剩余参数将其余值放在数组中。 在方括号中的剩余参数和真正的函数中的剩余参数是完全不同的：\n\n```js\n// This function expects an array argument. The first two elements of that\n// array are unpacked into the x and y parameters. Any remaining elements\n// are stored in the coords array. And any arguments after the first array\n// are packed into the rest array.\nfunction f([x, y, ...coords], ...rest) {\n    return [x+y, ...rest, ...coords];  // Note: spread operator here\n}\nf([1, 2, 3, 4], 5, 6)   // => [3, 5, 6, 3, 4]\n```\nIn ES2018, you can also use a rest parameter when you destructure an object. The value of that rest parameter will be an object that has any properties that did not get destructured. Object rest parameters are often useful with the object spread operator, which is also a new feature of ES2018:\n\n> 在 ES2018，也可以用剩余参数解构对象。剩余参数是一个没有解构的属性的对象。对象剩余参数经常与对象展开运算符连用，这是 ES2018 的新特性：\n\n```js\n// Multiply the vector {x,y} or {x,y,z} by a scalar value, retain other props\nfunction vectorMultiply({x, y, z=0, ...props}, scalar) {\n    return { x: x*scalar, y: y*scalar, z: z*scalar, ...props };\n}\nvectorMultiply({x: 1, y: 2, w: -1}, 2)  // => {x: 2, y: 4, z: 0, w: -1}\n```\nFinally, keep in mind that, in addition to destructuring argument objects and arrays, you can also destructure arrays of objects, objects that have array properties, and objects that have object properties, to essentially any depth. Consider graphics code that represents circles as objects with x, y, radius, and color properties, where the color property is an array of red, green, and blue color components. You might define a function that expects a single circle object to be passed to it but destructures that circle object into six separate parameters:\n\n> 最后，请记住，除了可以解构实参对象和数组，也可以解构数组对象，对象有数组属性，并且对象还有对象的属性。构思一个将圆表示为具有 x、y、半径和颜色属性的对象的图形代码，颜色属性是一个数组由 RGB 组成。你可以定义一个函数，该函数希望将单个圆对象传递给它，但其解构为六个单独的参数：\n\n```js\nfunction drawCircle({x, y, radius, color: [r, g, b]}) {\n    // Not yet implemented\n}\n```\nIf function argument destructuring is any more complicated than this, I find that the code becomes harder to read, rather than simpler. Sometimes, it is clearer to be explicit about your object property access and array indexing.\n\n> 如果函数实参解构比这更复杂，代码会变得更难读，而不是更简单。有时，显示地对对象属性访问和数组索引会让代码更清晰。\n\n### 8.3.6 Argument Types\nJavaScript method parameters have no declared types, and no type checking is performed on the values you pass to a function. You can help make your code self-documenting by choosing descriptive names for function arguments and by documenting them carefully in the comments for each function. (Alternatively, see §17.8 for a language extension that allows you to layer type checking on top of regular JavaScript.)\n\n> JavaScript 方法的形参并未声明类型，在形参传入函数体之前也未做任何类型检查。可以采用语义化的单词来给函数实参命名，并在函数注释给每一个实参详细描述，以此使代码自文本化。\n\nAs described in §3.9, JavaScript performs liberal type conversion as needed. So if you write a function that expects a string argument and then call that function with a value of some other type, the value you passed will simply be converted to a string when the function tries to use it as a string. All primitive types can be converted to strings, and all objects have toString() methods (if not necessarily useful ones), so an error never occurs in this case.\n\n> §3.9 已经提到，JavaScript 在必要时会进行类型转换。因此如果函数期 望接收一个字符串实参，而调用函数时传入其他类型的值，所传入的值会在函数体内将其用做字符串的地方转换为字符串类型。所有的原始类型都可以转换为字符串，所有的对象都包含 toString() 方法（尽管不一定有用），所以这种场景下是不会有任何错误的。\n\nThis is not always true, however. Consider again the arraycopy() method shown earlier. It expects one or two array arguments and will fail if these arguments are of the wrong type. Unless you are writing a private function that will only be called from nearby parts of your code, it may be worth adding code to check the types of arguments like this. It is better for a function to fail immediately and predictably when passed bad values than to begin executing and fail later with an error message that is likely to be unclear. Here is an example function that performs type-checking:\n\n> 然而事情不总是这样，回头看一下刚才提到的 arraycopy() 方法。这个方法期望获得一个或两个实参，并且这些实参的类型错误会导致函数执行失败。除非所写的私有函数只会被附近的代码调用，你应当添加类似的实参类型检查逻辑。因为宁愿程序在传入非法值时报错，也不愿非法值导致程序在执行时报错，相比而言，逻辑执行时的报错消息不甚清晰且更难处理。下面这个例子中的函数就做了这种类型检查：\n\n```js\n// Return the sum of the elements an iterable object a.\n// The elements of a must all be numbers.\nfunction sum(a) {\n    let total = 0;\n    for(let element of a) { // Throws TypeError if a is not iterable\n        if (typeof element !== \"number\") {\n            throw new TypeError(\"sum(): elements must be numbers\");\n        }\n        total += element;\n    }\n    return total;\n}\nsum([1,2,3])    // => 6\nsum(1, 2, 3);   // !TypeError: 1 is not iterable\nsum([1,2,\"3\"]); // !TypeError: element 2 is not a number\n```\n## 8.4 Functions as Values\nThe most important features of functions are that they can be defined and invoked. Function definition and invocation are syntactic features of JavaScript and of most other programming languages. In JavaScript, however, functions are not only syntax but also values, which means they can be assigned to variables, stored in the properties of objects or the elements of arrays, passed as arguments to functions, and so on.3\n\n> 函数可以定义，也可以调用，这是函数最重要的特性。函数定义和调用是  JavaScript 的词法特性，对于其他大多数编程语言来说亦是如此。然而在 JavaScript 中，函数不仅是一种语法，也是值，也就是说，可以将函数赋值给变量，存储在对象的属性或数组的元素中，作为参数传入另外一个函数等。3\n\nTo understand how functions can be JavaScript data as well as JavaScript syntax, consider this function definition:\n\n> 为了便于理解 JavaScript 中的函数是如何用做 Javascript 数据以及 JavaScript 语法的，来看一下这样一个函数定义：\n\n```js\nfunction square(x) { return x*x; }\n```\nThis definition creates a new function object and assigns it to the variable square. The name of a function is really immaterial; it is simply the name of a variable that refers to the function object. The function can be assigned to another variable and still work the same way:\n\n> 这个定义创建一个新的函数对象，并将其赋值给变量 square。函数的名字实际上是无形的，它（square）仅仅是变量的名称，这个变量是函数对象的引用。函数还可以赋值给其他的变量，并且仍可以正常工作：\n\n```js\nlet s = square;  // Now s refers to the same function that square does\nsquare(4)        // => 16\ns(4)             // => 16\n```\nFunctions can also be assigned to object properties rather than variables. As we’ve already discussed, we call the functions “methods” when we do this:\n\n> 除了可以将函数赋值给变量，同样可以将函数赋值给对象的属性。当函数作为对象的属性调用时，函数就称为“方法”：\n\n```js\nlet o = {square: function(x) { return x*x; }}; // An object literal\nlet y = o.square(16);                          // y == 256\n```\nFunctions don’t even require names at all, as when they’re assigned to array elements:\n\n> 函数甚至不需要带名字，就像把它们赋值给数组元素：\n\n```js\nlet a = [x => x*x, 20]; // An array literal\na[0](a[1])              // => 400\n```\nThe syntax of this last example looks strange, but it is still a legal function invocation expression!\n\n> 上面的例子看起来很奇怪，但的确是合法的函数调用表达式！\n\nAs an example of how useful it is to treat functions as values, consider the Array.sort() method. This method sorts the elements of an array. Because there are many possible orders to sort by (numerical order, alphabetical order, date order, ascending, descending, and so on), the sort() method optionally takes a function as an argument to tell it how to perform the sort. This function has a simple job: for any two values it is passed, it returns a value that specifies which element would come first in a sorted array. This function argument makes Array.sort() perfectly general and infinitely flexible; it can sort any type of data into any conceivable order. Examples are shown in §7.8.6.\n\n> 举一个例子来说明将函数当作值来对待的益处，考虑下 Array.sort() 方法。这个方法用来对数组元素进行排序。因为排序的规则有很多（基于数值大小、字母表顺序、日期大小、从小到大、从大到小等），sort() 方法可以接收一个函数作为参数，用来处理具体的排序操作。这个函数的作用非常简单：对于任意两个值都返回一个值，以指定它们在排序后的数组中的先后顺序。这个函数参数使得 Array.sort() 具有更完美的通用性和无限可扩展性，它可以对任何类型的数据进行任意排序。§7.8.6 有示例代码。\n\nExample 8-1 demonstrates the kinds of things that can be done when functions are used as values. This example may be a little tricky, but the comments explain what is going on.\n\n> 示例 8-1 展示了将函数用做值时的一些例子，这段代码可能会难读一些，但注释解释了代码的具体含义：\n\nExample 8-1. Using functions as data\n\n> 示例 8-1：用函数做值\n\n```js\n// We define some simple functions here\nfunction add(x,y) { return x + y; }\nfunction subtract(x,y) { return x - y; }\nfunction multiply(x,y) { return x * y; }\nfunction divide(x,y) { return x / y; }\n\n// Here's a function that takes one of the preceding functions\n// as an argument and invokes it on two operands\nfunction operate(operator, operand1, operand2) {\n    return operator(operand1, operand2);\n}\n\n// We could invoke this function like this to compute the value (2+3) + (4*5):\nlet i = operate(add, operate(add, 2, 3), operate(multiply, 4, 5));\n\n// For the sake of the example, we implement the simple functions again,\n// this time within an object literal;\nconst operators = {\n    add:      (x,y) => x+y,\n    subtract: (x,y) => x-y,\n    multiply: (x,y) => x*y,\n    divide:   (x,y) => x/y,\n    pow:      Math.pow  // This works for predefined functions too\n};\n\n// This function takes the name of an operator, looks up that operator\n// in the object, and then invokes it on the supplied operands. Note\n// the syntax used to invoke the operator function.\nfunction operate2(operation, operand1, operand2) {\n    if (typeof operators[operation] === \"function\") {\n        return operators[operation](operand1, operand2);\n    }\n    else throw \"unknown operator\";\n}\n\noperate2(\"add\", \"hello\", operate2(\"add\", \" \", \"world\")) // => \"hello world\"\noperate2(\"pow\", 10, 2)  // => 100\n```\n### 8.4.1 Defining Your Own Function Properties\nFunctions are not primitive values in JavaScript, but a specialized kind of object, which means that functions can have properties. When a function needs a “static” variable whose value persists across invocations, it is often convenient to use a property of the function itself. For example, suppose you want to write a function that returns a unique integer whenever it is invoked. The function must never return the same value twice. In order to manage this, the function needs to keep track of the values it has already returned, and this information must persist across function invocations. You could store this information in a global variable, but that is unnecessary, because the information is used only by the function itself. It is better to store the information in a property of the Function object. Here is an example that returns a unique integer whenever it is called:\n\n> JavaScript 中的函数并不是原始值，而是一种特殊的对象，也就是说，函数可以拥有属性。当函数需要一个“静态”变量来在调用时保持某个值不变，最方便的方式就是给函数定义属性，而不是定义全局变量，显然定义全局变量会让命名空间变得更加杂乱无章。比如，假设你想写一个返回一个唯一整数的函数，不管在哪里调用函数都会返回这个整数。而函数不能两次返回同一个值，为了做到这一点，函数必须能够跟踪它每次返回的值，而且这些值的信息需要在不同的函数调过程中持久化。可以将这些信息存放到全局变量中，但这并不是必需的，因为这个信息仅仅是函数本身用到的。最好将这个信息保存到函数对象的一个属性中，下面这个例子就实现了这样一个函数，每次调用函数都会返回一个唯一的整数：\n\n```js\n// Initialize the counter property of the function object.\n// Function declarations are hoisted so we really can\n// do this assignment before the function declaration.\nuniqueInteger.counter = 0;\n\n// This function returns a different integer each time it is called.\n// It uses a property of itself to remember the next value to be returned.\nfunction uniqueInteger() {\n    return uniqueInteger.counter++;  // Return and increment counter property\n}\nuniqueInteger()  // => 0\nuniqueInteger()  // => 1\n```\nAs another example, consider the following factorial() function that uses properties of itself (treating itself as an array) to cache previously computed results:\n\n> 来看另外一个例子，下面这个函数 factorial() 使用了自身的属性（将自身当做数组来对待）来缓存上一次的计算结果：\n\n```js\n// Compute factorials and cache results as properties of the function itself.\nfunction factorial(n) {\n    if (Number.isInteger(n) && n > 0) {           // Positive integers only\n        if (!(n in factorial)) {                  // If no cached result\n            factorial[n] = n * factorial(n-1);    // Compute and cache it\n        }\n        return factorial[n];                      // Return the cached result\n    } else {\n        return NaN;                               // If input was bad\n    }\n}\nfactorial[1] = 1;  // Initialize the cache to hold this base case.\nfactorial(6)  // => 720\nfactorial[5]  // => 120; the call above caches this value\n```\n## 8.5 Functions as Namespaces\nVariables declared within a function are not visible outside of the function. For this reason, it is sometimes useful to define a function simply to act as a temporary namespace in which you can define variables without cluttering the global namespace.\n\n> 变量声明在函数内对于函数体外是不可见的。因此，有时定义函数作为临时命名空间非常有用，您可以在其中定义变量而不弄乱全局命名空间。\n\nSuppose, for example, you have a chunk of JavaScript code that you want to use in a number of different JavaScript programs (or, for client-side JavaScript, on a number of different web pages). Assume that this code, like most code, defines variables to store the intermediate results of its computation. The problem is that since this chunk of code will be used in many different programs, you don’t know whether the variables it creates will conflict with variables created by the programs that use it. The solution is to put the chunk of code into a function and then invoke the function. This way, variables that would have been global become local to the function:\n\n> 比如，假设你写了一段 JavaScript 模块代码，这段代码将要用在不同的 JavaScript 程序中（对于客户端 JavaScript 来讲通常是用在各种各样的网页中）。和大多数代码一样，假定这段代码定义了一个用以存储中间计算结果的变量。这样问题就来了，当模块代码放到不同的程序中运行时，你无法得知这个变量是否已经创建了，如果已经存在这个变量，那么将会和代码发生冲突。解决办法当然是将代码放入一个函数内，然后调用这个函数。这样全局变量就变成了函数内的局部变量：\n\n```js\nfunction chunkNamespace() {\n    // Chunk of code goes here\n    // Any variables defined in the chunk are local to this function\n    // instead of cluttering up the global namespace.\n}\nchunkNamespace();  // But don't forget to invoke the function!\n```\nThis code defines only a single global variable: the function name chunkNamespace. If defining even a single property is too much, you can define and invoke an anonymous function in a single expression:\n\n> 这段代码仅仅定义了一个单独的全局变量：名为 chunkNamespace 的函数。如果还是太麻烦，可以用一个单独的表达式定义一个匿名函数并调用它：\n\n```js\n(function() {  // chunkNamespace() function rewritten as an unnamed expression.\n    // Chunk of code goes here\n}());          // End the function literal and invoke it now.\n```\nThis technique of defining and invoking a function in a single expression is used frequently enough that it has become idiomatic and has been given the name “immediately invoked function expression.” Note the use of parentheses in the previous code example. The open parenthesis before function is required because without it, the JavaScript interpreter tries to parse the function keyword as a function declaration statement. With the parenthesis, the interpreter correctly recognizes this as a function definition expression. The leading parenthesis also helps human readers recognize when a function is being defined to be immediately invoked instead of defined for later use.\n\n> 这种定义匿名函数并立即在单个表达式中调用它的写法非常常见，并给它起了个名字“匿名调用函数表达式”。注意上面代码的圆括号的用法，function 之前的左圆括号是必需的，因为如果不写这个左圆括号，JavaScript 解释器会试图将关键字 function 解析为函数声明语句。使用圆括号 JavaScript 解释器才会正确地将其解析为函数定义表达式。使用前导括号也有助于人类阅读时区分函数定义是立即执行还是供以后使用。\n\nThis use of functions as namespaces becomes really useful when we define one or more functions inside the namespace function using variables within that namesapce, but then pass them back out as the return value of the namespace function. Functions like this are known as closures, and they’re the topic of the next section.\n\n> 函数用作命名空间很常用，在命名空间函数中定义一个或多个函数使用其中的变量，然后将他们作为函数命名空间的返回值。这样的函数称为闭包，它们是下一节的主题。\n\n## 8.6 Closures\nLike most modern programming languages, JavaScript uses lexical scoping. This means that functions are executed using the variable scope that was in effect when they were defined, not the variable scope that is in effect when they are invoked. In order to implement lexical scoping, the internal state of a JavaScript function object must include not only the code of the function but also a reference to the scope in which the function definition appears. This combination of a function object and a scope (a set of variable bindings) in which the function’s variables are resolved is called a closure in the computer science literature.\n\n> 和其他大多数现代编程语言一样，JavaScript 也采用词法作用域。也就是说，函数的执行依赖于变量作用域，这个作用域是在函数定义时决定的，而不是函数调用时决定的。为了实现这种词法作用域，JavaScript 函数对象的内部状态不仅包含函数的代码逻辑，还必须包括对函数定义出现的作用域的引用。将函数对象可和作用域相互关联起来（一对变量的绑定），函数体内部的变量都可以保存在函数作用域内，这种特性在计算机科学文献中称为闭包。\n\nTechnically, all JavaScript functions are closures, but because most functions are invoked from the same scope that they were defined in, it normally doesn’t really matter that there is a closure involved. Closures become interesting when they are invoked from a different scope than the one they were defined in. This happens most commonly when a nested function object is returned from the function within which it was defined. There are a number of powerful programming techniques that involve this kind of nested function closures, and their use has become relatively common in JavaScript programming. Closures may seem confusing when you first encounter them, but it is important that you understand them well enough to use them comfortably.\n\n> 从技术的角度讲，所有的 JavaScript 函数都是闭包，但是大多数函数调用和定义在同一个作用域内，通常不会注意这里有涉及到闭包。当调用函数不和其定义处于同一作用域内时，事情就变得非常微妙。当一个函数嵌套了另外一个函数，外部函数将嵌套的函数对象作为返回值返回的时候往往会发生这种事情。有很多强大的编程技术都利用到了这类嵌套的函数闭包，以至于这种编程模式在 JavaScript 中非常常见。当你第一次碰到闭包时可能会觉得非常让人费解，一旦你理解掌握了闭包之后，就能非常自如地使用它了，了解这一点至关重要。\n\nThe first step to understanding closures is to review the lexical scoping rules for nested functions. Consider the following code:\n\n> 理解闭包首先要了解嵌套函数的词法作用域规则。看一下这段代码：\n\n```js\nlet scope = \"global scope\";          // A global variable\nfunction checkscope() {\n    let scope = \"local scope\";       // A local variable\n    function f() { return scope; }   // Return the value in scope here\n    return f();\n}\ncheckscope()                         // => \"local scope\"\n```\nThe checkscope() function declares a local variable and then defines and invokes a function that returns the value of that variable. It should be clear to you why the call to checkscope() returns “local scope”. Now, let’s change the code just slightly. Can you tell what this code will return?\n\n> checkscope() 函数声明了一个局部变量，然后定义并执行了一个函数 f() ，函数 f() 返回了这个变量的值，最后将函数 f() 的执行结果返回。你应当非常清楚为什么调用 checkscope() 会返回 local scope。现在我们对这段代码做一点改动。你知道这段代码返回什么吗？\n\n```js\nlet scope = \"global scope\";          // A global variable\nfunction checkscope() {\n    let scope = \"local scope\";       // A local variable\n    function f() { return scope; }   // Return the value in scope here\n    return f;\n}\nlet s = checkscope()();              // What does this return?\n```\nIn this code, a pair of parentheses has moved from inside checkscope() to outside of it. Instead of invoking the nested function and returning its result, checkscope() now just returns the nested function object itself. What happens when we invoke that nested function (with the second pair of parentheses in the last line of code) outside of the function in which it was defined?\n\n> 在这段代码中，我们将函数内的一对圆括号移动到了 checkscope() 之后。checkscope() 现在仅仅返回函数内嵌套的一个函数对象，而不是直接返回结果。在定义函数的作用域外面，调用这个嵌套的函数（包含最后一行代码的最后一对圆括号）会发生什么事情呢？\n\nRemember the fundamental rule of lexical scoping: JavaScript functions are executed using the scope they were defined in. The nested function f() was defined in a scope where the variable scope was bound to the value “local scope”. That binding is still in effect when f is executed, no matter where it is executed from. So the last line of the preceding code example returns “local scope”, not “global scope”. This, in a nutshell, is the surprising and powerful nature of closures: they capture the local variable (and parameter) bindings of the outer function within which they are defined.\n\n> 回想一下词法作用域的基本规则：JavaScript 函数的执行用到了作用域，这个作用域是函数定义的时候创建的。嵌套的函数 f() 定义在变量 scope 绑定的值是“local scope”的作用域里，这个绑定无论 f 函数在何处调用都依然有效。因此最后一行代码返回“local scope”，而不是“global scope”。简言之，闭包的这个特性强大到让人吃惊：它们可以捕捉到它们的外部函数所绑定的局部变量（和参数）。\n\nIn §8.4.1, we defined a uniqueInteger() function that used a property of the function itself to keep track of the next value to be returned. A shortcoming of that approach is that buggy or malicious code could reset the counter or set it to a noninteger, causing the uniqueInteger() function to violate the “unique” or the “integer” part of its contract. Closures capture the local variables of a single function invocation and can use those variables as private state. Here is how we could rewrite the uniqueInteger() using an immediately invoked function expression to define a namespace and a closure that uses that namespace to keep its state private:\n\n> 在 §8.4.1 中定义了 uniqueInteger() 函数，这个函数使用自身的一个属性来保存每次返回的值，以便每次调用都能跟踪上次的返回值。但这种做法有一个问题，就是恶意代码可能将计数器重置或者把一个非整数赋值给它，导致 uniquenterger() 函数不一定能产生“唯一”的“整数”。而闭包可以捕捉到单个函数调用的局部变量，并将这些局部变量用做私有状态。下面是如何用立即调用函数表达式重写 uniqueInteger() 来定义命名空间和闭包来保持其状态私有化：\n\n```js\nlet uniqueInteger = (function() {  // Define and invoke\n    let counter = 0;               // Private state of function below\n    return function() { return counter++; };\n}());\nuniqueInteger()  // => 0\nuniqueInteger()  // => 1\n```\nIn order to understand this code, you have to read it carefully. At first glance, the first line of code looks like it is assigning a function to the variable uniqueInteger. In fact, the code is defining and invoking (as hinted by the open parenthesis on the first line) a function, so it is the return value of the function that is being assigned to uniqueInteger. Now, if we study the body of the function, we see that its return value is another function. It is this nested function object that gets assigned to uniqueInteger. The nested function has access to the variables in its scope and can use the counter variable defined in the outer function. Once that outer function returns, no other code can see the counter variable: the inner function has exclusive access to it.\n\n> 你需要仔细阅读这段代码才能理解其含义。粗略来看，第一行代码看起来像将函数赋值给一个变量 uniqueInteger，实际上，这段代码定义了一个立即调用的函数（函数的开始带有左圆括号），因此是这个函数的返回值赋值给变量 uniqueInteger。现在，我们来看函数体，这个函数的返回值是另外一个函数。这是一个嵌套的函数，我们将它赋值给变量 uniqueInteger。嵌套的函数是可以访问作用域内的变量的，而且可以访问外部函数中定义的 counter 变量。当外部函数返回之后，其他任何代码都无法访问 counter 变量：只有内部的函数才能访问到它。\n\nPrivate variables like counter need not be exclusive to a single closure: it is perfectly possible for two or more nested functions to be defined within the same outer function and share the same scope. Consider the following code:\n\n> 像 counter 一样的私有变量不是只能用在一个单独的闭包内，在同一个外部函数内定义的多个嵌套函数也可以访问它，这多个嵌套函数都共享一个作用域，看一下这段代码：\n\n```js\nfunction counter() {\n    let n = 0;\n    return {\n        count: function() { return n++; },\n        reset: function() { n = 0; }\n    };\n}\n\nlet c = counter(), d = counter();   // Create two counters\nc.count()                           // => 0\nd.count()                           // => 0: they count independently\nc.reset();                          // reset() and count() methods share state\nc.count()                           // => 0: because we reset c\nd.count()                           // => 1: d was not reset\n```\nThe counter() function returns a “counter” object. This object has two methods: count() returns the next integer, and reset() resets the internal state. The first thing to understand is that the two methods share access to the private variable n. The second thing to understand is that each invocation of counter() creates a new scope—independent of the scopes used by previous invocations—and a new private variable within that scope. So if you call counter() twice, you get two counter objects with different private variables. Calling count() or reset() on one counter object has no effect on the other.\n\n> counter() 函数返回了一个“计数器”对象，这个对象包含两个方法：count() 返回下一个整数，reset() 重置内部状态。首先要理解，这两个方法都可以访问私有变量n。再者，每次调用 counter() 都会创建一个新的作用域链和一个新的私有变量。因此，如果调用 counter() 两次，则会得到两个计数器对象，而且彼此包含不同的私有变量，调用其中一个计数器对象的 count() 或 reset() 不会影响到另外一个对象。\n\nIt is worth noting here that you can combine this closure technique with property getters and setters. The following version of the counter() function is a variation on code that appeared in §6.10.6, but it uses closures for private state rather than relying on a regular object property:\n\n> 从技术角度看，其实可以将这个闭包合并为属性存取器方法 getter 和 setter。下面这段代码所示的 counter() 函数的版本是 §6.10.6 中代码的变种，所不同的是，这里私有状态的实现是利用了闭包，而不是利用普通的对象属性来实现：\n\n```js\nfunction counter(n) {  // Function argument n is the private variable\n    return {\n        // Property getter method returns and increments private counter var.\n        get count() { return n++; },\n        // Property setter doesn't allow the value of n to decrease\n        set count(m) {\n            if (m > n) n = m;\n            else throw Error(\"count can only be set to a larger value\");\n        }\n    };\n}\n\nlet c = counter(1000);\nc.count            // => 1000\nc.count            // => 1001\nc.count = 2000;\nc.count            // => 2000\nc.count = 2000;    // !Error: count can only be set to a larger value\n```\nNote that this version of the counter() function does not declare a local variable but just uses its parameter n to hold the private state shared by the property accessor methods. This allows the caller of counter() to specify the initial value of the private variable.\n\n> 需要注意的是，这个版本的 counter() 函数并未声明局部变量，而只是使用参数 n 来保存私有状态并与属性存取器方法共享。这样的话，调用 counter() 的函数就可以指定私有变量的初始值了。\n\nExample 8-2 is a generalization of the shared private state through the closures technique we’ve been demonstrating here. This example defines an addPrivateProperty() function that defines a private variable and two nested functions to get and set the value of that variable. It adds these nested functions as methods of the object you specify.\n\n> 示例 8-2是这种使用闭包技术来共享的私有状态的通用做法。这个例子定义了 addPrivateProperty() 函数，这个函数定义了一个私有变量，以及两个嵌套的函数用来获取和设置这个私有变量的值。它将这些嵌套函数添加为所指定对象的方法：\n\nExample 8-2. Private property accessor methods using closures\n\n> 示例 8-2：利用闭包实现的私有属性存取器方法\n\n```js\n// This function adds property accessor methods for a property with\n// the specified name to the object o. The methods are named get<name>\n// and set<name>. If a predicate function is supplied, the setter\n// method uses it to test its argument for validity before storing it.\n// If the predicate returns false, the setter method throws an exception.\n//\n// The unusual thing about this function is that the property value\n// that is manipulated by the getter and setter methods is not stored in\n// the object o. Instead, the value is stored only in a local variable\n// in this function. The getter and setter methods are also defined\n// locally to this function and therefore have access to this local variable.\n// This means that the value is private to the two accessor methods, and it\n// cannot be set or modified except through the setter method.\nfunction addPrivateProperty(o, name, predicate) {\n    let value;  // This is the property value\n\n    // The getter method simply returns the value.\n    o[`get${name}`] = function() { return value; };\n\n    // The setter method stores the value or throws an exception if\n    // the predicate rejects the value.\n    o[`set${name}`] = function(v) {\n        if (predicate && !predicate(v)) {\n            throw new TypeError(`set${name}: invalid value ${v}`);\n        } else {\n            value = v;\n        }\n    };\n}\n\n// The following code demonstrates the addPrivateProperty() method.\nlet o = {};  // Here is an empty object\n\n// Add property accessor methods getName and setName()\n// Ensure that only string values are allowed\naddPrivateProperty(o, \"Name\", x => typeof x === \"string\");\n\no.setName(\"Frank\");       // Set the property value\no.getName()               // => \"Frank\"\no.setName(0);             // !TypeError: try to set a value of the wrong type\n```\nWe’ve now seen a number of examples in which two closures are defined in the same scope and share access to the same private variable or variables. This is an important technique, but it is just as important to recognize when closures inadvertently share access to a variable that they should not share. Consider the following code:\n\n> 我们已经看到了很多例子，在同一个作用域中定义两个闭包，这两个闭包共享同样的私有变量或变量。这是一种非常重要的技术，但还是要特别小心那些不希望共享的变量往往不经意间共享给了其他的闭包，了解这一点也很重要。看一下下面这段代码：\n\n```js\n// This function returns a function that always returns v\nfunction constfunc(v) { return () => v; }\n\n// Create an array of constant functions:\nlet funcs = [];\nfor(var i = 0; i < 10; i++) funcs[i] = constfunc(i);\n\n// The function at array element 5 returns the value 5.\nfuncs[5]()    // => 5\n```\nWhen working with code like this that creates multiple closures using a loop, it is a common error to try to move the loop within the function that defines the closures. Think about the following code, for example:\n\n> 这段代码利用循环创建了很多个闭包，当写类似这种代码的时候往往会犯一个错误：那就是试图将循环代码移入定义这个闭包的函数之内，看一下这段代码：\n\n```js\n// Return an array of functions that return the values 0-9\nfunction constfuncs() {\n    let funcs = [];\n    for(var i = 0; i < 10; i++) {\n        funcs[i] = () => i;\n    }\n    return funcs;\n}\n\nlet funcs = constfuncs();\nfuncs[5]()    // => 10; Why doesn't this return 5?\n```\nThis code creates 10 closures and stores them in an array. The closures are all defined within the same invocation of the function, so they share access to the variable i. When constfuncs() returns, the value of the variable i is 10, and all 10 closures share this value. Therefore, all the functions in the returned array of functions return the same value, which is not what we wanted at all. It is important to remember that the scope associated with a closure is “live.” Nested functions do not make private copies of the scope or make static snapshots of the variable bindings. Fundamentally, the problem here is that variables declared with var are defined throughout the function. Our for loop declares the loop variable with var i, so the variable i is defined throughout the function rather than being more narrowly scoped to the body of the loop. The code demonstrates a common category of bugs in ES5 and before, but the introduction of block-scoped variables in ES6 addresses the issue. If we just replace the var with a let or a const, then the problem goes away. Because let and const are block scoped, each iteration of the loop defines a scope that is independent of the scopes for all other iterations, and each of these scopes has its own independent binding of i.\n\n> 上面这段代码创建了10个闭包，并将它们存储到一个数组中。这些闭包都是在同一个函数调用中定义的，因此它们可以共享变量 i。当 constfuncs() 返回时，变量 i 的值是10，所有的闭包都共享这一个值，因此，数组中的函数的返回值都是同一个值，这不是我们想要的结果。关联到闭包的作用域都是“活动的”，记住这一点非常重要。嵌套的函数不会将作用域内的私有成员复制一份，也不会对所绑定的变量生成静态快照。从根本上讲，这里的问题是，使用 var 声明的变量，它的定义贯穿整个函数。我们的 for 循环使用 var i 声明循环变量，因此变量 i 在整个函数中都有定义，而不是更狭义地作用于循环的主体。该代码演示了 ES6 之前的常见 Bug 类别，但在 ES6 中引入块级变量作用域解决了这个问题。如果我们只是用 let 或 const 替换 var， 那么问题就消失了。由于 let 和 const 是块级作用域，因此循环的每个迭代都定义了一个独立于所有其他迭代的作用域，并且每个作用域都有其自己的独立绑定 i。\n\nAnother thing to remember when writing closures is that this is a JavaScript keyword, not a variable. As discussed earlier, arrow functions inherit the this value of the function that contains them, but functions defined with the function keyword do not. So if you’re writing a closure that needs to use the this value of its containing function, you should use an arrow function, or call bind(), on the closure before returning it, or assign the outer this value to a variable that your closure will inherit:\n\n> 书写闭包的时候还需注意一件事情，this 是 JavaScript 的关键字，而不是变量。正如之前讨论的，箭头函数从包含它们的函数中继承 this 值，但是用 function 关键字定义的函数不是。所以如果写一个闭包需要使用包含它的函数的 this 值，要在闭包返回之前使用箭头函数或者用调用 bind()，或者将 this 值赋值给一个变量，这样你的闭包会继承它：\n\n```js\nconst self = this;  // Make the this value available to nested functions\n```\n## 8.7 Function Properties, Methods, and Constructor\nWe’ve seen that functions are values in JavaScript programs. The typeof operator returns the string “function” when applied to a function, but functions are really a specialized kind of JavaScript object. Since functions are objects, they can have properties and methods, just like any other object. There is even a Function() constructor to create new function objects. The subsections that follow document the length, name, and prototype properties; the call(), apply(), bind(), and toString() methods; and the Function() constructor.\n\n> 我们看到在 JavaScript 程序中，函数是值。对函数执行 typeof 运算会返回字符串“function”，但是函数是 JavaScript 中特殊的对象。因为函数也是对象，它们也可以拥有属性和方法，就像普通的对象可以拥有属性和方法一样。甚至可以用 Function() 构造函数来创建新的函数对象。接下来几节就会着重介绍函数 length、name 和 prototype 属性；call()、 apply()、 bind() 和 toString() 方法；以及 Function() 构造函数。\n\n### 8.7.1 The length Property\nThe read-only length property of a function specifies the arity of the function—the number of parameters it declares in its parameter list, which is usually the number of arguments that the function expects. If a function has a rest parameter, that parameter is not counted for the purposes of this length property.\n\n> 函数的只读属性 length 指定函数参数个数--声明在其参数列表中的参数个数，大多数函数期望的实参个数。如果一个函数有一个剩余函数，它的参数个数不被计算入 length 属性。（经测试，可选参数也不计算 length。）\n\n### 8.7.2 The name Property\nThe read-only name property of a function specifies the name that was used when the function was defined, if it was defined with a name, or the name of the variable or property that an unnamed function expression was assigned to when it was first created. This property is primarily useful when writing debugging or error messages.\n\n> 如果使用名称定义函数，只读属性 name 指定函数定义时用的名称，或未命名函数表达式在首次创建时分配给的变量或属性的名称。此属性在编写调试或错误消息时很有用。\n\n### 8.7.3 The prototype Property\nAll functions, except arrow functions, have a prototype property that refers to an object known as the prototype object. Every function has a different prototype object. When a function is used as a constructor, the newly created object inherits properties from the prototype object. Prototypes and the prototype property were discussed in §6.2.3 and will be covered again in Chapter 9.\n\n> 所有函数都包含一个 prototype 属性，这个属性是指向一个对象的引用，这个对象称做原型对象。每一个函数都包含不同的原型对象。当将函数用作构造函数的时候，新创建的对象会从原型对象上继承属性。§6.2.3 讨论了原型和 prototype 属性，在第 9 章里会有进一步讨论。\n\n### 8.7.4 The call() and apply() Methods\ncall() and apply() allow you to indirectly invoke (§8.2.4) a function as if it were a method of some other object. The first argument to both call() and apply() is the object on which the function is to be invoked; this argument is the invocation context and becomes the value of the this keyword within the body of the function. To invoke the function f() as a method of the object o (passing no arguments), you could use either call() or apply():\n\n> 我们可以将 call() 和 apply () 看做是某个对象的方法，通过调用方法的形式来间接调用（见 §8.2.4）函数。call() 和 apply() 的第一个实参是要调用函数的母对象，它是调用上下文，在函数体内变成 this 关键字的值。要想以对象 o 的方法来调用函数 f()（没有实参传递），可以这样使用 call() 和 apply()：\n\n```js\nf.call(o);\nf.apply(o);\n```\nEither of these lines of code are similar to the following (which assume that o does not already have a property named m):\n\n> 每行代码和下面代码的功能类似（假设对象 o 中预先不存在名为 m 的属性）:\n\n```js\no.m = f;     // Make f a temporary method of o.\no.m();       // Invoke it, passing no arguments.\ndelete o.m;  // Remove the temporary method.\n```\nRemember that arrow functions inherit the this value of the context where they are defined. This cannot be overridden with the call() and apply() methods. If you call either of those methods on an arrow function, the first argument is effectively ignored.\n\n> 不要忘了，箭头函数从它定义的位置的上下文继承 this 值。这不能被 call() 和 apply() 方法重写。如果通过箭头函数调用它俩任何一个方法，第一个实参实际上都被忽略。\n\nAny arguments to call() after the first invocation context argument are the values that are passed to the function that is invoked (and these arguments are not ignored for arrow functions). For example, to pass two numbers to the function f() and invoke it as if it were a method of the object o, you could use code like this:\n\n> 对于 call() 来说，除了第一个作为调用上下文实参，之后的所有实参就是要传入待调用函数的值（并且，这部分实参对于箭头函数来说不被忽略）。比如，以对象 o 的方法的形式调用函数 f()，并传入两个数，可以使用这样的代码：\n\n```js\nf.call(o, 1, 2);\n```\nThe apply() method is like the call() method, except that the arguments to be passed to the function are specified as an array:\n\n> apply() 方法和 call() 类似，但传入实参的形式和 call() 有所不同，它的实参都放入一个数组中：\n\n```js\nf.apply(o, [1,2]);\n```\nIf a function is defined to accept an arbitrary number of arguments, the apply() method allows you to invoke that function on the contents of an array of arbitrary length. In ES6 and later, we can just use the spread operator, but you may see ES5 code that uses apply() instead. For example, to find the largest number in an array of numbers without using the spread operator, you could use the apply() method to pass the elements of the array to the Math.max() function:\n\n> 如果一个函数的实参可以是任意数量，用 apply() 方法允许你传入的参数数组可以是任意长度的。在 ES6 之后，我们可以用展开运算符，但是在 ES5 的代码中你可以看到这种情况是用 apply() 来替代。比如，不用展开运算符找出数组中最大的数值元素，调用 Math.max() 方法的时候可以给  apply() 传入一个包含任意个元素的数组：\n\n```js\nlet biggest = Math.max.apply(Math, arrayOfNumbers);\n```\nThe trace() function defined in the following is similar to the timed() function defined in §8.3.4, but it works for methods instead of functions. It uses the apply() method instead of a spread operator, and by doing that, it is able to invoke the wrapped method with the same arguments and the same this value as the wrapper method:\n\n> 下面定义的 trace() 与 §8.3.4 中定义的 timed() 函数类似，但是它对方法有效而不是函数。它使用 apply() 方法而不是展开运算符，通过这样做，它能够调用具有相同参数和与被包装方法相同的 this 值的包装方法。\n\n```js\n// Replace the method named m of the object o with a version that logs\n// messages before and after invoking the original method.\nfunction trace(o, m) {\n    let original = o[m];         // Remember original method in the closure.\n    o[m] = function(...args) {   // Now define the new method.\n        console.log(new Date(), \"Entering:\", m);      // Log message.\n        let result = original.apply(this, args);      // Invoke original.\n        console.log(new Date(), \"Exiting:\", m);       // Log message.\n        return result;                                // Return result.\n    };\n}\n```\n### 8.7.5 The bind() Method\nThe primary purpose of bind() is to bind a function to an object. When you invoke the bind() method on a function f and pass an object o, the method returns a new function. Invoking the new function (as a function) invokes the original function f as a method of o. Any arguments you pass to the new function are passed to the original function. For example:\n\n> bind() 方法的主要作用就是将函数绑定至某个对象。当在函数 f() 上调用 bind() 方法并传入一个对象 o 作为参数，这个方法将返回一个新的函数。（以函数调用的方式）调用新的函数将会把原始的函数 f() 当做 o 的方法来调用。传入新函数的任何实参都将传入原始函数，比如：\n\n```js\nfunction f(y) { return this.x + y; } // This function needs to be bound\nlet o = { x: 1 };                    // An object we'll bind to\nlet g = f.bind(o);                   // Calling g(x) invokes f() on o\ng(2)                                 // => 3\nlet p = { x: 10, g };                // Invoke g() as a method of this object\np.g(2)                               // => 3: g is still bound to o, not p.\n```\nArrow functions inherit their this value from the environment in which they are defined, and that value cannot be overridden with bind(), so if the function f() in the preceding code was defined as an arrow function, the binding would not work. The most common use case for calling bind() is to make non-arrow functions behave like arrow functions, however, so this limitation on binding arrow functions is not a problem in practice.\n\n> 箭头函数从它们定义的上下文中继承 this 值，并且其不可被 bind() 方法重写，所以如果上面的代码用箭头函数定义函数 f()，这个绑定不会生效。调用 bind() 方法的最常用场景是让不带箭头的函数的行为像箭头函数一样，所以实际上绑定箭头函数的 this 局限性并不是一个问题。\n\nThe bind() method does more than just bind a function to an object, however. It can also perform partial application: any arguments you pass to bind() after the first are bound along with the this value. This partial application feature of bind() does work with arrow functions. Partial application is a common technique in functional programming and is sometimes called currying. Here are some examples of the bind() method used for partial application:\n\n> 但是 bind() 方法不仅仅是将函数绑定至一个对象。它还附带一些其他应用：除了第一个实参之外，传入 bind() 的实参也会绑定至 this 值。这个附带的应用在箭头函数上也同样生效。是一种常见的函数式编程技术，有时也被称为“柯里化”。参照下面这个例子中的 bind() 方法的实现：\n\n```js\nlet sum = (x,y) => x + y;      // Return the sum of 2 args\nlet succ = sum.bind(null, 1);  // Bind the first argument to 1\nsucc(2)  // => 3: x is bound to 1, and we pass 2 for the y argument\n\nfunction f(y,z) { return this.x + y + z; }\nlet g = f.bind({x: 1}, 2);     // Bind this and y\ng(3)     // => 6: this.x is bound to 1, y is bound to 2 and z is 3\n```\nThe name property of the function returned by bind() is the name property of the function that bind() was called on, prefixed with the word “bound”.\n\n> bind() 返回函数的名称属性是调用 bind() 的函数的名称属性前面加上前缀为单词\"bound\"。\n\n### 8.7.6 The toString() Method\nLike all JavaScript objects, functions have a toString() method. The ECMAScript spec requires this method to return a string that follows the syntax of the function declaration statement. In practice, most (but not all) implementations of this toString() method return the complete source code for the function. Built-in functions typically return a string that includes something like “[native code]” as the function body.\n\n> 和所有的 JavaScript 对象一样，函数也有 toString() 方法，ECMAScript 规范规定这个方法返回一个字符串，这个字符串和函数声明语句的语法相关。实际上，大多数（非全部）的 toString() 方法的实现都返回函数的完整源码。内置函数往往返回一个类似”[native code]”的字符串作为函数体。\n\n### 8.7.7 The Function() Constructor\nBecause functions are objects, there is a Function() constructor that can be used to create new functions:\n\n> 因为函数是对象，有一个 Function() 构造函数可以用来创建新的函数：\n\n```js\nconst f = new Function(\"x\", \"y\", \"return x*y;\");\n```\nThis line of code creates a new function that is more or less equivalent to a function defined with the familiar syntax:\n\n> 这一行代码创建一个新的函数，这个函数和通过下面代码定义的函数几乎等价：\n\n```js\nconst f = function(x, y) { return x*y; };\n```\nThe Function() constructor expects any number of string arguments. The last argument is the text of the function body; it can contain arbitrary JavaScript statements, separated from each other by semicolons. All other arguments to the constructor are strings that specify the parameter names for the function. If you are defining a function that takes no arguments, you would simply pass a single string—the function body—to the constructor.\n\n> Function() 构造函数可以传入任意数量的字符串实参，最后一个实参所表示的文本就是函数体；它可以包含任意的 JavaScript 语句，每两条语句之间用分号分隔。传入构造函数的其他所有的实参字符串是指定函数的形参名字的字符串。如果定义的函数不包含任何参数，只须给构造函数简单地传入一个字符串——函数体——即可。\n\nNotice that the Function() constructor is not passed any argument that specifies a name for the function it creates. Like function literals, the Function() constructor creates anonymous functions.\n\n> 注意，Function() 构造函数并不需要通过传入实参以指定函数名。就像函数字面量一样，Function() 构造函数创建一个匿名函数。\n\nThere are a few points that are important to understand about the Function() constructor:\n\n> 关于 Function() 构造函数有几点需要特别注意：\n\nThe Function() constructor allows JavaScript functions to be dynamically created and compiled at runtime.\n\n> Function() 构造函数允许 JavaScript 在运行时动态地创建并编译函数。\n\nThe Function() constructor parses the function body and creates a new function object each time it is called. If the call to the constructor appears within a loop or within a frequently called function, this process can be inefficient. By contrast, nested functions and function expressions that appear within loops are not recompiled each time they are encountered.\n\n> 每次调用 Function() 构造函数都会解析函数体，并创建新的函数对象。如果是在一个循环或者多次调用的函数中执行这个构造函数，执行效率会受影响。相比之下，循环中的嵌套函数和函数定义表达式则不会每次执行时都重新编译。\n\nA last, very important point about the Function() constructor is that the functions it creates do not use lexical scoping; instead, they are always compiled as if they were top-level functions, as the following code demonstrates:\n\n> 最后一点，也是关于 Function() 构造函数非常重要的一点，就是它所创建的函数并不是使用词法作用域，相反，函数体代码的编译类似顶层函数，如下面代码所示：\n\n```js\nlet scope = \"global\";\nfunction constructFunction() {\n    let scope = \"local\";\n    return new Function(\"return scope\");  // Doesn't capture local scope!\n}\n// This line returns \"global\" because the function returned by the\n// Function() constructor does not use the local scope.\nconstructFunction()()  // => \"global\"\n```\nThe Function() constructor is best thought of as a globally scoped version of eval() (see §4.12.2) that defines new variables and functions in its own private scope. You will probably never need to use this constructor in your code.\n\n> 我们可以将 Function() 构造函数认为是在全局作用域中执行的 eval()（见  §4.12.2），eval() 可以在自己的私有作用域内定义新变量和函数，Function() 构造函数在实际编程过程中很少会用到。\n\n## 8.8 Functional Programming\nJavaScript is not a functional programming language like Lisp or Haskell, but the fact that JavaScript can manipulate functions as objects means that we can use functional programming techniques in JavaScript. Array methods such as map() and reduce() lend themselves particularly well to a functional programming style. The sections that follow demonstrate techniques for functional programming in JavaScript. They are intended as a mind-expanding exploration of the power of JavaScript’s functions, not as a prescription for good programming style.\n\n> 和 Lisp、Haskell 不同，JavaScript 并非函数式编程语言，但在 JavaScript 中可以像操控对象一样操控函数，也就是说可以在 JavaScript 中应用函数式编程技术。数组方法诸如 map() 和 reduce() 就可以非常适合用于函数式编程风格。接下来的几节将会着重介绍 JavaScript 中的函数式编程技术。函数式编程旨在扩展对 JavaScript 函数功能功能的探索，而不是为了良好的编程风格。\n\n### 8.8.1 Processing Arrays with Functions\nSuppose we have an array of numbers and we want to compute the mean and standard deviation of those values. We might do that in nonfunctional style like this:\n\n> 假设有一个数组，数组元素都是数字，我们想要计算这些元素的平均值和标准差。若使用非函数式编程风格的话，代码会是这样：\n\n```js\nlet data = [1,1,3,5,5];  // This is our array of numbers\n\n// The mean is the sum of the elements divided by the number of elements\nlet total = 0;\nfor(let i = 0; i < data.length; i++) total += data[i];\nlet mean = total/data.length;  // mean == 3; The mean of our data is 3\n\n// To compute the standard deviation, we first sum the squares of\n// the deviation of each element from the mean.\ntotal = 0;\nfor(let i = 0; i < data.length; i++) {\n    let deviation = data[i] - mean;\n    total += deviation * deviation;\n}\nlet stddev = Math.sqrt(total/(data.length-1));  // stddev == 2\n```\nWe can perform these same computations in concise functional style using the array methods map() and reduce() like this (see §7.8.1 to review these methods):\n\n> 可以使用数组方法 map() 和 reduce() 来实现同样的计算，这种实现极其简洁（参照 §7.8.1 来查看这些方法）：\n\n```js\n// First, define two simple functions\nconst sum = (x,y) => x+y;\nconst square = x => x*x;\n\n// Then use those functions with Array methods to compute mean and stddev\nlet data = [1,1,3,5,5];\nlet mean = data.reduce(sum)/data.length;  // mean == 3\nlet deviations = data.map(x => x-mean);\nlet stddev = Math.sqrt(deviations.map(square).reduce(sum)/(data.length-1));\nstddev  // => 2\n```\nThis new version of the code looks quite different than the first one, but it is still invoking methods on objects, so it has some object-oriented conventions remaining. Let’s write functional versions of the map() and reduce() methods:\n\n> 这个新版本的代码看起来跟第一版有很大不同，但是它仍然调用对象的方法，所以它还是面向对象编程。接下来用函数版本的 map() 和 reduce() 方法：\n\n```js\nconst map = function(a, ...args) { return a.map(...args); };\nconst reduce = function(a, ...args) { return a.reduce(...args); };\n```\nWith these map() and reduce() functions defined, our code to compute the mean and standard deviation now looks like this:\n\n> 用这两个函数定义了 map() 和 reduce()，我们计算平均值和标准差变成这样：\n\n```js\nconst sum = (x,y) => x+y;\nconst square = x => x*x;\n\nlet data = [1,1,3,5,5];\nlet mean = reduce(data, sum)/data.length;\nlet deviations = map(data, x => x-mean);\nlet stddev = Math.sqrt(reduce(map(deviations, square), sum)/(data.length-1));\nstddev  // => 2\n```\n### 8.8.2 Higher-Order Functions\nA higher-order function is a function that operates on functions, taking one or more functions as arguments and returning a new function. Here is an example:\n\n> 所谓高阶函数就是操作函数的函数，它接收一个或多个函数作为参数，并返回一个新函数。来看这个例子：\n\n```js\n// This higher-order function returns a new function that passes its\n// arguments to f and returns the logical negation of f's return value;\nfunction not(f) {\n    return function(...args) {             // Return a new function\n        let result = f.apply(this, args);  // that calls f\n        return !result;                    // and negates its result.\n    };\n}\n\nconst even = x => x % 2 === 0; // A function to determine if a number is even\nconst odd = not(even);         // A new function that does the opposite\n[1,1,3,5,5].every(odd)         // => true: every element of the array is odd\n```\nThis not() function is a higher-order function because it takes a function argument and returns a new function. As another example, consider the mapper() function that follows. It takes a function argument and returns a new function that maps one array to another using that function. This function uses the map() function defined earlier, and it is important that you understand how the two functions are different:\n\n> 上面的 not() 函数就是一个高阶函数，因为它接收一个函数作为参数，并返回一个新函数。另外一个例子，来看下面的 mapper() 函数，它也是接收一个函数作为实参，并返回一个新函数，这个新函数将一个数组映射到另一个使用这个函数的数组上。这个函数使用了之前定义的 map() 函数，但要首先理解这两个函数有哪里不 同，理解这一点至关重要：\n\n```js\n// Return a function that expects an array argument and applies f to\n// each element, returning the array of return values.\n// Contrast this with the map() function from earlier.\nfunction mapper(f) {\n    return a => map(a, f);\n}\n\nconst increment = x => x+1;\nconst incrementAll = mapper(increment);\nincrementAll([1,2,3])  // => [2,3,4]\n```\nHere is another, more general, example that takes two functions, f and g, and returns a new function that computes f(g()):\n\n> 这里是一个更常见的例子，它接收两个函数 f() 和 g()，并返回一个新的函数用以计算 f(g())：\n\n```js\n// Return a new function that computes f(g(...)).\n// The returned function h passes all of its arguments to g, then passes\n// the return value of g to f, then returns the return value of f.\n// Both f and g are invoked with the same this value as h was invoked with.\nfunction compose(f, g) {\n    return function(...args) {\n        // We use call for f because we're passing a single value and\n        // apply for g because we're passing an array of values.\n        return f.call(this, g.apply(this, args));\n    };\n}\n\nconst sum = (x,y) => x+y;\nconst square = x => x*x;\ncompose(square, sum)(2,3)  // => 25; the square of the sum\n```\nThe partial() and memoize() functions defined in the sections that follow are two more important higher-order functions.\n\n> 本章后续几节中定义了 partial() 和 memoize() 函数，这两个函数是非常重要的高阶函数。\n\n### 8.8.3 Partial Application of Functions\nThe bind() method of a function f (see §8.7.5) returns a new function that invokes f in a specified context and with a specified set of arguments. We say that it binds the function to an object and partially applies the arguments. The bind() method partially applies arguments on the left—that is, the arguments you pass to bind() are placed at the start of the argument list that is passed to the original function. But it is also possible to partially apply arguments on the right:\n\n> 函数 f()（见 §8.7.5）的 bind() 方法返回一个新函数，给新函数传入特定的上下文和一组指定的参数，然后调用函数 f()。我们说它把函数“绑定至”对象并传入一部分参数。bind() 方法只是将实参放在（完整实参列表的）左侧，也就是说传入 bind() 的实参都是放在传入原始函数的实参列表开始的位置，但有时我们期望将传入 bind() 的实参放在（完整实参列表的）右侧：\n\n```js\n// The arguments to this function are passed on the left\nfunction partialLeft(f, ...outerArgs) {\n    return function(...innerArgs) { // Return this function\n        let args = [...outerArgs, ...innerArgs]; // Build the argument list\n        return f.apply(this, args);              // Then invoke f with it\n    };\n}\n\n// The arguments to this function are passed on the right\nfunction partialRight(f, ...outerArgs) {\n    return function(...innerArgs) {  // Return this function\n        let args = [...innerArgs, ...outerArgs]; // Build the argument list\n        return f.apply(this, args);              // Then invoke f with it\n    };\n}\n\n// The arguments to this function serve as a template. Undefined values\n// in the argument list are filled in with values from the inner set.\nfunction partial(f, ...outerArgs) {\n    return function(...innerArgs) {\n        let args = [...outerArgs]; // local copy of outer args template\n        let innerIndex=0;          // which inner arg is next\n        // Loop through the args, filling in undefined values from inner args\n        for(let i = 0; i < args.length; i++) {\n            if (args[i] === undefined) args[i] = innerArgs[innerIndex++];\n        }\n        // Now append any remaining inner arguments\n        args.push(...innerArgs.slice(innerIndex));\n        return f.apply(this, args);\n    };\n}\n\n// Here is a function with three arguments\nconst f = function(x,y,z) { return x * (y - z); };\n// Notice how these three partial applications differ\npartialLeft(f, 2)(3,4)         // => -2: Bind first argument: 2 * (3 - 4)\npartialRight(f, 2)(3,4)        // =>  6: Bind last argument: 3 * (4 - 2)\npartial(f, undefined, 2)(3,4)  // => -6: Bind middle argument: 3 * (2 - 4)\n```\nThese partial application functions allow us to easily define interesting functions out of functions we already have defined. Here are some examples:\n\n> 利用这种不完全函数的编程技巧，可以编写一些有意思的代码，利用已有的函数来定义新的函数，参照下面这个例子：\n\n```js\nconst increment = partialLeft(sum, 1);\nconst cuberoot = partialRight(Math.pow, 1/3);\ncuberoot(increment(26))  // => 3\n```\nPartial application becomes even more interesting when we combine it with other higher-order functions. Here, for example, is a way to define the preceding not() function just shown using composition and partial application:\n\n> 当将不完全调用和其他高阶函数整合在一起的时候，事情就变得格外有趣了。比如，这里的例子定义了 not() 函数，它用到了刚才提到的不完全调用：\n\n```js\nconst not = partialLeft(compose, x => !x);\nconst even = x => x % 2 === 0;\nconst odd = not(even);\nconst isNumber = not(isNaN);\nodd(3) && isNumber(2)  // => true\n```\nWe can also use composition and partial application to redo our mean and standard deviation calculations in extreme functional style:\n\n> 我们也可以使用不完全调用的组合来重新组织求平均数和标准差的代码，这种编码风格是非常纯粹的函数式编程：\n\n```js\n// sum() and square() functions are defined above. Here are some more:\nconst product = (x,y) => x*y;\nconst neg = partial(product, -1);\nconst sqrt = partial(Math.pow, undefined, .5);\nconst reciprocal = partial(Math.pow, undefined, neg(1));\n\n// Now compute the mean and standard deviation.\nlet data = [1,1,3,5,5];   // Our data\nlet mean = product(reduce(data, sum), reciprocal(data.length));\nlet stddev = sqrt(product(reduce(map(data,\n                                     compose(square,\n                                             partial(sum, neg(mean)))),\n                                 sum),\n                          reciprocal(sum(data.length,neg(1)))));\n[mean, stddev]  // => [3, 2]\n```\nNotice that this code to compute mean and standard deviation is entirely function invocations; there are no operators involved, and the number of parentheses has grown so large that this JavaScript is beginning to look like Lisp code. Again, this is not a style that I advocate for JavaScript programming, but it is an interesting exercise to see how deeply functional JavaScript code can be.\n\n> 注意，这段代码计算平均值和标准差完全是函数调用;没有涉及运算符，并且括号的数量增长如此之大让 JavaScript 开始看起来像 Lisp 代码。同样，这不是我提倡的 JavaScript 编程风格，但它是一个有趣的练习，看看 JavaScript 代码的功能有多深。\n\n### 8.8.4 Memoization\nIn §8.4.1, we defined a factorial function that cached its previously computed results. In functional programming, this kind of caching is called memoization. The code that follows shows a higher-order function, memoize(), that accepts a function as its argument and returns a memoized version of the function:\n\n> 在 §8.4.1 中定义了一个阶乘函数，它可以将上次的计算结果缓存起来。在函数式编程当中，这种缓存技巧叫做“记忆”（memorization）。下面的代码展示了一个高阶函数，memorize() 接收一个函数作为实参，并返回带有记忆能力的函数:\n\n```js\n// Return a memoized version of f.\n// It only works if arguments to f all have distinct string representations.\nfunction memoize(f) {\n    const cache = new Map();  // Value cache stored in the closure.\n\n    return function(...args) {\n        // Create a string version of the arguments to use as a cache key.\n        let key = args.length + args.join(\"+\");\n        if (cache.has(key)) {\n            return cache.get(key);\n        } else {\n            let result = f.apply(this, args);\n            cache.set(key, result);\n            return result;\n        }\n    };\n}\n```\nThe memoize() function creates a new object to use as the cache and assigns this object to a local variable so that it is private to (in the closure of) the returned function. The returned function converts its arguments array to a string and uses that string as a property name for the cache object. If a value exists in the cache, it returns it directly. Otherwise, it calls the specified function to compute the value for these arguments, caches that value, and returns it. Here is how we might use memoize():\n\n> memorize() 函数创建一个新的对象，这个对象被当做缓存（的宿主）并赋值给一个局部变量，因此对于返回的函数来说它是私有的（在闭包中）。所返回的函数将它的实参数组转换成字符串，并将字符串用做缓存对象的属性名。如果在缓存中存在这个值，则直接返回它。否则，就调用既定的函数对实参进行计算，将计算结果缓存起来并返回，下面的代码展示了如何使用 memorize()：\n\n```js\n// Return the Greatest Common Divisor of two integers using the Euclidian\n// algorithm: http://en.wikipedia.org/wiki/Euclidean_algorithm\nfunction gcd(a,b) {  // Type checking for a and b has been omitted\n    if (a < b) {           // Ensure that a >= b when we start\n        [a, b] = [b, a];   // Destructuring assignment to swap variables\n    }\n    while(b !== 0) {       // This is Euclid's algorithm for GCD\n        [a, b] = [b, a%b];\n    }\n    return a;\n}\n\nconst gcdmemo = memoize(gcd);\ngcdmemo(85, 187)  // => 17\n\n// Note that when we write a recursive function that we will be memoizing,\n// we typically want to recurse to the memoized version, not the original.\nconst factorial = memoize(function(n) {\n    return (n <= 1) ? 1 : n * factorial(n-1);\n});\nfactorial(5)      // => 120: also caches values for 4, 3, 2 and 1.\n```\n## 8.9 Summary\nSome key points to remember about this chapter are as follows:\n\nYou can define functions with the function keyword and with the ES6 => arrow syntax.\n\nYou can invoke functions, which can be used as methods and constructors.\n\nSome ES6 features allow you to define default values for optional function parameters, to gather multiple arguments into an array using a rest parameter, and to destructure object and array arguments into function parameters.\n\nYou can use the ... spread operator to pass the elements of an array or other iterable object as arguments in a function invocation.\n\nA function defined inside of and returned by an enclosing function retains access to its lexical scope and can therefore read and write the variables defined inside the outer function. Functions used in this way are called closures, and this is a technique that is worth understanding.\n\nFunctions are objects that can be manipulated by JavaScript, and this enables a functional style of programming.\n\n> 本章关键点总结如下：\n> \n> 可以用函数关键字和 ES6 => 箭头函数来定义函数。\n> \n> 可以以方法和构造函数的方式调用函数。\n> \n> 一些 ES6 特性，允许参数设定默认值，可以用剩余参数将多个参数搜集到一个数组中，可以解构对象和数组实参到函数参数中。\n> \n> 可以用 ... 展开运算符传递数组元素或者其他可迭代对象到函数调用。\n> \n> 封闭函数内部定义并返回的函数保留对其词法作用域的访问，因此可以读取和写入外部函数内定义的变量。用这种方式使用的函数称为闭包，这是一种值得理解的技术。\n> \n> 函数是可由 JavaScript 操作的对象，这使 JavaScript 支持函数式编程。\n\n---\n\n1. The term was coined by Martin Fowler. See http://martinfowler.com/dslCatalog/methodChaining.html.\n2. If you are familiar with Python, note that this is different than Python, in which every invocation shares the same default value.\n3. This may not seem like a particularly interesting point unless you are familiar with more static languages, in which functions are part of a program but cannot be manipulated by the program.\n\n> 1. 这个术语最初是由 Martin Fowler 提出的，参见http://martinfowler.com/dslwip/MethodChaining.html。\n> 2. 这看起来不足为奇，但如果你对 Python 很熟悉，你会发现 Python 中的函数是程序的一 部分，但无法被程序操作。\n> 3. 这似乎并不是一个特别有趣的点，除非你熟悉更多的静态语言，其中函数是程序的一部分，但不能由程序操作。"
  },
  {
    "path": "content/posts/ch9.md",
    "content": "---\ntitle: \"第 9 章 类\"\ndate: 2020-11-02T22:18:34+08:00\n---\n\nJavaScript objects were covered in Chapter 6. That chapter treated each object as a unique set of properties, different from every other object. It is often useful, however, to define a class of objects that share certain properties. Members, or instances, of the class have their own properties to hold or define their state, but they also have methods that define their behavior. These methods are defined by the class and shared by all instances. Imagine a class named Complex that represents and performs arithmetic on complex numbers, for example. A Complex instance would have properties to hold the real and imaginary parts (the state) of the complex number. And the Complex class would define methods to perform addition and multiplication (the behavior) of those numbers.\n\n> 第 6 章详细介绍了 JavaScript 对象，每个 JavaScript 对象都是一个属性集合，相互之间没有任何联系。在 JavaScript 中也可以定义对象的类，让每个对象都共享某些属性，这种“共享”的特性是非常有用的。类的成员或实例都包含一些属性，用以存放或定义它们的状态，其中有些属性定义了它们的行为（通常称为方法）。这些行为通常是由类定义的，而且为所有实例所共享。例如，假设有一个名为 Complex 的类用来表示复数，同时还定义了一些复数运算。一个 Complex 实例应当包含复数的实部和虚部（状态），同样 Complex 类还会定义复数的加法和乘法操作（行为）。\n\nIn JavaScript, classes use prototype-based inheritance: if two objects inherit properties (generally function-valued properties, or methods) from the same prototype, then we say that those objects are instances of the same class. That, in a nutshell, is how JavaScript classes work. JavaScript prototypes and inheritance were covered in §6.2.3 and §6.3.2, and you will need to be familiar with the material in those sections to understand this chapter. This chapter covers prototypes in §9.1.\n\n> 在 JavaScript 中，类的实现是基于其原型继承机制的。如果两个实例都从同一个原型对象上继承了属性，我们说它们是同一个类的实例。JavaScript 原型和继承在 §6.2.3 和 §6.3.2 节中有详细讨论，为了更好地理解本章的内容，请务必首先阅读这两个章节。本章将会在 §9.1 中对原型做进一步讨论。\n\nIf two objects inherit from the same prototype, this typically (but not necessarily) means that they were created and initialized by the same constructor function or factory function. Constructors have been covered in §4.6, §6.2.2, and §8.2.3, and this chapter has more in §9.2.\n\n> 如果两个对象继承自同一个原型，往往意味着（但不是绝对）它们是由同一个构造函数创建并初始化的。我们已经在 §4.6、§6.2.2 和 §8.2.3 节中详细讲解了构造函数，§9.2 会有进一步讨论。\n\nJavaScript has always allowed the definition of classes. ES6 introduced a brand-new syntax (including a class keyword) that makes it even easier to create classes. These new JavaScript classes work in the same way that old-style classes do, and this chapter starts by explaining the old way of creating classes because that demonstrates more clearly what is going on behind the scenes to make classes work. Once we’ve explained those fundamentals, we’ll shift and start using the new, simplified class definition syntax.\n\n> JavaScript 一直允许定义类。ES6 引入了全新的语法（包括 class 关键字），使创建类更加容易。这些新的 JavaScript 类的工作方式与旧式类相同，本章首先解释创建类的旧方法，因为这更清楚地展示了类是如何工作的。一旦我们解释了这些基本原理，我们将改变并开始使用新的、简化的类定义语法。\n\nIf you’re familiar with strongly typed object-oriented programming languages like Java or C++, you’ll notice that JavaScript classes are quite different from classes in those languages. There are some syntactic similarities, and you can emulate many features of “classical” classes in JavaScript, but it is best to understand up front that JavaScript’s classes and prototype-based inheritance mechanism are substantially different from the classes and class-based inheritance mechanism of Java and similar languages.\n\n> 如果你对诸如 Java 和 C++ 这种强类型的面向对象编程比较熟悉，你会发现 JavaScript 中的类和 Java 以及 C++ 中的类有很大不同。尽管在写法上类似，而且在 JavaScript 中也能“模拟”出很多经典的类的特性，但是最好要理解 JavaScript 的类和基于原型的继承机制，以及和传统的 Java（当然还有类似 Java 的语言）的类和基于类的继承机制的不同之处。\n\n## 9.1 Classes and Prototypes\n\nIn JavaScript, a class is a set of objects that inherit properties from the same prototype object. The prototype object, therefore, is the central feature of a class. Chapter 6 covered the Object.create() function that returns a newly created object that inherits from a specified prototype object. If we define a prototype object and then use Object.create() to create objects that inherit from it, we have defined a JavaScript class. Usually, the instances of a class require further initialization, and it is common to define a function that creates and initializes the new object. Example 9-1 demonstrates this: it defines a prototype object for a class that represents a range of values and also defines a factory function that creates and initializes a new instance of the class.\n\n> 在 JavaScript 中，类的所有实例对象都从同一个原型对象上继承属性。因此，原型对象是类的核心。在示例 6-1 中定义了 inherit() 函数，这个函数返回一个新创建的对象，后者继承自某个原型对象。如果定义一个原型对象，然后通过 inherit() 函数创建一个继承自它的对象，这样就定义了一个 JavaScript 类。通常，类的实例还需要进一步的初始化，通常是通过定义一个函数来创建并初始化这个新对象，参照示例 9-1。示例 9-1 给一个表示“值的范围”的类定义了原型对象，还定义了一个“工厂”函数用以创建并初始化类的实例。\n\nExample 9-1. A simple JavaScript class\n\n> 示例 9-1：一个简单的 JavaScript 类\n\n```js\n// This is a factory function that returns a new range object.\nfunction range(from, to) {\n    // Use Object.create() to create an object that inherits from the\n    // prototype object defined below.  The prototype object is stored as\n    // a property of this function, and defines the shared methods (behavior)\n    // for all range objects.\n    let r = Object.create(range.methods);\n\n    // Store the start and end points (state) of this new range object.\n    // These are noninherited properties that are unique to this object.\n    r.from = from;\n    r.to = to;\n\n    // Finally return the new object\n    return r;\n}\n\n// This prototype object defines methods inherited by all range objects.\nrange.methods = {\n    // Return true if x is in the range, false otherwise\n    // This method works for textual and Date ranges as well as numeric.\n    includes(x) { return this.from <= x && x <= this.to; },\n\n    // A generator function that makes instances of the class iterable.\n    // Note that it only works for numeric ranges.\n    *[Symbol.iterator]() {\n        for(let x = Math.ceil(this.from); x <= this.to; x++) yield x;\n    },\n\n    // Return a string representation of the range\n    toString() { return \"(\" + this.from + \"...\" + this.to + \")\"; }\n};\n\n// Here are example uses of a range object.\nlet r = range(1,3);      // Create a range object\nr.includes(2)            // => true: 2 is in the range\nr.toString()             // => \"(1...3)\"\n[...r]                   // => [1, 2, 3]; convert to an array via iterator\n```\nThere are a few things worth noting in the code of Example 9-1:\n\n> 在示例 9-1 的代码中有一下几点值得注意：\n\nThis code defines a factory function range() for creating new Range objects.\n\n> 这段代码定义了一个工厂函数 range() 用来创建一个新的 Range 对象。\n\nIt uses the methods property of this range() function as a convenient place to store the prototype object that defines the class. There is nothing special or idiomatic about putting the prototype object here.\n\n> 用 range() 函数的 methods 属性来存放定义类的原型对象。只是将原型对象随意的放在一个地方，并不是一个规约或者习惯。\n\nThe range() function defines from and to properties on each Range object. These are the unshared, noninherited properties that define the unique state of each individual Range object.\n\n> range() 函数在每个 Range 对象中都定义 from 和 to 属性。它们是非共享、非继承属性，是每个独立的 Range 对象的独特自有状态。\n\nThe range.methods object uses the ES6 shorthand syntax for defining methods, which is why you don’t see the function keyword anywhere. (See §6.10.5 to review object literal shorthand method syntax.)\n\n> range.methods 对象应用了 ES6 的速记语法来定义方法，这是为什么没有看到 function 关键字的原因。（参照 §6.10.5 来复习对象字面量速记方法语法。）\n\nOne of the methods in the prototype has the computed name (§6.10.2) Symbol.iterator, which means that it is defining an iterator for Range objects. The name of this method is prefixed with *, which indicates that it is a generator function instead of a regular function. Iterators and generators are covered in detail in Chapter 12. For now, the upshot is that instances of this Range class can be used with the for/of loop and with the ... spread operator.\n\n> 原型中的一个方法 Symbol.iterator 使用了计算属性名（§6.10.2），表明它是为 Range 对象定义一个迭代器。方法名称带有一个前缀 *，标识它是一个生成器函数而不是普通的函数。迭代器和生成器在第 12 章会详细描述。现在，只需要知道 Range 类的实例可以用 for/of 循环和可以用 ... 展开运算符。\n\nThe shared, inherited methods defined in range.methods all use the from and to properties that were initialized in the range() factory function. In order to refer to them, they use the this keyword to refer to the object through which they were invoked. This use of this is a fundamental characteristic of the methods of any class.\n\n> 定义在 range.methods 中的共享继承方法都使用在 range() 工厂函数初始化的 from 和 to 属性。在这些方法被调用时，为了引用 from 和 to 属性，都使用 this 关键字来获取对象的引用。this 这种用法是任何类中方法的基本特征。\n\n## 9.2 Classes and Constructors\nExample 9-1 demonstrates a simple way to define a JavaScript class. It is not the idiomatic way to do so, however, because it did not define a constructor. A constructor is a function designed for the initialization of newly created objects. Constructors are invoked using the new keyword as described in §8.2.3. Constructor invocations using new automatically create the new object, so the constructor itself only needs to initialize the state of that new object. The critical feature of constructor invocations is that the prototype property of the constructor is used as the prototype of the new object. §6.2.3 introduced prototypes and emphasized that while almost all objects have a prototype, only a few objects have a prototype property. Finally, we can clarify this: it is function objects that have a prototype property. This means that all objects created with the same constructor function inherit from the same object and are therefore members of the same class. Example 9-2 shows how we could alter the Range class of Example 9-1 to use a constructor function instead of a factory function. Example 9-2 demonstrates the idiomatic way to create a class in versions of JavaScript that do not support the ES6 class keyword. Even though class is well supported now, there is still lots of older JavaScript code around that defines classes like this, and you should be familiar with the idiom so that you can read old code and so that you understand what is going on “under the hood” when you use the class keyword.\n\n> 示例 9-1 展示了一个简单方式来定义一个 JavaScript 类。但是这种方法并不常用，因为它没有定义一个构造函数。构造函数是用来初始化新建对象的。如 §8.2.3 中所述构造函数用 new 关键字来调用。使用 new 调用构造函数会自动创建一个新对象，因此构造函数本身只需初始化这个新对象的状态即可。§6.2.3 介绍并强调了虽然所有对象都有原型，但是只有一部分对象有一个 prototype 属性。最后，我们可以澄清这一点：是函数对象具有 prototype 属性。这意味着所有用同一构造函数创建的对象继承同一个对象，因此它们是同一类的成员。示例 9-2 说明了如何使用一个构造函数来替代示例 9-1 中的工厂函数来修改 Range 类。示例 9-2 演示了在不支持 ES6 class 关键字版本的 JavaScript 中创建一个类的通用方法。即使是 class 已经很好支持的今天，仍然有很多旧 JavaScript 代码用这种方式定义类，并且你必须熟悉这种习惯用法，以便于阅读旧代码，也能够在使用 class 关键字时明白在底层中发生了什么。\n\nExample 9-2. A Range class using a constructor\n\n> 示例 9-2：使用构造函数的 Range 类\n\n```js\n// This is a constructor function that initializes new Range objects.\n// Note that it does not create or return the object. It just initializes this.\nfunction Range(from, to) {\n    // Store the start and end points (state) of this new range object.\n    // These are noninherited properties that are unique to this object.\n    this.from = from;\n    this.to = to;\n}\n\n// All Range objects inherit from this object.\n// Note that the property name must be \"prototype\" for this to work.\nRange.prototype = {\n    // Return true if x is in the range, false otherwise\n    // This method works for textual and Date ranges as well as numeric.\n    includes: function(x) { return this.from <= x && x <= this.to; },\n\n    // A generator function that makes instances of the class iterable.\n    // Note that it only works for numeric ranges.\n    [Symbol.iterator]: function*() {\n        for(let x = Math.ceil(this.from); x <= this.to; x++) yield x;\n    },\n\n    // Return a string representation of the range\n    toString: function() { return \"(\" + this.from + \"...\" + this.to + \")\"; }\n};\n\n// Here are example uses of this new Range class\nlet r = new Range(1,3);   // Create a Range object; note the use of new\nr.includes(2)             // => true: 2 is in the range\nr.toString()              // => \"(1...3)\"\n[...r]                    // => [1, 2, 3]; convert to an array via iterator\n```\nIt is worth comparing Examples 9-1 and 9-2 fairly carefully and noting the differences between these two techniques for defining classes. First, notice that we renamed the range() factory function to Range() when we converted it to a constructor. This is a very common coding convention: constructor functions define, in a sense, classes, and classes have names that (by convention) begin with capital letters. Regular functions and methods have names that begin with lowercase letters.\n\n> 将示例 9-1 和示例 9-2 中的代码做一个仔细的对比，可以发现两种类定义技术的差别。首先，注意当工厂函数 range() 转化为构造函数时被重命名为 Range()。这里遵循了一个常见的编程约定：从某种意义上讲，定义构造函数即是定义类，并且类名首字母要大写。而普通的函数和方法都是首字母小写。\n\nNext, notice that the Range() constructor is invoked (at the end of the example) with the new keyword while the range() factory function was invoked without it. Example 9-1 uses regular function invocation (§8.2.1) to create the new object, and Example 9-2 uses constructor invocation (§8.2.3). Because the Range() constructor is invoked with new, it does not have to call Object.create() or take any action to create a new object. The new object is automatically created before the constructor is called, and it is accessible as the this value. The Range() constructor merely has to initialize this. Constructors do not even have to return the newly created object. Constructor invocation automatically creates a new object, invokes the constructor as a method of that object, and returns the new object. The fact that constructor invocation is so different from regular function invocation is another reason that we give constructors names that start with capital letters. Constructors are written to be invoked as constructors, with the new keyword, and they usually won’t work properly if they are invoked as regular functions. A naming convention that keeps constructor functions distinct from regular functions helps programmers know when to use new.\n\n> 再者，注意 Range() 构造函数是通过 new 关键字调用的（在示例代码的末尾）， 而 range() 工厂函数则不必使用 new。示例 9-1 通过调用普通函数（§8.2.1）来创建新对象，示例 9-2 则使用构造函数调用（§8.2.3）来创建新对象。由于 Range() 构造函数是通过 new 关键字调用的，因此不必调用 Object.create() 或其他什么逻辑来创建新对象。在调用构造函数之前就已经创建了新对象，并且通过 this 关键字可以获取这个新对象。Range() 构造函数只不过是初始化 this 而已。构造函数甚至不必返回这个新创建的对象，构造函数会自动创建对象，然后将构造函数作为这个对象的方法来调用一次，最后返回这个新对象。构造函数调用与常规函数调用如此不同，实际上，这是我们为构造函数命名以大写字母为名的另一个原因。构造函数就是用来“构造新对象”的，它必须通过关键字 new 调用，如果将构造函数用做普通函数的话，往往不会正常工作。开发者可以通过命名规约来（构造函数首字母大写，普通方法首字母小写）判断是否应当在函数之前冠以关键字 new。\n\n**CONSTRUCTORS AND NEW.TARGET**\nWithin a function body, you can tell whether the function has been invoked as a constructor with the special expression new.target. If the value of that expression is defined, then you know that the function was invoked as a constructor, with the new keyword. When we discuss subclasses in §9.5, we’ll see that new.target is not always a reference to the constructor it is used in: it might also refer to the constructor function of a subclass.\n\n> 在函数正文中，可以使用特殊的表达式 new.target 来判断函数是否以构造函数的方式调用。如果定义了该表达式的值，那么这个函数是通过 new 关键字调用的构造函数。当我们在 §9.5 中讨论子类时，我们会看到 new.target 不总是其所在的构造函数的引用：它可能还引用子类的构造函数。\n\nIf new.target is undefined, then the containing function was invoked as a function, without the new keyword. JavaScript’s various error constructors can be invoked without new, and if you want to emulate this feature in your own constructors, you can write them like this:\n\n> 如果 new.target 是 undefined，那么包含它的函数是作为函数调用的，没有使用 new 关键字。JavaScript 很多异常构造函数可以不使用 new 来调用，如果想模仿这个特性可以这样写：\n\n```js\nfunction C() {\n    if (!new.target) return new C();\n    // initialization code goes here\n}\n```\nThis technique only works for constructors defined in this old-fashioned way. Classes created with the class keyword do not allow their constructors to be invoked without new.\n\n> 这个老式技巧只在构造函数定义时生效。类使用 class 关键字创建，不允许不使用 new 调用它的构造函数。\n\nAnother critical difference between Examples 9-1 and 9-2 is the way the prototype object is named. In the first example, the prototype was range.methods. This was a convenient and descriptive name, but arbitrary. In the second example, the prototype is Range.prototype, and this name is mandatory. An invocation of the Range() constructor automatically uses Range.prototype as the prototype of the new Range object.\n\n> 示例 9-1 和 9-2 之间的另一个关键区别是原型对象的命名方式。在第一个示例中，原型是 range.methods。这是一个方便和描述性的名称，但随意。在第二个示例中，原型为 Range.prototype，此名称是规定的。Range() 构造函数的调用自动使用 Range.prototype 作为新 Range 对象的原型。\n\nFinally, also note the things that do not change between Examples 9-1 and 9-2 : the range methods are defined and invoked in the same way for both classes. Because Example 9-2 demonstrates the idiomatic way to create classes in versions of JavaScript before ES6, it does not use the ES6 shorthand method syntax in the prototype object and explicitly spells out the methods with the function keyword. But you can see that the implementation of the methods is the same in both examples.\n\n> 最后，还要注意示例 9-1 和 9-2 之间不变的部分：对两个类来说，range 方法以相同的方式定义和调用。由于示例 9-2 演示了在 ES6 之前在 JavaScript 版本中创建类的惯用方法，因此它没原型对象中使用 ES6 速记方法语法，并且使用 function 关键字显式拼出方法名。但是，可以看到，在这两个示例中，方法的实现是相同的。\n\nImportantly, note that neither of the two range examples uses arrow functions when defining constructors or methods. Recall from §8.1.3 that functions defined in this way do not have a prototype property and so cannot be used as constructors. Also, arrow functions inherit the this keyword from the context in which they are defined rather than setting it based on the object through which they are invoked, and this makes them useless for methods because the defining characteristic of methods is that they use this to refer to the instance on which they were invoked.\n\n> 请注意，在定义构造函数或方法时，两个 range 示例都没使用箭头函数。回想一下，从 §8.1.3 中，用这种方式定义的函数没有原型属性，因此不能用作构造函数。此外，箭头函数从定义它们的上下文中继承 this 关键字，而不是基于调用它们的对象设置 this 值，这使得箭头函数对方法毫无用处，因为方法的定义特征是方法使用 this 关键字来引用调用方法的实例。\n\nFortunately, the new ES6 class syntax doesn’t allow the option of defining methods with arrow functions, so this is not a mistake that you can accidentally make when using that syntax. We will cover the ES6 class keyword soon, but first, there are more details to cover about constructors.\n\n> 幸运的是，新的 ES6 类语法不允许使用 arrow 函数定义方法，因此这不是使用该语法时可能会意外犯的错误。我们将很快介绍 ES6 类关键字，但首先，有更多关于构造函数的详细信息。\n\n### 9.2.1 Constructors, Class Identity, and instanceof\nAs we’ve seen, the prototype object is fundamental to the identity of a class: two objects are instances of the same class if and only if they inherit from the same prototype object. The constructor function that initializes the state of a new object is not fundamental: two constructor functions may have prototype properties that point to the same prototype object. Then, both constructors can be used to create instances of the same class.\n\n> 正如我们所看到的，原型对象是类的基本标识：只有两个对象继承同一原型对象时，这两个对象是同一类实例。构造函数的关键点不是初始化新创建对象的状态：两个构造函数可能具有指向同一原型对象的原型属性。那么，两个构造函数都可用于创建同一类的实例。\n\nEven though constructors are not as fundamental as prototypes, the constructor serves as the public face of a class. Most obviously, the name of the constructor function is usually adopted as the name of the class. We say, for example, that the Range() constructor creates Range objects. More fundamentally, however, constructors are used as the righthand operand of the instanceof operator when testing objects for membership in a class. If we have an object r and want to know if it is a Range object, we can write:\n\n> 尽管构造函数不像原型那样重要，但是构造函数充当 class 的大众脸。最明显的是，构造函数的名称通常用作类的名称。例如，我们说 Range() 构造函数创建 Range 对象。然而，更重要的是测试类中对象的成员关系，构造函数在右边被用作 instanceof 运算符的操作数。如果有一个对象 r，并且想知道它是不是一个 Range 对象，可以这样写：\n\n```js\nr instanceof Range   // => true: r inherits from Range.prototype\n```\nThe instanceof operator was described in §4.9.4. The lefthand operand should be the object that is being tested, and the righthand operand should be a constructor function that names a class. The expression o instanceof C evaluates to true if o inherits from C.prototype. The inheritance need not be direct: if o inherits from an object that inherits from an object that inherits from C.prototype, the expression will still evaluate to true.\n\n> instanceof 运算符在 §4.9.4 中有描述。左边的操作数是想要测试的对象，右边的操作数是命名类的构造函数。表达式 `o instanceof C` 计算结果为 true 时，o 继承自 C.prototype。不需要直接继承：如果 o 继承于一个继承了 C.prototype 的对象，表达式的计算结果也仍会是 true。\n\nTechnically speaking, in the previous code example, the instanceof operator is not checking whether r was actually initialized by the Range constructor. Instead, it is checking whether r inherits from Range.prototype. If we define a function Strange() and set its prototype to be the same as Range.prototype, then objects created with new Strange() will count as Range objects as far as instanceof is concerned (they won’t actually work as Range objects, however, because their from and to properties have not been initialized):\n\n> 从技术上讲，在上一个代码示例中，instanceof 运算符的实例没有检查 r 是否实际由 Range 构造函数初始化。相反，它是检查 r 是否继承 Range.prototype。如果我们定义一个函数 Strange() 并将其原型设置与 Range.prototype 相同，则使用新 Strange() 创建的对象用 instanceof 运算符将算作 Range 对象（但是，它们实际上不会作为 Range 对象工作，因为它们的 from 和 to 属性尚未初始化）：\n\n```js\nfunction Strange() {}\nStrange.prototype = Range.prototype;\nnew Strange() instanceof Range   // => true\n```\nEven though instanceof cannot actually verify the use of a constructor, it still uses a constructor function as its righthand side because constructors are the public identity of a class.\n\n> 实际上即使 instanceof 不能验证使用了构造函数，它仍然使用构造函数作为其右侧，因为构造函数是类的公共标识。\n\nIf you want to test the prototype chain of an object for a specific prototype and do not want to use the constructor function as an intermediary, you can use the isPrototypeOf() method. In Example 9-1, for example, we defined a class without a constructor function, so there is no way to use instanceof with that class. Instead, however, we could test whether an object r was a member of that constructor-less class with this code:\n\n> 如果要为特定原型测试对象的原型链，并且不想将构造函数用作媒介，可以使用 isPrototypeOf() 方法。例如，在示例 9-1 中，我们定义了一个没有构造函数的类，因此无法将 instanceof 与该类一起使用。但是，我们可以测试对象 r 是否是具有此代码的无构造函数类的成员：\n\n```js\nrange.methods.isPrototypeOf(r);  // range.methods is the prototype object.\n```\n### 9.2.2 The constructor Property\nIn Example 9-2, we set Range.prototype to a new object that contained the methods for our class. Although it was convenient to express those methods as properties of a single object literal, it was not actually necessary to create a new object. Any regular JavaScript function (excluding arrow functions, generator functions, and async functions) can be used as a constructor, and constructor invocations need a prototype property. Therefore, every regular JavaScript function1 automatically has a prototype property. The value of this property is an object that has a single, non-enumerable constructor property. The value of the constructor property is the function object:\n\n> 在示例 9-2 中，将 Range.prototype 定义为一个新对象，这个对象包含类所需要的方法。其实没有必要新创建一个对象，用单个对象字面量的属性就可以方便地定义原型上的方法。任何普通 JavaScript 函数（除箭头函数、生成器函数和异步函数之外）都可以用做构造函数，并且调用构造函数是需要一个 prototype 属性的。因此，每个 JavaScript 函数都自动拥有一个 prototype 属性。这个属性的值是一个对象，这个对象包含唯一一个不可枚举属性 constructor。constructor 属性的值是一个函数对象：\n\n```js\nlet F = function() {}; // This is a function object.\nlet p = F.prototype;   // This is the prototype object associated with F.\nlet c = p.constructor; // This is the function associated with the prototype.\nc === F                // => true: F.prototype.constructor === F for any F\n```\nThe existence of this predefined prototype object with its constructor property means that objects typically inherit a constructor property that refers to their constructor. Since constructors serve as the public identity of a class, this constructor property gives the class of an object:\n\n> 可以看到构造函数的原型中存在预先定义好的 constructor 属性，这意味着对象通常继承的 constructor 是它们的构造函数的引用。由于构造函数是类的“公共标识”， 因此这个 constructor 属性为对象提供了类。\n\n```js\nlet o = new F();      // Create an object o of class F\no.constructor === F   // => true: the constructor property specifies the class\n```\nFigure 9-1 illustrates this relationship between the constructor function, its prototype object, the back reference from the prototype to the constructor, and the instances created with the constructor.\n\n> 如图 9-1 所示，图 9-1 展示了构造函数和原型对象之间的关系，包括原型到构造函数的反向引用以及构造函数创建的实例。\n\n<Figures figure=\"9-1\">A constructor function, its prototype, and instances</Figures>\n\nNotice that Figure 9-1 uses our Range() constructor as an example. In fact, however, the Range class defined in Example 9-2 overwrites the predefined Range.prototype object with an object of its own. And the new prototype object it defines does not have a constructor property. So instances of the Range class, as defined, do not have a constructor property. We can remedy this problem by explicitly adding a constructor to the prototype:\n\n> 需要注意的是，图 9-1 用 Range() 构造函数作为示例。但实际上，示例 9-2 中定义的 Range 类使用它自身的一个新对象重写了预定义的 Range.prototype 对象。这个新定义的原型对象不含有 constructor 属性。因此 Range 类的实例也不含有 constructor 属性。我们可以通过补救措施来修正这个问题，显式给原型添加一个构造函数：\n\n```js\nRange.prototype = {\n    constructor: Range,  // Explicitly set the constructor back-reference\n\n    /* method definitions go here */\n};\n```\nAnother common technique that you are likely to see in older JavaScript code is to use the predefined prototype object with its constructor property and add methods to it one at a time with code like this:\n\n> 另一种常见的解决办法是使用预定义的原型对象，预定义的原型对象包含 constructor 属性，然后依次给原型对象添加方法：\n\n```js\n// Extend the predefined Range.prototype object so we don't overwrite\n// the automatically created Range.prototype.constructor property.\nRange.prototype.includes = function(x) {\n    return this.from <= x && x <= this.to;\n};\nRange.prototype.toString = function() {\n    return \"(\" + this.from + \"...\" + this.to + \")\";\n};\n```\n## 9.3 Classes with the class Keyword\nClasses have been part of JavaScript since the very first version of the language, but in ES6, they finally got their own syntax with the introduction of the class keyword. Example 9-3 shows what our Range class looks like when written with this new syntax.\n\n> 类自第一个版本以来一直是 JavaScript 的一部分，但在 ES6 中，它们最终引入 class 关键字得到了自己的语法。示例 9-3 显示了使用此新语法编写 Range 类的实现。\n\nExample 9-3. The Range class rewritten using class\n\n> 示例 9-3：使用 class 编写 Range 类\n\n```js\nclass Range {\n    constructor(from, to) {\n        // Store the start and end points (state) of this new range object.\n        // These are noninherited properties that are unique to this object.\n        this.from = from;\n        this.to = to;\n    }\n\n    // Return true if x is in the range, false otherwise\n    // This method works for textual and Date ranges as well as numeric.\n    includes(x) { return this.from <= x && x <= this.to; }\n\n    // A generator function that makes instances of the class iterable.\n    // Note that it only works for numeric ranges.\n    *[Symbol.iterator]() {\n        for(let x = Math.ceil(this.from); x <= this.to; x++) yield x;\n    }\n\n    // Return a string representation of the range\n    toString() { return `(${this.from}...${this.to})`; }\n}\n\n// Here are example uses of this new Range class\nlet r = new Range(1,3);   // Create a Range object\nr.includes(2)             // => true: 2 is in the range\nr.toString()              // => \"(1...3)\"\n[...r]                    // => [1, 2, 3]; convert to an array via iterator\n```\nIt is important to understand that the classes defined in Examples 9-2 and 9-3 work in exactly the same way. The introduction of the class keyword to the language does not alter the fundamental nature of JavaScript’s prototype-based classes. And although Example 9-3 uses the class keyword, the resulting Range object is a constructor function, just like the version defined in Example 9-2. The new class syntax is clean and convenient but is best thought of as “syntactic sugar” for the more fundamental class definition mechanism shown in Example 9-2.\n\n> 重要的是要了解，在示例 9-2 和 9-3 中定义的类的工作方式完全相同。将 class 关键字引入语言并不会改变 JavaScript 基于原型的类的基本性质。尽管示例 9-3 使用 class 关键字，但生成的 Range 对象是一个构造函数，就像示例 9-2 中定义的版本一样。新的 class 语法更清洁方便，但是最好将其看作示例 9-2 所示的基本类定义机制的语法糖。\n\nNote the following things about the class syntax in Example 9-3:\n\n> 注意在示例 9-3 中关于 class 语法的一下几点：\n\nThe class is declared with the class keyword, which is followed by the name of class and a class body in curly braces.\n\n> 用 class 关键字声明类，后面接一个类名，最后是花括号包含类的正文。\n\nThe class body includes method definitions that use object literal method shorthand (which we also used in Example 9-1), where the function keyword is omitted. Unlike object literals, however, no commas are used to separate the methods from each other. (Although class bodies are superficially similar to object literals, they are not the same thing. In particular, they do not support the definition of properties with name/value pairs.)\n\n> 类正文包括使用对象字面量方法速记定义的方法（我们在示例 9-1 中也使用了），其中省略了函数关键字。但是，与对象字面量不同，没有用逗号将方法彼此分开。（虽然类正文表面上与对象字面量相似，但它们不是一回事。与对象不同，类不支持具有名/值对的属性的定义。\n\nThe keyword constructor is used to define the constructor function for the class. The function defined is not actually named “constructor”, however. The class declaration statement defines a new variable Range and assigns the value of this special constructor function to that variable.\n\n> 关键字 constructor 用于定义类的构造函数。但是，定义的函数实际上并不命名为 constructor。类声明语句定义一个新的变量 Range，并将此特殊构造函数的值分配给该变量。\n\nIf your class does not need to do any initialization, you can omit the constructor keyword and its body, and an empty constructor function will be implicitly created for you.\n\n> 如果类不需要执行任何初始化，可以省略构造函数关键字及其正文，并将隐式创建一个空构造函数。\n\nIf you want to define a class that subclasses—or inherits from—another class, you can use the extends keyword with the class keyword:\n\n> 如果要定义子类（或继承来自另一个类的类），可以使用 extends 关键字与 class 关键字：\n\n```js\n// A Span is like a Range, but instead of initializing it with\n// a start and an end, we initialize it with a start and a length\nclass Span extends Range {\n    constructor(start, length) {\n        if (length >= 0) {\n            super(start, start + length);\n        } else {\n            super(start + length, start);\n        }\n    }\n}\n```\nCreating subclasses is a whole topic of its own. We’ll return to it, and explain the extends and super keywords shown here, in §9.5.\n\n> 创建子类是完整的一节。我们将在 §9.5 重新讲到它，并解释这里的 extends 和 super 关键字。\n\nLike function declarations, class declarations have both statement and expression forms. Just as we can write:\n\n> 与函数声明一样，类声明同时具有语句和表达式形式。正如我们可以写：\n\n```js\nlet square = function(x) { return x * x; };\nsquare(3)  // => 9\nwe can also write:\n\nlet Square = class { constructor(x) { this.area = x * x; } };\nnew Square(3).area  // => 9\n```\nAs with function definition expressions, class definition expressions can include an optional class name. If you provide such a name, that name is only defined within the class body itself.\n\n> 与函数定义表达式一样，类定义表达式可以包含可选类名。如果提供这样的名称，则该名称仅在类正文本身中有定义。\n\nAlthough function expressions are quite common (particularly with the arrow function shorthand), in JavaScript programming, class definition expressions are not something that you are likely to use much unless you find yourself writing a function that takes a class as its argument and returns a subclass.\n\n> 尽管函数表达式很常见（尤其是使用箭头函数），但 JavaScript 编程中，类定义表达式可能并不是经常使用，除非正在编写一个以类为实参并返回子类的函数。\n\nWe’ll conclude this introduction to the class keyword by mentioning a couple of important things you should know that are not apparent from class syntax:\n\n> 结束对 class 关键字的介绍前，最后提几个重要但是不易注意的类语法：\n\nAll code within the body of a class declaration is implicitly in strict mode (§5.6.3), even if no \"use strict\" directive appears. This means, for example, that you can’t use octal integer literals or the with statement within class bodies and that you are more likely to get syntax errors if you forget to declare a variable before using it.\n\n> 类声明正文中的所有代码都隐式采用严格模式（§5.6.3），即使未出现\"use strict\"指令。例如，这意味着不能在类正文中使用八进制整数字面量或 with 语句，并且如果在使用变量之前忘记声明变量，则更有可能出现语法错误。\n\nUnlike function declarations, class declarations are not “hoisted.” Recall from §8.1.1 that function definitions behave as if they had been moved to the top of the enclosing file or enclosing function, meaning that you can invoke a function in code that comes before the actual definition of the function. Although class declarations are like function declarations in some ways, they do not share this hoisting behavior: you cannot instantiate a class before you declare it.\n\n> 与函数声明不同，类声明不是\"声明提前\"的。回想一下 §8.1.1 中，函数定义的行为就像它们被移动到封闭文件的顶部或封闭函数的顶部一样，这意味着可以在函数实际定义之前的代码中调用函数。尽管类声明在某些方面与函数声明一样，但它们不共享这种提前行为：在声明类之前，不能实例化类。\n\n### 9.3.1 Static Methods\nYou can define a static method within a class body by prefixing the method declaration with the static keyword. Static methods are defined as properties of the constructor function rather than properties of the prototype object.\n\n> 可以通过使用 static 关键字作为方法声明前缀来定义类正文中的静态方法。静态方法定义为构造函数的属性，而不是原型对象的属性。\n\nFor example, suppose we added the following code to Example 9-3:\n\n> 例如，假设我们将以下代码添加到示例 9-3 中：\n\n```js\nstatic parse(s) {\n    let matches = s.match(/^\\((\\d+)\\.\\.\\.(\\d+)\\)$/);\n    if (!matches) {\n        throw new TypeError(`Cannot parse Range from \"${s}\".`)\n    }\n    return new Range(parseInt(matches[1]), parseInt(matches[2]));\n}\n```\nThe method defined by this code is Range.parse(), not Range.prototype.parse(), and you must invoke it through the constructor, not through an instance:\n\n> 此代码定义的方法是 Range.parse()， 而不是 Range.prototype.parse()， 必须通过构造函数而不是通过实例调用它：\n\n```js\nlet r = Range.parse('(1...10)'); // Returns a new Range object\nr.parse('(1...10)');             // TypeError: r.parse is not a function\n```\nYou’ll sometimes see static methods called class methods because they are invoked using the name of the class/constructor. When this term is used, it is to contrast class methods with the regular instance methods that are invoked on instances of the class. Because static methods are invoked on the constructor rather than on any particular instance, it almost never makes sense to use the this keyword in a static method.\n\n> 有时会看到静态方法称为类方法，因为它们是使用类/构造函数的名称调用的。使用此术语时，将类方法与在类实例上调用的常规实例方法进行对比，由于静态方法在构造函数上调用，而不是在任何特定实例上调用，因此在静态方法中使用 this 关键字几乎从来就没有意义。\n\nWe’ll see examples of static methods in Example 9-4.\n\n> 我们将在示例 9-4 中看到静态方法的示例。\n\n### 9.3.2 Getters, Setters, and other Method Forms\nWithin a class body, you can define getter and setter methods (§6.10.6) just as you can in object literals. The only difference is that in class bodies, you don’t put a comma after the getter or setter. Example 9-4 includes a practical example of a getter method in a class.\n\n> 在类正文中，可以定义 getter 和 setter 方法（§6.10.6），就像在对象字面量中一样。唯一的区别是，在类正文中，不会将逗号放在 getter 或 setter 之后。示例 9-4 包括类中 getter 方法的实际示例。\n\nIn general, all of the shorthand method definition syntaxes allowed in object literals are also allowed in class bodies. This includes generator methods (marked with *) and methods whose names are the value of an expression in square brackets. In fact, you’ve already seen (in Example 9-3) a generator method with a computed name that makes the Range class iterable:\n\n> 通常，对象字面量中允许的所有速记方法定义语法也允许在类正文中使用。这包括生成器方法（用 * 标记）和名称为方括号中表达式值的方法。事实上，已经看到了（在示例 9-3 中）具有计算名称的生成器方法，该方法使 Range 类可重复：\n\n```js\n*[Symbol.iterator]() {\n    for(let x = Math.ceil(this.from); x <= this.to; x++) yield x;\n}\n```\n### 9.3.3 Public, Private, and Static Fields\nIn the discussion here of classes defined with the class keyword, we have only described the definition of methods within the class body. The ES6 standard only allows the creation of methods (including getters, setters, and generators) and static methods; it does not include syntax for defining fields. If you want to define a field (which is just an object-oriented synonym for “property”) on a class instance, you must do that in the constructor function or in one of the methods. And if you want to define a static field for a class, you must do that outside the class body, after the class has been defined. Example 9-4 includes examples of both kinds of fields.\n\n> 在此处对使用 class 关键字定义的类的讨论中，我们只描述了类正文中方法的定义。ES6 标准只允许创建方法（包括 getter、setter 和生成器）和静态方法；它不包括用于定义字段的语法。如果要在类实例上定义字段（这只是面向对象中\"属性\"的同义词），则必须在构造函数函数或其中一个方法中这样做。必须在类正文之外类定义后，才能为类定义静态字段。示例 9-4 包括这各种字段的示例。\n\nStandardization is underway, however, for extended class syntax that allows the definition of instance and static fields, in both public and private forms. The code shown in the rest of this section is not yet standard JavaScript as of early 2020 but is already supported in Chrome and partially supported (public instance fields only) in Firefox. The syntax for public instance fields is in common use by JavaScript programmers using the React framework and the Babel transpiler.\n\n> 但是，对于允许以公有和私有形式定义实例和静态字段的扩展类语法正在进行标准化。本节其余部分中显示的代码在 2020 年初还不是标准 JavaScript，但在 Chrome 中已经支持，并且 Firefox 中已部分支持（仅使公有实例字段）。使用 React 框架和 Babel 编译器的 JavaScript 程序员常用公有实例字段的语法。\n\nSuppose you’re writing a class like this one, with a constructor that initializes three fields:\n\n> 假设你正在编写一个这样的类，其中一个构造函数初始化了三个字段：\n\n```js\nclass Buffer {\n    constructor() {\n        this.size = 0;\n        this.capacity = 4096;\n        this.buffer = new Uint8Array(this.capacity);\n    }\n}\n```\nWith the new instance field syntax that is likely to be standardized, you could instead write:\n\n> 使用可能标准化的新实例字段语法，可以这样编写：\n\n```js\nclass Buffer {\n    size = 0;\n    capacity = 4096;\n    buffer = new Uint8Array(this.capacity);\n}\n```\nThe field initialization code has moved out of the constructor and now appears directly in the class body. (That code is still run as part of the constructor, of course. If you do not define a constructor, the fields are initialized as part of the implicitly created constructor.) The this. prefixes that appeared on the lefthand side of the assignments are gone, but note that you still must use this. to refer to these fields, even on the righthand side of the initializer assignments. The advantage of initializing your instance fields in this way is that this syntax allows (but does not require) you to put the initializers up at the top of the class definition, making it clear to readers exactly what fields will hold the state of each instance. You can declare fields without an initializer by just writing the name of the field followed by a semicolon. If you do that, the initial value of the field will be undefined. It is better style to always make the initial value explicit for all of your class fields.\n\n> 字段初始化代码已移出构造函数，现在直接显示在类正文中。（当然，该代码仍作为构造函数的一部分运行。如果不定义构造函数，则字段初始化为隐式创建的构造函数的一部分。赋值左侧的 this. 前缀消失，但请注意即使是在初始化赋值的右侧，仍必须使用 this. 前缀引用这些字段。这种方式初始化实例字段的优点是，此语法允许（但不需要）将初始化放在类定义的顶部，使读者清楚地了解字段在每个实例将保存的状态。可以通过字段名后面跟一个分号来只声明不初始化一个字段。如果这样做，字段的初始值将是 undefined。显式设定初始化字段的值是比较好的风格。\n\nBefore the addition of this field syntax, class bodies looked a lot like object literals using shortcut method syntax, except that the commas had been removed. This field syntax—with equals signs and semicolons instead of colons and commas—makes it clear that class bodies are not at all the same as object literals.\n\n> 在添加字段语法之前，类正文看起来很像使用快捷方法语法的对象字面量，只不过逗号被删除。字段语法（使用等号和分号代替冒号和逗号）清楚地表明类正文与对象字面量不完全相同。\n\nThe same proposal that seeks to standardize these instance fields also defines private instance fields. If you use the instance field initialization syntax shown in the previous example to define a field whose name begins with # (which is not normally a legal character in JavaScript identifiers), that field will be usable (with the # prefix) within the class body but will be invisible and inaccessible (and therefore immutable) to any code outside of the class body. If, for the preceding hypothetical Buffer class, you wanted to ensure that users of the class could not inadvertently modify the size field of an instance, you could use a private #size field instead, then define a getter function to provide read-only access to the value:\n\n> 标准化中的实例字段同时也定义了私有实例字段。如果使用上例中所示的实例字段初始化语法来定义其名称以 # 开头的字段（在 JavaScript 标识符中通常不是合法字符），则该字段在类正文中可用（使用 # 前缀），但对类正文之外的任何代码不可见且不可访问（因此不可变）。如果对于前面的 Buffer 类，要确保类的用户不会无意中修改实例的 size 字段，可以改为使用私有 #size 字段，然后定义 getter 函数以提供对值的只读访问：\n\n```js\nclass Buffer {\n    #size = 0;\n    get size() { return this.#size; }\n}\n```\nNote that private fields must be declared using this new field syntax before they can be used. You can’t just write this.#size = 0; in the constructor of a class unless you include a “declaration” of the field directly in the class body.\n\n> 请注意，必须先使用新字段语法声明私有字段，然后才能使用它们。你不能只在类的构造函数中写 `this.#size = 0;`，除非直接在类正文中包含字段的\"声明\"。\n\nFinally, a related proposal seeks to standardize the use of the static keyword for fields. If you add static before a public or private field declaration, those fields will be created as properties of the constructor function instead of properties of instances. Consider the static Range.parse() method we’ve defined. It included a fairly complex regular expression that might be good to factor out into its own static field. With the proposed new static field syntax, we could do that like this:\n\n> 最后，在标准化过程中相关建议字段使用 static 关键字。如果在公有或私有字段声明之前添加静态字段，这些字段将创建为构造函数的属性，而不是实例的属性。思考我们定义的静态 Range.parse() 方法。它包括一个相当复杂的正则表达式，将其拆分到它自有的静态字段中可能会更好。使用建议的新静态字段语法，我们可以这编写：\n\n```js\nstatic integerRangePattern = /^\\((\\d+)\\.\\.\\.(\\d+)\\)$/;\nstatic parse(s) {\n    let matches = s.match(Range.integerRangePattern);\n    if (!matches) {\n        throw new TypeError(`Cannot parse Range from \"${s}\".`)\n    }\n    return new Range(parseInt(matches[1]), matches[2]);\n}\n```\nIf we wanted this static field to be accessible only within the class, we could make it private using a name like #pattern.\n\n> 如果我们希望此静态字段只能在类中访问，我们可以使用像 #pattern 这样的名称将其私有化。\n\n### 9.3.4 Example: A Complex Number Class\nExample 9-4 defines a class to represent complex numbers. The class is a relatively simple one, but it includes instance methods (including getters), static methods, instance fields, and static fields. It includes some commented-out code demonstrating how we might use the not-yet-standard syntax for defining instance fields and static fields within the class body.\n\n> 示例 9-4 定义了一个表示复数的类。该类相对简单，但它包括实例方法（包括 getters）、静态方法、实例字段和静态字段。它包括一些注释掉的代码，演示如何使用尚未加入标准的语法定义类正文中的实例字段和静态字段。\n\nExample 9-4. Complex.js: a complex number class\n\n> 示例 9-4：Complex.js：一个复数类\n\n```js\n/**\n * Instances of this Complex class represent complex numbers.\n * Recall that a complex number is the sum of a real number and an\n * imaginary number and that the imaginary number i is the square root of -1.\n */\nclass Complex {\n    // Once class field declarations are standardized, we could declare\n    // private fields to hold the real and imaginary parts of a complex number\n    // here, with code like this:\n    //\n    // #r = 0;\n    // #i = 0;\n\n    // This constructor function defines the instance fields r and i on every\n    // instance it creates. These fields hold the real and imaginary parts of\n    // the complex number: they are the state of the object.\n    constructor(real, imaginary) {\n        this.r = real;       // This field holds the real part of the number.\n        this.i = imaginary;  // This field holds the imaginary part.\n    }\n\n    // Here are two instance methods for addition and multiplication\n    // of complex numbers. If c and d are instances of this class, we\n    // might write c.plus(d) or d.times(c)\n    plus(that) {\n        return new Complex(this.r + that.r, this.i + that.i);\n    }\n    times(that) {\n        return new Complex(this.r * that.r - this.i * that.i,\n                           this.r * that.i + this.i * that.r);\n    }\n\n    // And here are static variants of the complex arithmetic methods.\n    // We could write Complex.sum(c,d) and Complex.product(c,d)\n    static sum(c, d) { return c.plus(d); }\n    static product(c, d) { return c.times(d); }\n\n    // These are some instance methods that are defined as getters\n    // so they're used like fields. The real and imaginary getters would\n    // be useful if we were using private fields this.#r and this.#i\n    get real() { return this.r; }\n    get imaginary() { return this.i; }\n    get magnitude() { return Math.hypot(this.r, this.i); }\n\n    // Classes should almost always have a toString() method\n    toString() { return `{${this.r},${this.i}}`; }\n\n    // It is often useful to define a method for testing whether\n    // two instances of your class represent the same value\n    equals(that) {\n        return that instanceof Complex &&\n            this.r === that.r &&\n            this.i === that.i;\n    }\n\n    // Once static fields are supported inside class bodies, we could\n    // define a useful Complex.ZERO constant like this:\n    // static ZERO = new Complex(0,0);\n}\n\n// Here are some class fields that hold useful predefined complex numbers.\nComplex.ZERO = new Complex(0,0);\nComplex.ONE = new Complex(1,0);\nComplex.I = new Complex(0,1);\n```\n\nWith the Complex class of Example 9-4 defined, we can use the constructor, instance fields, instance methods, class fields, and class methods with code like this:\n\n> 定义了示例 9-4 的 Complex 类后，我们可以将构造函数、实例字段、实例方法、类字段和类方法如下使用：\n\n```js\nlet c = new Complex(2, 3);     // Create a new object with the constructor\nlet d = new Complex(c.i, c.r); // Use instance fields of c\nc.plus(d).toString()           // => \"{5,5}\"; use instance methods\nc.magnitude                    // => Math.hypot(2,3); use a getter function\nComplex.product(c, d)          // => new Complex(0, 13); a static method\nComplex.ZERO.toString()        // => \"{0,0}\"; a static property\n```\n## 9.4 Adding Methods to Existing Classes\nJavaScript’s prototype-based inheritance mechanism is dynamic: an object inherits properties from its prototype, even if the properties of the prototype change after the object is created. This means that we can augment JavaScript classes simply by adding new methods to their prototype objects.\n\n> JavaScript 基于原型的继承机制是动态的：对象从其原型继承属性，即使原型的属性在创建对象后发生更改。这意味着我们只需向原型对象添加新方法，即可扩展 JavaScript 类。\n\nHere, for example, is code that adds a method for computing the complex conjugate to the Complex class of Example 9-4:\n\n> 例如，下面是将计算共轭复数的方法添加到示例 9-4 的 Complex 类的代码：\n\n```js\n// Return a complex number that is the complex conjugate of this one.\nComplex.prototype.conj = function() { return new Complex(this.r, -this.i); };\n```\n\nThe prototype object of built-in JavaScript classes is also open like this, which means that we can add methods to numbers, strings, arrays, functions, and so on. This is useful for implementing new language features in older versions of the language:\n\n> JavaScript 类内置的原型对象也是这样展现的，这意味着我们可以向数字、字符串、数组、函数等添加方法。这对于在旧版本的语言中实现新特性非常有用：\n\n```js\n// If the new String method startsWith() is not already defined...\nif (!String.prototype.startsWith) {\n    // ...then define it like this using the older indexOf() method.\n    String.prototype.startsWith = function(s) {\n        return this.indexOf(s) === 0;\n    };\n}\n```\n\nHere is another example:\n\n> 这是另外一个例子：\n\n```js\n// Invoke the function f this many times, passing the iteration number\n// For example, to print \"hello\" 3 times:\n//     let n = 3;\n//     n.times(i => { console.log(`hello ${i}`); });\nNumber.prototype.times = function(f, context) {\n    let n = this.valueOf();\n    for(let i = 0; i < n; i++) f.call(context, i);\n};\n```\nAdding methods to the prototypes of built-in types like this is generally considered to be a bad idea because it will cause confusion and compatibility problems in the future if a new version of JavaScript defines a method with the same name. It is even possible to add methods to Object.prototype, making them available for all objects. But this is never a good thing to do because properties added to Object.prototype are visible to for/in loops (though you can avoid this by using Object.defineProperty() [§14.1] to make the new property non-enumerable).\n\n> 向这样在内置类型的原型添加方法通常被认为是一个坏主意，因为如果新版本的 JavaScript 定义具有相同名称的方法，将来就会造成混淆和兼容性问题。甚至可以向 Object.prototype 添加方法，使它们可作用于所有对象。但这从来就不是一件好事，因为添加到 Object.prototype 的属性对 for/in 循环可见（尽管可以使用 Object.defineProperty()（§14.1）来使新属性不可枚举来避免这种情况）。\n\n## 9.5 Subclasses\nIn object-oriented programming, a class B can extend or subclass another class A. We say that A is the superclass and B is the subclass. Instances of B inherit the methods of A. The class B can define its own methods, some of which may override methods of the same name defined by class A. If a method of B overrides a method of A, the overriding method in B often needs to invoke the overridden method in A. Similarly, the subclass constructor B() must typically invoke the superclass constructor A() in order to ensure that instances are completely initialized.\n\n> 在面向对象的编程中，B 类可以扩展 A 类或成为 A 类子类。我们称 A 是父类，B 是子类。B 的实例继承 A 的方法。B 类可以定义自有方法，使用相同名称可以重写类 A 中的方法。如果 B 的方法重写 A 方法，则 B 中的重写方法通常需要调用 A 中的重写方法。同样，子类构造函数 B() 通常必须调用父类构造函数 A()，以确保实例完全初始化。\n\nThis section starts by showing how to define subclasses the old, pre-ES6 way, and then quickly moves on to demonstrate subclassing using the class and extends keywords and superclass constructor method invocation with the super keyword. Next is a subsection about avoiding subclasses and relying on object composition instead of inheritance. The section ends with an extended example that defines a hierarchy of Set classes and demonstrates how abstract classes can be used to separate interface from implementation.\n\n> 本节首先演示 ES6 之前如何定义子类，然后演示使用 class 和 extends 关键字的子类和使用 super 关键字调用父类构造函数。再接下来一节是关于避免使用子类而依靠对象组合代替继承。本章最后结束于一个扩展的示例，定义 Set 类的层次结构和演示如何使用抽象类将接口与实现分离。\n\n### 9.5.1 Subclasses and Prototypes\nSuppose we wanted to define a Span subclass of the Range class from Example 9-2. This subclass will work just like a Range, but instead of initializing it with a start and an end, we’ll instead specify a start and a distance, or span. An instance of this Span class is also an instance of the Range superclass. A span instance inherits a customized toString() method from Span.prototype, but in order to be a subclass of Range, it must also inherit methods (such as includes()) from Range.prototype.\n\n> 假设我们想要给示例 9-2 中 Range 类定义 Span 子类。此子类的工作方式与 Range 一样，但我们初始化改为指定开始和范围，而不是制定开始和结束。Span 类的实例也是父类 Range 的实例。Span 实例从 Span.prototype 继承自定义的 toString() 方法，但为了成为 Range 的子类，它还必须从 Range.prototype 继承方法（如 includes()）。\n\nExample 9-5. Span.js: a simple subclass of Range\n\n> 实例 9-5：Span.js：Range 一个简单的子类\n\n```js\n// This is the constructor function for our subclass\nfunction Span(start, span) {\n    if (span >= 0) {\n        this.from = start;\n        this.to = start + span;\n    } else {\n        this.to = start;\n        this.from = start + span;\n    }\n}\n\n// Ensure that the Span prototype inherits from the Range prototype\nSpan.prototype = Object.create(Range.prototype);\n\n// We don't want to inherit Range.prototype.constructor, so we\n// define our own constructor property.\nSpan.prototype.constructor = Span;\n\n// By defining its own toString() method, Span overrides the\n// toString() method that it would otherwise inherit from Range.\nSpan.prototype.toString = function() {\n    return `(${this.from}... +${this.to - this.from})`;\n};\n```\nIn order to make Span a subclass of Range, we need to arrange for Span.prototype to inherit from Range.prototype. The key line of code in the preceding example is this one, and if it makes sense to you, you understand how subclasses work in JavaScript:\n\n> 为了使 Span 成为 Range 的子类，我们需要使 Span.prototype 从 Range.prototype 继承。前面示例中的关键代码行是此代码行，如果能理解子类使如何工作的，它是非常有意义的：\n\n```js\nSpan.prototype = Object.create(Range.prototype);\n```\n\nObjects created with the Span() constructor will inherit from the Span.prototype object. But we created that object to inherit from Range.prototype, so Span objects will inherit from both Span.prototype and Range.prototype.\n\n> 使用 Span() 构造函数创建的对象将从 Span.prototype 对象继承。但是，我们创建了该对象并继承 Range.prototype，因此 Span 对象将同时从 Span.prototype 和 Range.prototype 继承。\n\nYou may notice that our Span() constructor sets the same from and to properties that the Range() constructor does and so does not need to invoke the Range() constructor to initialize the new object. Similarly, Span’s toString() method completely re-implements the string conversion without needing to call Range’s version of toString(). This makes Span a special case, and we can only really get away with this kind of subclassing because we know the implementation details of the superclass. A robust subclassing mechanism needs to allow classes to invoke the methods and constructor of their superclass, but prior to ES6, JavaScript did not have a simple way to do these things.\n\n> 可能会注意到，我们的 Span() 构造函数设置与 Range() 构造函数相同的 from 和 to 属性，因此不需要调用 Range() 构造函数来初始化新对象。同样，Span 的 toString() 方法完全重新实现字符串转换，而无需调用 Range 版本的 toString()。这使得 Span 成为特例，我们必须摆脱这种子类，因为这种情况是我们知道父类的实现细节。一个健壮的子类机制需要允许类调用其父类的方法和构造函数，但在 ES6 之前，JavaScript 没有一个简单的方法来执行这些操作。\n\nFortunately, ES6 solves these problems with the super keyword as part of the class syntax. The next section demonstrates how it works.\n\n> 幸运的是，ES6 用 super 关键字作为类语法一部分解决了这些问题。下一节演示它是如何工作的。\n\n### 9.5.2 Subclasses with extends and super\nIn ES6 and later, you can create a superclass simply by adding an extends clause to a class declaration, and you can do this even for built-in classes:\n\n> 在 ES6 之后，可以简单的在类声明时接一个 extends 从句添加一个父类，即使对于内置类也可以这样做：\n\n```js\n// A trivial Array subclass that adds getters for the first and last elements.\nclass EZArray extends Array {\n    get first() { return this[0]; }\n    get last() { return this[this.length-1]; }\n}\n\nlet a = new EZArray();\na instanceof EZArray  // => true: a is subclass instance\na instanceof Array    // => true: a is also a superclass instance.\na.push(1,2,3,4);      // a.length == 4; we can use inherited methods\na.pop()               // => 4: another inherited method\na.first               // => 1: first getter defined by subclass\na.last                // => 3: last getter defined by subclass\na[1]                  // => 2: regular array access syntax still works.\nArray.isArray(a)      // => true: subclass instance really is an array\nEZArray.isArray(a)    // => true: subclass inherits static methods, too!\n```\nThis EZArray subclass defines two simple getter methods. Instances of EZArray behave like ordinary arrays, and we can use inherited methods and properties like push(), pop(), and length. But we can also use the first and last getters defined in the subclass. Not only are instance methods like pop() inherited, but static methods like Array.isArray are also inherited. This is a new feature enabled by ES6 class syntax: EZArray() is a function, but it inherits from Array():\n\n> EZArray 子类定义了两个简单的 getter 方法。EZArray 实例的行为类似于普通数组，我们可以使用继承的方法和属性，如 push()、pop() 和 length。但是，我们也可以使用子类中定义的 first 和 last getter 方法。不仅继承实例方法如 pop() ，也继承 Array.isArray 等静态方法。这是 ES6 类语法启用的新特性：EZArray() 是一个函数，但它从 Array() 继承：\n\n```js\n// EZArray inherits instance methods because EZArray.prototype\n// inherits from Array.prototype\nArray.prototype.isPrototypeOf(EZArray.prototype) // => true\n\n// And EZArray inherits static methods and properties because\n// EZArray inherits from Array. This is a special feature of the\n// extends keyword and is not possible before ES6.\nArray.isPrototypeOf(EZArray) // => true\n```\nOur EZArray subclass is too simple to be very instructive. Example 9-6 is a more fully fleshed-out example. It defines a TypedMap subclass of the built-in Map class that adds type checking to ensure that the keys and values of the map are of the specified types (according to typeof). Importantly, this example demonstrates the use of the super keyword to invoke the constructor and methods of the superclass.\n\n> EZArray 子类太简单了，没有教育意义。示例 9-6 是一个更充实的示例。它定义了内置 Map 类的 TypedMap 子类，该子类添加类型检查以确保映射的键和值是指定的类型（根据 typeof）。重要的是，此示例演示了使用 super 关键字来调用父类的构造函数和方法。\n\nExample 9-6. TypedMap.js: a subclass of Map that checks key and value types\n\n> 示例 9-6：TypedMap.js：一个检测 key 和 value 类型的 Map 子类\n\n```js\nclass TypedMap extends Map {\n    constructor(keyType, valueType, entries) {\n        // If entries are specified, check their types\n        if (entries) {\n            for(let [k, v] of entries) {\n                if (typeof k !== keyType || typeof v !== valueType) {\n                    throw new TypeError(`Wrong type for entry [${k}, ${v}]`);\n                }\n            }\n        }\n\n        // Initialize the superclass with the (type-checked) initial entries\n        super(entries);\n\n        // And then initialize this subclass by storing the types\n        this.keyType = keyType;\n        this.valueType = valueType;\n    }\n\n    // Now redefine the set() method to add type checking for any\n    // new entries added to the map.\n    set(key, value) {\n        // Throw an error if the key or value are of the wrong type\n        if (this.keyType && typeof key !== this.keyType) {\n            throw new TypeError(`${key} is not of type ${this.keyType}`);\n        }\n        if (this.valueType && typeof value !== this.valueType) {\n            throw new TypeError(`${value} is not of type ${this.valueType}`);\n        }\n\n        // If the types are correct, we invoke the superclass's version of\n        // the set() method, to actually add the entry to the map. And we\n        // return whatever the superclass method returns.\n        return super.set(key, value);\n    }\n}\n```\nThe first two arguments to the TypedMap() constructor are the desired key and value types. These should be strings, such as “number” and “boolean”, that the typeof operator returns. You can also specify a third argument: an array (or any iterable object) of [key,value] arrays that specify the initial entries in the map. If you specify any initial entries, then the first thing the constructor does is verify that their types are correct. Next, the constructor invokes the superclass constructor, using the super keyword as if it was a function name. The Map() constructor takes one optional argument: an iterable object of [key,value] arrays. So the optional third argument of the TypedMap() constructor is the optional first argument to the Map() constructor, and we pass it to that superclass constructor with super(entries).\n\n> TypedMap() 构造函数的前两个实参是所需的键和值类型。这些实参应该是字符串，如\"number\"和\"boolean\"，这些字符串应该是 typeof 运算符的返回值。还可以指定第三个实参：指定 map 中初始条目的 [key,value] 数组（或任何可迭代对象）。如果指定任何初始条目，则构造函数要做的第一件事是验证其类型是否正确。接下来，构造函数调用父类构造函数，使用 super 关键字，就像它是一个函数名称一样。Map() 构造函数采用一个可选实参：可迭代对象 [key,value] 的数组。因此，TypedMap() 构造函数的第三个可选实参是 Map() 构造函数的第一个可选实参，我们用 `super(entries)` 将它传递给父类的构造函数。\n\nAfter invoking the superclass constructor to initialize superclass state, the TypedMap() constructor next initializes its own subclass state by setting this.keyType and this.valueType to the specified types. It needs to set these properties so that it can use them again in the set() method.\n\n> 调用父类构造函数初始化父类状态后，接下来 TypedMap() 构造函数初始化其自己的子类状态 this.keyType 和 this.valueType。它需要设置这些属性，以便它可以在 set() 方法中再次使用它们。\n\nThere are a few important rules that you will need to know about using super() in constructors:\n\n> 在构造函数中使用 super() 需要了解一些重要规则：\n\nIf you define a class with the extends keyword, then the constructor for your class must use super() to invoke the superclass constructor.\n\n> 如果使用 extends 关键字定义类，则类的构造函数必须使用 super() 调用父类构造函数。\n\nIf you don’t define a constructor in your subclass, one will be defined automatically for you. This implicitly defined constructor simply takes whatever values are passed to it and passes those values to super().\n\n> 如果未在子类中定义构造函数，将自动为你定义一个构造函数。此隐式定义的构造函数只将传递给它值传递给 super()。\n\nYou may not use the this keyword in your constructor until after you have invoked the superclass constructor with super(). This enforces a rule that superclasses get to initialize themselves before subclasses do.\n\n> 在使用 super() 调用父类构造函数之前，不能在构造函数中使用 this 关键字。这个强制规则确保父类先于子类初始化。\n\nThe special expression new.target is undefined in functions that are invoked without the new keyword. In constructor functions, however, new.target is a reference to the constructor that was invoked. When a subclass constructor is invoked and uses super() to invoke the superclass constructor, that superclass constructor will see the subclass constructor as the value of new.target. A well-designed superclass should not need to know whether it has been subclassed, but it might be useful to be able to use new.target.name in logging messages, for example.\n\n> 在未使用 new 关键字调用的函数中，new.target 表达式是 undefined。但是，在构造函数中，new.target 是引用调用的构造函数。当调用子类构造函数并使用 super() 调用父类构造函数时，该父类构造函数将看到子类构造函数作为 new.target 的值。虽然设计良好的父类不需要知道它是否有子类，但，在日志记录消息的场景中使用 new.target.name 会很有用。\n\nAfter the constructor, the next part of Example 9-6 is a method named set(). The Map superclass defines a method named set() to add a new entry to the map. We say that this set() method in TypedMap overrides the set() method of its superclass. This simple TypedMap subclass doesn’t know anything about adding new entries to map, but it does know how to check types, so that is what it does first, verifying that the key and value to be added to the map have the correct types and throwing an error if they do not. This set() method doesn’t have any way to add the key and value to the map itself, but that is what the superclass set() method is for. So we use the super keyword again to invoke the superclass’s version of the method. In this context, super works much like the this keyword does: it refers to the current object but allows access to overridden methods defined in the superclass.\n\n> 在构造函数之后，示例 9-6 的下一部分是名为 set() 的方法。Map 父类定义了名为 set() 的方法，以向 Map 添加新条目。TypedMap 中的 set() 方法将重写其父类的 set() 方法。这个简单的 TypedMap 子类对向 Map 添加新条目一无所知，但它知道如何检查类型，因此它首先会这样做，验证要添加到地图中的键和值的类型是否正确，如果它们不正确则抛出异常。此 set() 方法无法将键和值添加到 Map 本身，但这就是父类 set() 方法的用途。因此，我们再次使用 super 关键字来调用父类版本的 set() 方法。在此上下文中，super 的工作方式与 this 关键字的工作方式非常相似：它引用当前对象，但允许访问在父级类中定义的被重写方法。\n\nIn constructors, you are required to invoke the superclass constructor before you can access this and initialize the new object yourself. There are no such rules when you override a method. A method that overrides a superclass method is not required to invoke the superclass method. If it does use super to invoke the overridden method (or any method) in the superclass, it can do that at the beginning or the middle or the end of the overriding method.\n\n> 在构造函数中，需要先调用父类构造函数，然后才能访问 this 和初始化新对象。但重写方法时没有此类规则。调用重写父类方法时不需要调用父类方法。如果它确实使用 super 来调用父类中的重写方法（或任何方法），它可以在重写方法的开头、中间或末尾调用。\n\nFinally, before we leave the TypedMap example behind, it is worth noting that this class is an ideal candidate for the use of private fields. As the class is written now, a user could change the keyType or valueType properties to subvert the type checking. Once private fields are supported, we could change these properties to #keyType and #valueType so that they could not be altered from the outside.\n\n> 最后，在将 TypedMap 示例抛在脑后之前，值得注意的是，此类是使用私有字段的理想场景。现在编写类时，用户可以更改 keyType 或 valueType 属性以破坏类型检查。一旦支持私有字段，我们可以将这些属性改为 #keyType 和 #valueType，以便它们不能从外部更改。\n\n### 9.5.3 Delegation Instead of Inheritance\nThe extends keyword makes it easy to create subclasses. But that does not mean that you should create lots of subclasses. If you want to write a class that shares the behavior of some other class, you can try to inherit that behavior by creating a subclass. But it is often easier and more flexible to get that desired behavior into your class by having your class create an instance of the other class and simply delegating to that instance as needed. You create a new class not by subclassing, but instead by wrapping or “composing” other classes. This delegation approach is often called “composition”, and it is an oft-quoted maxim of object-oriented programming that one should “favor composition over inheritance.”2\n\n> extends 关键字便于创建子类。但这并不意味着应该创建大量的子类。如果要编写某些其他类共享的行为的类，可以尝试通过创建子类来继承该行为。但是，通常将期望的行为编写在类中比用类创建其他类的实例并根据需要委托给该实例更简单也更灵活。创建新类不将其作为子类，而是通过包装或\"组合\"其他类。这种委托方法通常称为\"组合\"，它是一种面向对象编程经常被引用的座右铭\"倾向于组合而不是继承\"。^2\n\nSuppose, for example, we wanted a Histogram class that behaves something like JavaScript’s Set class, except that instead of just keeping track of whether a value has been added to set or not, it instead maintains a count of the number of times the value has been added. Because the API for this Histogram class is similar to Set, we might consider subclassing Set and adding a count() method. On the other hand, once we start thinking about how we might implement this count() method, we might realize that the Histogram class is more like a Map than a Set because it needs to maintain a mapping between values and the number of times they have been added. So instead of subclassing Set, we can create a class that defines a Set-like API but implements those methods by delegating to an internal Map object. Example 9-7 shows how we could do this.\n\n> 例如，假设我们想要一个行为类似于 JavaScript 的 Set 类的 Histogram 类，只不过，它不只是跟踪是否给集合添加了值，还维护该值添加次数的计数。由于 Histogram 类的 API 与 Set 类似，因此我们可以考虑 Set 子类并添加 count() 方法。另一方面，一旦我们开始考虑如何实现 count() 方法，我们可能会意识到 Histogram 类更像是一个 Map 而不是一个 Set，因为它需要维护值与添加它们次数之间的映射。因此，我们可以创建一个类，该类定义一个类似 Set 的 API，但委托内部 Map 对象来实现这些方法，而不是创建 Set 的子类。示例 9-7 显示了我们如何做到这一点。\n\nExample 9-7. Histogram.js: a Set-like class implemented with delegation\n\n> 示例 9-7：Histogram.js：使用委托实现一个类似 Set 的类\n\n```js\n/**\n * A Set-like class that keeps track of how many times a value has\n * been added. Call add() and remove() like you would for a Set, and\n * call count() to find out how many times a given value has been added.\n * The default iterator yields the values that have been added at least\n * once. Use entries() if you want to iterate [value, count] pairs.\n */\nclass Histogram {\n    // To initialize, we just create a Map object to delegate to\n    constructor() { this.map = new Map(); }\n\n    // For any given key, the count is the value in the Map, or zero\n    // if the key does not appear in the Map.\n    count(key) { return this.map.get(key) || 0; }\n\n    // The Set-like method has() returns true if the count is non-zero\n    has(key) { return this.count(key) > 0; }\n\n    // The size of the histogram is just the number of entries in the Map.\n    get size() { return this.map.size; }\n\n    // To add a key, just increment its count in the Map.\n    add(key) { this.map.set(key, this.count(key) + 1); }\n\n    // Deleting a key is a little trickier because we have to delete\n    // the key from the Map if the count goes back down to zero.\n    delete(key) {\n        let count = this.count(key);\n        if (count === 1) {\n            this.map.delete(key);\n        } else if (count > 1) {\n            this.map.set(key, count - 1);\n        }\n    }\n\n    // Iterating a Histogram just returns the keys stored in it\n    [Symbol.iterator]() { return this.map.keys(); }\n\n    // These other iterator methods just delegate to the Map object\n    keys() { return this.map.keys(); }\n    values() { return this.map.values(); }\n    entries() { return this.map.entries(); }\n}\n```\nAll the Histogram() constructor does in Example 9-7 is create a Map object. And most of the methods are one-liners that just delegate to a method of the map, making the implementation quite simple. Because we used delegation rather than inheritance, a Histogram object is not an instance of Set or Map. But Histogram implements a number of commonly used Set methods, and in an untyped language like JavaScript, that is often good enough: a formal inheritance relationship is sometimes nice, but often optional.\n\n> 在示例 9-7 中，Histogram() 构造函数创建一个 Map 对象。大多数方法都是单行，它们只是委托给 Map 的方法，使得实现变得非常简单。因为我们使用委托而不是继承，所以 Histogram 对象不是 Set 或 Map 的实例。但是，Histogram 实现了许多常用的 Set 方法，并没有过多的使用额外的 JavaScript，这通常足够好：有条理的继承关系有时是不错，但通常是可选的。\n\n### 9.5.4 Class Hierarchies and Abstract Classes\nExample 9-6 demonstrated how we can subclass Map. Example 9-7 demonstrated how we can instead delegate to a Map object without actually subclassing anything. Using JavaScript classes to encapsulate data and modularize your code is often a great technique, and you may find yourself using the class keyword frequently. But you may find that you prefer composition to inheritance and that you rarely need to use extends (except when you’re using a library or framework that requires you to extend its base classes).\n\n> 示例 9-6 演示了如创建 Map 的子类。示例 9-7 演示了如何在不创建子类的情况下将委托给 Map 对象。使用 JavaScript 类封装数据和模块化代码通常是一种很好的技术，你可能会发现自己经常使用类关键字。但是，你可能会发现，你更喜欢组合而不是继承，而且很少需要使用 extends（除非使用需要扩展的库或框架）。\n\nThere are some circumstances when multiple levels of subclassing are appropriate, however, and we’ll end this chapter with an extended example that demonstrates a hierarchy of classes representing different kinds of sets. (The set classes defined in Example 9-8 are similar to, but not completely compatible with, JavaScript’s built-in Set class.)\n\n> 但是，在某些情况下，多个级别的子类是合适的，我们将举一个扩展示例来结束本章，通过描述不同种类的集合来演示类的层次结构。（示例 9-8 中定义的类跟 JavaScript 中的内置 Set 类很相似，但不完全兼容。）\n\nExample 9-8 defines lots of subclasses, but it also demonstrates how you can define abstract classes—classes that do not include a complete implementation—to serve as a common superclass for a group of related subclasses. An abstract superclass can define a partial implementation that all subclasses inherit and share. The subclasses, then, only need to define their own unique behavior by implementing the abstract methods defined—but not implemented—by the superclass. Note that JavaScript does not have any formal definition of abstract methods or abstract classes; I’m simply using that name here for unimplemented methods and incompletely implemented classes.\n\n> 示例 9-8 定义了大量子类，但它也演示了如何定义抽象类（不包括完整实现的类）作为一组相关子类的通用父类。抽象父类可以定义所有子类继承和共享的部分实现。因此，子类只需要通过实现父类定义的抽象方法（没有实现）来定义它们自己的独特行为。请注意，JavaScript 对抽象方法或抽象类没有任何正式定义；我只是将这个名字用于未实现的方法和不完全实现的类。\n\nExample 9-8 is well commented and stands on its own. I encourage you to read it as a capstone example for this chapter on classes. The final class in Example 9-8 does a lot of bit manipulation with the &, |, and ~ operators, which you can review in §4.8.3.\n\n> 示例 9-8 评论良好，并且独立。我鼓励你把它作为本章关于类的顶级示例。示例 9-8 用 &、| 和 ~ 运算符执行大量位操作，可以在 §4.8.3 中查看这些运算符。\n\nExample 9-8. Sets.js: a hierarchy of abstract and concrete set classes\n\n> 示例 9-8：Sets.js：抽象类和实体类的层次\n\n```js\n/**\n * The AbstractSet class defines a single abstract method, has().\n */\nclass AbstractSet {\n    // Throw an error here so that subclasses are forced\n    // to define their own working version of this method.\n    has(x) { throw new Error(\"Abstract method\"); }\n}\n\n/**\n * NotSet is a concrete subclass of AbstractSet.\n * The members of this set are all values that are not members of some\n * other set. Because it is defined in terms of another set it is not\n * writable, and because it has infinite members, it is not enumerable.\n * All we can do with it is test for membership and convert it to a\n * string using mathematical notation.\n */\nclass NotSet extends AbstractSet {\n    constructor(set) {\n        super();\n        this.set = set;\n    }\n\n    // Our implementation of the abstract method we inherited\n    has(x) { return !this.set.has(x); }\n    // And we also override this Object method\n    toString() { return `{ x| x ∉ ${this.set.toString()} }`; }\n}\n\n/**\n * Range set is a concrete subclass of AbstractSet. Its members are\n * all values that are between the from and to bounds, inclusive.\n * Since its members can be floating point numbers, it is not\n * enumerable and does not have a meaningful size.\n */\nclass RangeSet extends AbstractSet {\n    constructor(from, to) {\n        super();\n        this.from = from;\n        this.to = to;\n    }\n\n    has(x) { return x >= this.from && x <= this.to; }\n    toString() { return `{ x| ${this.from} ≤ x ≤ ${this.to} }`; }\n}\n\n/*\n * AbstractEnumerableSet is an abstract subclass of AbstractSet.  It defines\n * an abstract getter that returns the size of the set and also defines an\n * abstract iterator. And it then implements concrete isEmpty(), toString(),\n * and equals() methods on top of those. Subclasses that implement the\n * iterator, the size getter, and the has() method get these concrete\n * methods for free.\n */\nclass AbstractEnumerableSet extends AbstractSet {\n    get size() { throw new Error(\"Abstract method\"); }\n    [Symbol.iterator]() { throw new Error(\"Abstract method\"); }\n\n    isEmpty() { return this.size === 0; }\n    toString() { return `{${Array.from(this).join(\", \")}}`; }\n    equals(set) {\n        // If the other set is not also Enumerable, it isn't equal to this one\n        if (!(set instanceof AbstractEnumerableSet)) return false;\n\n        // If they don't have the same size, they're not equal\n        if (this.size !== set.size) return false;\n\n        // Loop through the elements of this set\n        for(let element of this) {\n            // If an element isn't in the other set, they aren't equal\n            if (!set.has(element)) return false;\n        }\n\n        // The elements matched, so the sets are equal\n        return true;\n    }\n}\n\n/*\n * SingletonSet is a concrete subclass of AbstractEnumerableSet.\n * A singleton set is a read-only set with a single member.\n */\nclass SingletonSet extends AbstractEnumerableSet {\n    constructor(member) {\n        super();\n        this.member = member;\n    }\n\n    // We implement these three methods, and inherit isEmpty, equals()\n    // and toString() implementations based on these methods.\n    has(x) { return x === this.member; }\n    get size() { return 1; }\n    *[Symbol.iterator]() { yield this.member; }\n}\n\n/*\n * AbstractWritableSet is an abstract subclass of AbstractEnumerableSet.\n * It defines the abstract methods insert() and remove() that insert and\n * remove individual elements from the set, and then implements concrete\n * add(), subtract(), and intersect() methods on top of those. Note that\n * our API diverges here from the standard JavaScript Set class.\n */\nclass AbstractWritableSet extends  AbstractEnumerableSet {\n    insert(x) { throw new Error(\"Abstract method\"); }\n    remove(x) { throw new Error(\"Abstract method\"); }\n\n    add(set) {\n        for(let element of set) {\n            this.insert(element);\n        }\n    }\n\n    subtract(set) {\n        for(let element of set) {\n            this.remove(element);\n        }\n    }\n\n    intersect(set) {\n        for(let element of this) {\n            if (!set.has(element)) {\n                this.remove(element);\n            }\n        }\n    }\n}\n\n/**\n * A BitSet is a concrete subclass of AbstractWritableSet with a\n * very efficient fixed-size set implementation for sets whose\n * elements are non-negative integers less than some maximum size.\n */\nclass BitSet extends AbstractWritableSet {\n    constructor(max) {\n        super();\n        this.max = max;  // The maximum integer we can store.\n        this.n = 0;      // How many integers are in the set\n        this.numBytes = Math.floor(max / 8) + 1;   // How many bytes we need\n        this.data = new Uint8Array(this.numBytes); // The bytes\n    }\n\n    // Internal method to check if a value is a legal member of this set\n    _valid(x) { return Number.isInteger(x) && x >= 0 && x <= this.max; }\n\n    // Tests whether the specified bit of the specified byte of our\n    // data array is set or not. Returns true or false.\n    _has(byte, bit) { return (this.data[byte] & BitSet.bits[bit]) !== 0; }\n\n    // Is the value x in this BitSet?\n    has(x) {\n        if (this._valid(x)) {\n            let byte = Math.floor(x / 8);\n            let bit = x % 8;\n            return this._has(byte, bit);\n        } else {\n            return false;\n        }\n    }\n\n    // Insert the value x into the BitSet\n    insert(x) {\n        if (this._valid(x)) {               // If the value is valid\n            let byte = Math.floor(x / 8);   // convert to byte and bit\n            let bit = x % 8;\n            if (!this._has(byte, bit)) {    // If that bit is not set yet\n                this.data[byte] |= BitSet.bits[bit]; // then set it\n                this.n++;                            // and increment set size\n            }\n        } else {\n            throw new TypeError(\"Invalid set element: \" + x );\n        }\n    }\n\n    remove(x) {\n        if (this._valid(x)) {              // If the value is valid\n            let byte = Math.floor(x / 8);  // compute the byte and bit\n            let bit = x % 8;\n            if (this._has(byte, bit)) {    // If that bit is already set\n                this.data[byte] &= BitSet.masks[bit];  // then unset it\n                this.n--;                              // and decrement size\n            }\n        } else {\n            throw new TypeError(\"Invalid set element: \" + x );\n        }\n    }\n\n    // A getter to return the size of the set\n    get size() { return this.n; }\n\n    // Iterate the set by just checking each bit in turn.\n    // (We could be a lot more clever and optimize this substantially)\n    *[Symbol.iterator]() {\n        for(let i = 0; i <= this.max; i++) {\n            if (this.has(i)) {\n                yield i;\n            }\n        }\n    }\n}\n\n// Some pre-computed values used by the has(), insert() and remove() methods\nBitSet.bits = new Uint8Array([1, 2, 4, 8, 16, 32, 64, 128]);\nBitSet.masks = new Uint8Array([~1, ~2, ~4, ~8, ~16, ~32, ~64, ~128]);\n```\n## 9.6 Summary\nThis chapter has explained the key features of JavaScript classes:\n\n> 本章说明了 JavaScript 类的主要功能：\n\nObjects that are members of the same class inherit properties from the same prototype object. The prototype object is the key feature of JavaScript classes, and it is possible to define classes with nothing more than the Object.create() method.\n\n> 同一类的成员对象从同一原型对象继承属性。原型对象是 JavaScript 类的关键特性，只有 Object.create() 方法可以定义类。\n\nPrior to ES6, classes were more typically defined by first defining a constructor function. Functions created with the function keyword have a prototype property, and the value of this property is an object that is used as the prototype of all objects created when the function is invoked with new as a constructor. By initializing this prototype object, you can define the shared methods of your class. Although the prototype object is the key feature of the class, the constructor function is the public identity of the class.\n\n> 在 ES6 之前，典型的类定义先定义构造函数。使用 function 关键字创建的函数具有原型属性，this 属性的值是使用 new 将函数用作构造函数调用时创建的对象的原型对象。通过初始化此原型对象，可以定义类的共享方法。虽然原型对象是类的关键特性，但构造函数是类的公共标识。\n\nES6 introduces a class keyword that makes it easier to define classes, but under the hood, constructor and prototype mechanism remains the same.\n\n> ES6 引入了一个 class 关键字，它使定义类更加容易，但它只是个语法糖，构造函数和原型机制保持不变。\n\nSubclasses are defined using the extends keyword in a class declaration.\n\n> 子类使用类声明中的 extends 关键字定义。\n\nSubclasses can invoke the constructor of their superclass or overridden methods of their superclass with the super keyword.\n\n> 子类可以使用 super 关键字调用其父类的构造函数或父类的重写方法。\n\n---\n\n1. Except functions returned by the ES5 Function.bind() method. Bound functions have no prototype property of their own, but they use the prototype of the underlying function if they are invoked as constructors.\n\n2. See Design Patterns (Addison-Wesley Professional) by Erich Gamma et al. or Effective Java (Addison-Wesley Professional) by Joshua Bloch, for example.\n\n> 1. 除了 ES5 Function.bind() 方法返回的函数。绑定函数没有自己的原型属性，但如果它们作为构造函数调用，则它们使用基础函数的原型。\n\n> 2. 例如，见《Design Patterns (Addison-Wesley Professional)》作者 Erich Gamma 等，或者《Effective Java (Addison-Wesley Professional)》作者 Joshua Bloch。"
  },
  {
    "path": "layouts/partials/head/seo.html",
    "content": "{{- $params := .Scratch.Get \"params\" -}}\n\n{{- with .Site.Params.verification.google -}}\n    <meta name=\"google-site-verification\" content=\"{{ . }}\" />\n{{- end -}}\n{{- with .Site.Params.verification.bing -}}\n    <meta name=\"msvalidate.01\" content=\"{{ . }}\" />\n{{- end -}}\n{{- with .Site.Params.verification.yandex -}}\n    <meta name=\"yandex-verification\" content=\"{{ . }}\" />\n{{- end -}}\n{{- with .Site.Params.verification.pinterest -}}\n    <meta name=\"p:domain_verify\" content=\"{{ . }}\" />\n{{- end -}}\n{{- with .Site.Params.verification.baidu -}}\n    <meta name=\"baidu-site-verification\" content=\"{{ . }}\" />\n{{- end -}}\n\n{{- /* Home SEO */ -}}\n{{- if .IsHome -}}\n    <script type=\"application/ld+json\">\n    {\n        \"@context\": \"http://schema.org\",\n        \"@type\": \"WebSite\",\n        \"url\": \"{{ .Permalink }}\",\n        {{- with .Site.LanguageCode -}}\n            \"inLanguage\": \"{{ . }}\",\n        {{- end -}}\n        {{- with .Site.Author.name -}}\n            \"author\": {\n                \"@type\": \"Person\",\n                \"name\": {{ . | safeHTML }}\n            },\n        {{- end -}}\n        {{- with .Site.Params.description -}}\n            \"description\": {{ . | safeHTML }},\n        {{- end -}}\n        {{- $image := .Site.Params.seo.image -}}\n        {{- with dict \"Path\" $image \"Resources\" .Resources | partial \"function/resource.html\" -}}\n            \"image\": {\n                \"@type\": \"ImageObject\",\n                \"url\": \"{{ .Permalink }}\",\n                \"width\": {{ .Width }},\n                \"height\": {{ .Height }}\n            },\n        {{- else -}}\n            {{- with $image -}}\n                \"image\": \"{{ . | absURL }}\",\n            {{- end -}}\n        {{- end -}}\n        {{- with .Site.Params.seo.thumbnailUrl -}}\n            {{- with dict \"Path\" . \"Resources\" $.Resources | partial \"function/resource.html\" -}}\n                \"thumbnailUrl\": \"{{ .Permalink }}\",\n            {{- else -}}\n                \"thumbnailUrl\": \"{{ . | absURL }}\",\n            {{- end -}}\n        {{- end -}}\n        {{- with .Site.Copyright -}}\n            \"license\": \"{{ . | safeHTML }}\",\n        {{- end -}}\n        \"name\": {{ .Site.Title | safeHTML }}\n    }\n    </script>\n\n{{- /* Page SEO */ -}}\n{{- else if .IsPage -}}\n    <script type=\"application/ld+json\">\n    {\n        \"@context\": \"http://schema.org\",\n        \"@type\": \"BlogPosting\",\n        \"headline\": {{ .Title | safeHTML }},\n        \"inLanguage\": \"{{ .Site.LanguageCode }}\",\n        \"mainEntityOfPage\": {\n            \"@type\": \"WebPage\",\n            \"@id\": \"{{ .Permalink }}\"\n        },\n        {{- $images := $params.seo.images | default slice -}}\n        {{- if not $images -}}\n            {{- with .Resources.GetMatch \"featured-image-preview\" -}}\n                {{- $images = slice \"featured-image-preview\" -}}\n            {{- end -}}\n            {{- with .Resources.GetMatch \"featured-image\" -}}\n                {{- $images = slice \"featured-image\" -}}\n            {{- end -}}\n        {{- end -}}\n        {{- with .Site.Params.seo.image -}}\n            {{- $images = $images | default (slice .) -}}\n        {{- end -}}\n        {{- with $images -}}\n            \"image\": [\n                {{- range $index, $value := . -}}\n                    {{- if gt $index 0 }},{{ end -}}\n                    {{- with dict \"Path\" $value \"Resources\" $.Resources | partial \"function/resource.html\" -}}\n                        {\n                            \"@type\": \"ImageObject\",\n                            \"url\": \"{{ .Permalink }}\",\n                            \"width\": {{ .Width }},\n                            \"height\": {{ .Height }}\n                        }\n                    {{- else -}}\n                        {{- with $value -}}\n                            \"{{ . | absURL }}\"\n                        {{- end -}}\n                    {{- end -}}\n                {{- end -}}\n            ],\n        {{- end -}}\n        \"genre\": \"{{ .Type }}\",\n        {{- with .Params.tags -}}\n            \"keywords\": \"{{ delimit . \", \" }}\",\n        {{- end -}}\n        \"wordcount\": {{ .WordCount }},\n        \"url\": \"{{ .Permalink }}\",\n        {{- if not .PublishDate.IsZero -}}\n            \"datePublished\": {{ .PublishDate.Format \"2006-01-02T15:04:05-07:00\" | safeHTML }},\n        {{- else if not .Date.IsZero -}}\n            \"datePublished\": {{ .Date.Format \"2006-01-02T15:04:05-07:00\" | safeHTML }},\n        {{- end -}}\n        {{- with .Lastmod -}}\n            \"dateModified\": {{ .Format \"2006-01-02T15:04:05-07:00\" | safeHTML }},\n        {{- end -}}\n        {{- with .Site.Copyright -}}\n            \"license\": {{ . | safeHTML }},\n        {{- end -}}\n        {{- $publisher := .Params.author | default .Site.Author.name | default (T \"author\") | dict \"name\" -}}\n        {{- $publisher = $params.seo.publisher | default dict | merge $publisher -}}\n        \"publisher\": {\n            \"@type\": \"Organization\",\n            \"name\": {{ $publisher.name | safeHTML }}\n            {{- $logo := $publisher.logoUrl -}}\n            {{- with dict \"Path\" $logo \"Resources\" .Resources | partial \"function/resource.html\" -}}\n                ,\"logo\": {\n                    \"@type\": \"ImageObject\",\n                    \"url\": \"{{ .Permalink }}\",\n                    \"width\": {{ .Width }},\n                    \"height\": {{ .Height }}\n                }\n            {{- else -}}\n                {{- with $logo -}}\n                    ,\"logo\": \"{{ . | absURL }}\"\n                {{- end -}}\n            {{- end -}}\n        },\n        {{- with .Params.author | default .Site.Author.name | default (T \"author\") -}}\n            \"author\": {\n                \"@type\": \"Person\",\n                \"name\": {{ . | safeHTML }}\n            },\n        {{- end -}}\n        \"description\": {{ .Description | safeHTML }}\n    }\n    </script>\n{{- end -}}\n<script async src=\"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-4005185440822195\" crossorigin=\"anonymous\">\n</script>\n"
  },
  {
    "path": "static/CNAME",
    "content": "js.okten.cn"
  },
  {
    "path": "static/browserconfig.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<browserconfig>\n    <msapplication>\n        <tile>\n            <square150x150logo src=\"/mstile-150x150.png\"/>\n            <TileColor>#2b5797</TileColor>\n        </tile>\n    </msapplication>\n</browserconfig>\n"
  },
  {
    "path": "static/site.webmanifest",
    "content": "{\n    \"name\": \"\",\n    \"short_name\": \"\",\n    \"icons\": [\n        {\n            \"src\": \"/android-chrome-192x192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"/android-chrome-256x256.png\",\n            \"sizes\": \"256x256\",\n            \"type\": \"image/png\"\n        }\n    ],\n    \"theme_color\": \"#ffffff\",\n    \"background_color\": \"#ffffff\",\n    \"display\": \"standalone\"\n}\n"
  }
]