阿西河

所有教程

公众号
🌙
阿西河前端的公众号

我的收藏

    最近访问  (文章)

      教程列表

      抓包专区
      测试专区

      第 1 章 JavaScript简介 学习笔记

      第1章: JavaScript简介

      JavaScript 是一门为 Web 网页而生的编程语言,基本上所有的网站都或多或少的使用了 JavaScript 这门语言。并且你现在使用的所有智能终端设备(PC电脑/平板/手机等),基本上都安装了网页浏览器,这些浏览器几乎全部内置了 JavaScript 的解释器;有了这些基础设备的支持,让 JavaScript 有了广泛的应用空间,也让 JavaScript 成为了目前世界上使用最广泛的编程语言,没有之一。除此以外,自从 2009 年的 Node.js 发布,让 JavaScript 拥有了脱离浏览器运行的能力,Node.js 目前的发展势头非常好,深受广大开发者的喜爱,成千上万的开发者推动着社区的日益壮大,让 Node.js 的生态越来越完善。 Node.js 的成功也让 JavaScript 在后端开发领域中有了一席之地。不过 JavaScript 的语言本身是令人诟病的,很多人在学习的时候,甚至感觉很多地方非常无厘头。但是这些都不特别大的问题,无论你是从头开始学习的小白,还是已经在使用 JavaScript 进行写项目的开发者,这本书都会让你全面的掌握 JavaScript 这门语言。

      如果你以前使用过其他的面向对象语言,那么你在学习 JavaScript 的时候会事半功倍,也很容易理解 JavaScript 的语法。你需要事先知道的是: JavaScript 是一门弱类型语言,变量在声明的时候是不需要预先指定数据类型的,变量的类型只取决于后续给它赋予什么类型的值。如果你以前是使用 Java 或者 C++ 等强类型语言,开始接触的时候可能会感觉这种方式很不安全。如果你以前是没有任何基础的小白,之所以学习 JavaScript 仅仅是为了搞钱,这种情况的小伙伴也不需要担心,JavaScript 是很好学的语言。

      “JavaScript” 的名字本身,可能会让你和 Java 产生误会;这是因为发明 JavaScript 的时候,Java 那会风头正盛,JavaScript 创始人 Brendan Eich 为了蹭热度才起的这个名字,这是一种标题党的行为。两者的关系类似"雷锋"和"雷峰塔"的关系,或者"老婆"和"老婆饼"的关系。目前 JavaScript 已经发展成为一种健壮高效的通用语言了,这些黑历史就没有必要深究了,我们学习技术才是硬道理。

      JavaScript的名字, 版本和严格模式

      JavaScript 是 Netscape 创建的,至于历史演变没有什么好说的,JavaScript 这门语言的核心是一个叫做 ECMASctipt 的东西,ECMAScript 为 JavaScript 提供了核心的语言功能,规定了这们语言的书写规范和使用方法等。

      目前常说的 JavaScript 基础语法,指的是 2010 年发布的第五版 ECMASctipt 规范,ECMASctipt 简称 ES,而 ES5 指的是第五版的 ECMAScript 。本书是基于 ES5 进行渐进式书写的,自从 2015 年发布的 ECMAScript 2015 开始,每年都会发布一个 ECMAScript release 版本的规范,比如 ECMASctipt 2015(简称 ES6), ECMASctipt 2016(简称 ES7) ...

      虽然 JavaScript 早期的语法有很多缺陷,但是为了向后兼容,我们并不能粗暴的把以前的语法直接废弃处理,为了解决这个问题,在 ECMAScript 2010 这个版本里,开始引入严格模式的概念,详细内容可参考本书的第 5.6.3 节,那一节详细介绍了普通模式和严格模式的区别。从 ES6 开始,新的语法会隐式调用严格模式,比如你使用 ES6 的 class 创建类,则该类中的所有代码都自动使用严格模式进行解析,如果你在类的代码中使用了以前有缺陷的语句,将不会被支持。本书涵盖了以前老的 JavaScript 语法,介绍的同时也会告诉你这些有缺陷的语句是不能用在严格模式中的。

      为了满足日常使用,所有的语言都有自己的标准库以及用于基本输入和输出的平台。JavaScript 的核心部分定义了这本语言的使用规范以及处理数据的基础API,但是它并不包含输入和输出的功能。输入和输出工作交给了 JavaScript 的宿主环境进行实现。

      最常见的 JavaScript 宿主环境就是我们使用的浏览器及 Node.js ;在浏览器中,用户对鼠标和键盘的操作可以转化为 JavaScript 的输入信息,你可以用 JavaScript 在浏览器中基于用户的操作发起 Http 请求或者做别的输出操作,比如 JavaScript 可以借助 HTML 和 CSS 向用户原样输出他的输入信息。

      而 Node.js 可以让 JavaScript 拥有访问整个操作系统的权利,允许 JavaScript 程序读取和写入系统文件,发送和接受网络数据等操作,Node.js 可以轻松的实现一个 WEB 服务器,还可以简单的写一些代码来代替 Shell 脚本的工作。

      本书的大部分内容都是介绍 JavaScript 这门语言的本身,第 11 章介绍了 JavaScript 的标准库,第 15 章介绍了 JavaScript 的浏览器宿主环境,第 16 章介绍了 JavaScript 的 Node.js 宿主环境。

      本书首先介绍了 JavaScript 的基础知识,然后在这些基础之上继续探讨了更高级的用法。阅读本章的时候,你可以根据自己的实际情况有选择的进行阅读。如果你已经是一名经验丰富的 JavaScript 程序员,阅读的时候跳过本章是没有任何问题的,不过在跳过之前,推荐你看一下本章最后的 案例 1-1

      学习编程的时候,如果单纯的按照 A, B, C, D, E 这种顺序逐个学习,并不能很好的掌握这们语言,因为每个模块的知识点都不是独立存在的,都会和其它模块相关联。我们在学习的时候要跳出这个知识点本身,站在更高的位置总结相类似的方法和用法,以及这些相似方法都是用在什么场景下的,最后根据方法的优缺点进行扬长避短的使用。原书在介绍某个模块时,也会穿插一些相关知识,但是穿插的内容惜字如金,并不能入木三分。译者在翻译这本书的时候,会根据自己的经验进行批注,遇到重点知识,会重点介绍;如果某个知识点有相似语法和方法的时候,会尽量把相关的知识都彻底的介绍清楚。

      1.1 了解 JavaScript

      当你跟着本书学习 JavaScript 时,请一定要亲自动手试一下本书中的案例。然后还可以修改案例中的代码,看看它是不是按照你的预期进行执行的,这么做可以加深你对 JavaScript 这门语言的理解(如果你能动手敲一遍本书案例中的代码,那就更好啦)。

      为了能够正常的查看结果,你需要一个 JavaScript 的解释器。

      浏览器的开发者工具

      最简单的办法就是使用浏览器的开发者工具(Windows电脑可以通过按键盘上的 F12,或者按 Ctrl+Shift+i 进行打开;Mac电脑可以通过按 Command+Option+i 进行打开),打开后确保选中其中的 console 选项卡(有的浏览器也叫"控制台")。然后就可以在里面输入 JavaScript 代码了,输入完毕后敲回车键进行确认,就可以看到代码的输出结果了。开发者工具通常显示在浏览器窗口的底部或者右侧,你可以根据自己的使用习惯进行调整,你甚至可以把它和浏览器分离开,作为单独的窗口进行使用 (如 图 1-1 中的展示)。

      图 1-1. Chrome浏览器的控制台

      注: 原书使用火狐浏览器进行演示,因为图片质量一般,而且火狐的市场份额远远小于 Chrome 浏览器,所以译者改用 Chrome 浏览器做演示

      Node.js 的 REPL 环境

      另一种方式是从 https://nodejs.org 下载 Node.js ,在系统成功安装 Node.js 以后,你只需要在终端里输入 node ,回车后就就可以得到一个交互式 JavaScript 对话框,在这 Nodejs 里属于 REPL 模块,类似浏览器的控制台。

      $ node
      Welcome to Node.js v12.13.0.
      Type ".help" for more information.
      > .help
      .break    Sometimes you get stuck, this gets you out
      .clear    Alias for .break
      .editor   Enter editor mode
      .exit     Exit the repl
      .help     Print this help message
      .load     Load JS from a file into the REPL session
      .save     Save all evaluated commands in this REPL session to a file
      
      Press ^C to abort current expression, ^D to exit the repl 
      > let x = 2, y = 3;
      undefined
      > x+ y
      5
      > (x === 2) && (y === 3)
      true
      > (x > 3) || (y < 3)
      false
      

      1.2 Hello World

      当你准备正式学习 JavaScript 的时候,使用上面介绍的浏览器的开发者工具或 Node.js 的 REPL 交互环境,就非常不合适了,效率太低了!推荐使用专业的代码编辑器进行写代码,个人比较推荐 VSCode 或者 WebStorm ,这两个都是非常优秀的代码编辑器,其中 VSCode 是免费的;WebStorm 是免费试用 30 天后,需要付费购买才可以正常使用。随便选择哪一个都不会错,不要再编辑器选择上浪费时间,学习技术才是重要的,如果你有选择困难症,你就直接用 VSCode 来写代码吧。

      你可以把 JavaScript 代码写在 .js 文件中,然后把写好的代码复制到浏览器控制台或者 Node.js REPL交互环境中去运行。但是通常情况下,我们会用 Node.js 直接运行这个 .js 文件,或者用 html 文件来引用该文件。(写完 JavaScript 代码之后,请确保你的 .js 文件已经被保存)

      Node.js 运行 js 文件

      下面这种就是 Nodejs 运行.js文件的方式

      $ node hello.js
      

      如果你使用上面这种方式,它不会像 浏览器的开发者工具Node.js的REPL交互环境 那样自动打印代码的返回值。如果你需要输出这些信息,你可以使用 **console.log()**来输出你想要查看的信息。

      比如,你创建一个包含下面代码的 hello.js 文件。

      console.log("Hello World!");
      

      使用 node hello.js 运行该文件,你会看到终端里打印了 “Hello World!” 这条消息。

      注意:当你运行 node hello.js 时,请确保你终端所在的目录和编辑器中文件所在的目录是一致的,否则可能会出现下面报错

      internal/modules/cjs/loader.js:968
        throw err;
        ^
      
      Error: Cannot find module 'E:\SystemUserDir\Desktop\hello.js'
          at Function.Module._resolveFilename (internal/modules/cjs/loader.js:965:15)
          at Function.Module._load (internal/modules/cjs/loader.js:841:27)
          at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
          at internal/main/run_main_module.js:17:47 {
        code: 'MODULE_NOT_FOUND',
        requireStack: []
      }
      

      终端的打开方式:

      • Windows 电脑:
        1. 打开 hello.js 文件所在的目录
        2. 按住 Shift 键后,在文件夹内点击鼠标右键
        3. 点击菜单的 “在此处打开命令窗口”
      • Mac 电脑:
        • 第一种方式: 点按程序坞中的“启动台”图标 ,在搜索栏中键入“终端”,然后点按“终端”。
          • 使用命令 cd xx/xxx 进入目标目录,
        • 第二种方式: 在“访达” 中,打开“/应用程序/实用工具”文件夹,然后连按“终端”。
          • 使用命令 cd xx/xxx 进入目标目录,
      • Linux 电脑 (Ubuntu为例):
        • 第一种方式: 在Linux的桌面,使用 Ctrl + Alt + T 打开终端
          • 使用命令 cd xx/xxx 进入目标目录,
        • 第二种方式: 通过“search your computer”功能搜索,terminal
          • 使用命令 cd xx/xxx 进入目标目录,

      html 中运行 js 文件

      如果你想要在浏览器中查看输出的信息,请在同一目录下创建 hello.html , 并把下面的代码写在新建的html文件中。

      <script src="hello.js"></script>
      

      然后打开浏览器,在电脑文件夹中找到新创建的 hello.html 文件,将该文件直接拖到浏览器内即可。

      浏览器的地址栏出现类似下面的网址。

      file:///Users/username/javascript/hello.html
      或
      file:///E:/file/010/hello.html
      

      打开浏览器的开发者工具,选择 Console 选项卡,就可以看到输出的"Hello World!" 这条消息。

      这个例子的配套源码下载,请移步译者的Github仓库

      https://github.com/anbang/javascript-the-definitive-guide-7th-edition/tree/master/code

      1.3 JavaScript之旅

      这一节会通过例子带你快速预览后面第二章和第三章的主要内容,这些内容后面会深入的进行探讨。第2章介绍了 JavaScript 注释,分号和 Unicode 字符集等内容。第三章开始真正的进入 JavaScript 的学习,主要介绍了 JavaScript 变量,以及可以分配哪些类型的值给变量。

      下面是一个实例代码,用来介绍第二章和第三章的主要内容;

      // 双斜杠后面的所有内容都是注释内容。
      // 注释的作用是解释 JavaScript 代码,建议仔细阅读
      
      // 变量是数据的代言人。
      // 可以使用 let 关键字声明变量:
      
      let x;              // 声明一个名字叫 x 的变量
      
      // 可以通过 = 把值分配给变量
      x = 0;              // 现在变量 x 被赋予 0 这个值
      x                   // => 0: 变量 x 的值已经是 0 了
      
      // JavaScript 支持多种类型的值
      x = 1;              // 数字类型
      x = 0.01;           // 数字类型可以是整数,也可以是小数
      x = "hello world";  // 字符串类型,使用引号进行包裹
      x = 'JavaScript';   // 使用单引号进行包裹,也是一个合法的字符串类型
      
      // 学初中英语中,判断题通常让写 T 或 F。
      // 这里的 T 和 F 表示中文的对和错
      // T 代表 true , F 代表 false
      // 布尔值是计算机科学中的逻辑数据类型,
      // 表示某件事情是否正确,只有 true 和 false 两种可能
      x = true;           // 一个布尔值
      x = false;          // 另外一个布尔值
      
      x = null;           // Null 是一个特殊值,表示"没有值"
      x = undefined;      // Undefined 是另外一个特殊值,有点像 null.
      

      在第 6 章和第 7 章中,会重点介绍 JavaScript 的两个非常重要的数据类型: 对象和数组。因为它们在 JavaScript 中的地位太重要了,在你没有读到那一章之前,你看的内容会不可避免的牵扯到它们,所以这里也顺便介绍下。

      // 对象是 JavaScript 中最重要的数据类型。
      // 对象一个 key/value 结构的集合。
      // 它的产生可以直接创建出来,也可以由特定的字符串解析而来。
      let book = {                // 对象是用一对花括号括起来的值
          topic: "JavaScript",    // 在属性 "topic" 上赋值字符串 "JavaScript"
          edition: 7              // 在属性 "edition" 上赋值数字 7
      };                          // 对象是一对花括号组成的,记得用 } 来结束
      
      // 使用 . 或者 [] 访问对象的属性:
      book.topic                  // => "JavaScript"
      book["edition"]             // => 7: 访问属性值的另一种方法
      
      book.author = "Flanagan";   // 给对象创建新的属性
      book.contents = {};         // {} 是没有属性值的空对象,它也是合法的对象类型
      
      // 使用可选链操作符 ?. 访问对象属性 
      // ES2020 的新语法,该语法于 2020 年 8 月 3 日进入规范的第四阶段
      book.contents?.ch01?.sect1  // => undefined: book.contents 中没有 ch01 的属性
      
      // JavaScript 还支持数组类型 (一个有序的元素序列) 
      let primes = [2, 3, 5, 7];  // 拥有 4 个值的数组,使用 [ 和 ] 进行包裹
      // 注意:数组第一个元素的索引是 0 而不是 1 
      primes[0]                   // => 2: [0]表示获取数组primes的第一个元素 
      primes.length               // => 4: 查询这个数组当前有多少个元素
      primes[primes.length - 1]   // => 7: 获取数组最后一个元素(因为索引是以0开始,而不是1)
      
      // [4]表示数组的第五个元素
      // 目前只有四个元素,你可以尝试下写 primes[14] = 9; 看看有什么效果
      primes[4] = 9;              // 给数组添加一个新元素
      primes[4] = 11;             // 更改数组的原有元素
      
      let empty = [];             // [] 是一个没有任何元素的空数组
      empty.length                // => 0
      
      // 数组和对象的内部,还可以拥有其他的数组和对象
      let points = [              // 拥有2个元素的数组
          { x: 0, y: 0 },         // 每个元素都是一个对象
          { x: 1, y: 1 }
      ];
      let data = {                // 拥有两个属性的对象
          trial1: [[1, 2],[3, 4]],// 每一个属性的值都是数组
          trial2: [[2, 3],[4, 5]] // 数组内的元素还是数组
      };
      

      示例代码中的注释

      上面代码中看到了以箭头 => 开头的注释,箭头之后的值表示注释之前的代码结果。这是模拟浏览器开发者工具的 Console 交互结果

      另外 // => 这种注释也是作者用来做断言使用的,方法是编写一个工具用来测试代码并验证它的结果是否等于 // => 之后的值,作者通过这种方式可以大大减少这本书中的错误,这种断言的思路还是非常值得学习的。

      除了上面这种断言方式,作者还使用了下面两种断言方法,你在本书的后面会大量看到

      两种相关的,用来做断言的注释:

      • 如果你看到注释为 // a == 42,则表示注释之前的代码运行之后,变量 a 的值为 42。
      • 如果你看到 // ! 这种注释,则表示注释之前行的代码会引起执行异常,而感叹号之后的注释内容,会说明引起这种异常的原因。

      上面代码中所示的,用来创建空数组的 [] 符号 , 和创建空对象中的 {} ,这些语法被称为初始化表达式。表达式是 JavaScript 中非常重要的概念,比如获取对象属性和数组元素的 .[] ,这也是表达式的一种。表达式相关的内容会在第四章里进行详细介绍。

      在JavaScript中,最常见的表达式写法是使用运算符。

      下面是一些运算符的用法示例:

      // 对值使用运算符,会返回一个新的值
      // 先介绍一下比较常见的算术运算符
      3 + 2                       // => 5: 加法运算
      3 - 2                       // => 1: 减法运算
      3 * 2                       // => 6: 乘法运算
      3 / 2                       // => 1.5: 除法运算
      "3" + "2"                   // => "32": 加号除了用于加法运算,还可以用于字符串的拼接
      let points = [
          { x: 0, y: 0 },
          { x: 1, y: 1 }
      ];
      points[1].x - points[0].x   // => 1: 操作符除了直接对值进行操作,还可以对变量或属性进行操作
      
      // JavaScript 中一些简写方式
      let count = 0;              // 定义了一个变量
      count ++;                   // 变量自增1: 类似 count = count + 1;
      count --;                   // 变量自减1: 类似 count = count - 1;
      count += 2;                 // 变量自增2: 类似 count = count + 2;
      count *= 3;                 // 变量自乘3: 类似 count = count * 3;
      count                       // => 6: 变量名本身也是一个表达式
      
      // 关系运算符用来判断两个值之间的关系
      // 关系有: 相等,不等,小于,小于等于,大于,大于等于
      // 关系运算符返回一个布尔值。
      let x = 2,
          y = 3;              // 符号 = 是用来赋值的,不是用来比较y和3是否相等的
      x === y                 // => false: 判断两个值相同
      x !== y                 // => true: 判断两个值不相同
      x < y                   // => true: 判断左边的值 小于 右边
      x <= y                  // => true: 判断左边的值 小于等于 右边
      x > y                   // => false: 判断左边的值 大于 右边的值
      x >= y                  // => false: 判断左边的值 大于等于 右边的值
      "two" === "three"       // => false: 两个字符串不相同
      "two" > "three"         // => true: 左右两边的第一个字母't"相等,
                              // 但是第二个字符开始,"w" 在字母表中的索引大于 "h"
      false === (x > y)       // => true: 左右两边先运算,然后再判断,两边都是false,所以结果是true
      
      // 运算符的组合使用
      // && 若左侧 (x === 2) 可转换为 true,则返回右侧的 (y === 3),否则返回左侧的 (x === 2)
      (x === 2) && (y === 3)  // => true , 因为 (y === 3) 是 true
      
      // || 若左侧 (x > 3) 可转换为 true,则返回左侧的 (x > 3);否则,返回右侧的 (y < 3)。
      (x > 3) || (y < 3)      // => false,因为右侧的 (y < 3) 是 false
      
      // 取反操作
      !(x === y)              // => true: ! 是把后面的值转成布尔值后,对布尔值进行取反
      

      如果把 JavaScript 的表达式比作是短语,那么语句就是一个完整的句子。在第五章的时候会重点介绍语句。表达式可以用来计算值但是它并不会改变原有的值。而语句本身没有值,但是它会改变值,比如上面的变量声明和赋值操作;其中有一类叫做控制语句的非常重要,比如循环,判断等。

      函数是一个可以生成独立空间并且接受参数的代码块,定义之后,可以复用。这在第八章的时候会重点的介绍。在你看到第八章之前,你会经常看到它,所以这里放一些简单的例子,先了解一下;

      let y = 3;
      // 函数是可以接受参数的可复用代码片段
      function plus1(x) {     // 定义一个接受参数 x 的函数 plus1
          return x + 1;       // 把传入的参数加 1 后,返回出去
      }                       // 函数的语法使用一对花括号进行包裹 
      plus1(y)                // => 4: y 在上面赋值了 3, plus1 会返回 3+1,所以结果是 4
      
      let square = function(x) { 
          // return 之后的内容是函数对外的返回值,
          // 该返回值可以赋值给其他变量或者直接使用
          return x * x;       // 计算变量的值
      };  // 分号用来表示一条语句结束(注: 虽然分号可以省略,但是推荐养成写分号的习惯)
      square(plus1(y))        // => 16: 同时调用两个函数
      

      上面是通用的函数写法,在ES6的规范中,提出了箭头函数的语法 ,通过 => 符号来连接 函数的参数函数的主体 。箭头函数的非常的简洁和方便,如果你打算写一个匿名函数,那么使用箭头函数是一个很不错的选择。

      把上面的函数,用ES6的语法写一遍,代码如下:

      let y = 3;
      const plus1 = x => x + 1;   // 函数的参数是 x , 返回值是 x + 1
      const square = x => x * x;  // 函数的参数是 x , 返回值是 x * x
      plus1(y)                    // => 4: 功能上和上面的 plus1 函数一模一样
      square(plus1(y))            // => 16
      

      除了我们定义的函数以外,我们还可以使用对象上的方法,比如下面例子中的代码:

      注: 如果一个函数是挂在某个对象上的,通常把这类函数叫做"方法";比如某个对象上有一个 push 函数,通常也会以 “push方法” 来称呼它。

      let a = [];             // 创建一个空数组
      
      // 所有数组类型的数据,都天生具有下面的方法
      a.push(1, 2, 3);        //  这里的 push() 方法的作用是将元素添加到数组中
      a.reverse();            // reverse 方法是将数组内的元素进行倒序操作
      
      // 我们也可以自定义一个方法,下面代码里的this指的是对象 points 本身
      let points = [
          { x: 0, y: 0 },
          { x: 1, y: 1 }
      ];
      points.dist = function() {              // 定义一个两点之间距离的方法
          let p1 = this[0];                   // 第一个元素
          let p2 = this[1];                   // 第二个元素
          let a = p2.x - p1.x;                // 两点之间的x坐标差值
          let b = p2.y - p1.y;                // 两点之间的y坐标差值
      
          // Math.sqrt() 用来处理平方根
          return Math.sqrt(a * a + b * b);    // 勾股定理: a^2+b^2=c^2
      };
      points.dist()                           // => 两点之间的距离
      

      上面代码中的 this 关键字比较重要,后面会详细进行介绍,总结成一句话就是: 谁调用了该函数,那么该函数内的this就指向谁;这里的 dist 函数是 points 使用 points.dist() 调用的,所以 dist 内的 this 就指向 points;

      下面介绍 JavaScript 中常见的流程控制语句:

      // JavaScript 的判断和循环语句,类似 C, C++, Java 等其他语言.
      // 如果你有其他面向对象语言的基础,你学起来会事半功倍
      function abs(x) {           // 写一个计算绝对值的方法
          if (x >= 0) {           // if语句;表示如果满足什么条件,则执行{}内的操作
              // 如果参数 x 的值大于等于0,则执行这行代码;
              return x;           // 把 x 的值返回出去,并终止函数的后续执行
          } // if语句用 } 表示语句的结束位置
          // 如果不满足上面的if判断条件,则执行下面else里面的代码;
          // else 是对上面if的补充,它是非必需的,你可以只写 if 语句而不写 else 语句
          else {
              // 对数值进行取反
              return -x;          // 注意负号和上面 布尔取反的 ! 之间有区别的。 
          } 
      } // 注意,这个函数内的 return 都是写在 if / else 内的
      
      abs(-10) === abs(10)        // => true
      
      function sum(array) {       // 计算数组内的所有元素的总和
          // 定义一个变量sum用来储存所有元素的总和
          let sum = 0;            // 初始时候赋值为 0
          // 遍历参数中的数组,每次都把目标元素赋值给变量 x
          for (let x of array) {  
              // 这条语句类似 sum = sum + x ; 
              sum += x;           // 把每次遍历的元素,累加到变量sum上
          }                       // 循环结束
          // 循环结束后,把变量 sum 的值返回出去
          return sum;
      }
      
      let primes = [2, 3, 5, 7, 11];
      sum(primes) // => 28: 函数 sum 计算数组 primes 内 5 个元素之和 2+3+5+7+11
      
      // 如果要理解下面这个函数,可以先了解一下数学中的阶乘概念
      function factorial(n) {     // 计算一个正整数阶乘的函数
          let product = 1;        // 以1作为阶乘的开始
      
          // 只要满足参数 n 大于 1 的条件,会持续执行花括号 {} 内的代码
          while (n > 1) {
              product *= n;       // 等价于 product = product * n;
              // 使用 while 循环的时候,一定要控制好条件
              // 否则会出现死循环,导致程序卡死的现象
              n--;                // 等价于 n = n - 1;
          }                       // while循环结束
          return product;         // 返回最后的阶乘结果
      }
      factorial(4)                // => 24: 1*4*3*2
      
      // 另外一种阶乘的写法
      // 这种写法里的for循环,省略了花括号 {},虽然这种写法是合法的,但是非常不推荐
      function factorial2(n) {       
          let i, product = 1;         // 以1作为阶乘的开始,并额外声明一个变量 i
          for (i = 2; i <= n; i++)    // i用来控制条件,并在初始的时候赋值2
              product *= i;           // 注意省略{}的情况下的代码缩进
          return product;             // 返回最后的阶乘结果
      }
      factorial2(5)                   // => 120: 1*2*3*4*5
      

      JavaScript 支持面向对象的编程风格,但是它与传统的面向对象语言还是有很大差异的。在后面第九章的那里,会使用大量的例子来详细介绍 JavaScript 中的面向对象编程方法。下面是一个非常简单的小例子,演示了在 JavaScript 中使用类来表示坐标的 x 轴和 y 轴。并且在这个类上还定义了一个 distance 方法,这个方法用来计算某一个点到坐标原点的距离。

      // 所有语言的惯例,写类名的时候,首字母需要大写
      class Point {
          constructor(x, y) {     // constructor 函数用来初始化新的实例
              this.x = x;         // 初始化对象
              this.y = y;         // 将传入的参数初始化为一个对象
          }                       // 在构造函数内不需要 return
      
          // 逻辑和上面 points.dist 一样的,使用勾股定理
          distance() {            // 计算某一个坐标点到原点的距离
              return Math.sqrt(   
              this.x * this.x +   // this 指向 Point 的实例
              this.y * this.y     
          }
      }
      
      // 使用 new 关键字来创建构造函数 Point(),返回值是一个对象
      let p = new Point(1, 1);    // 传入参数 (1, 1)用来初始化坐标点
      
      // p 是 Point 的实例,调用上面的 distance 方法
      p.distance()                // => 进行勾股定理的运算
      

      JavaScript 基本语法的预览,到这里就算结束了,后面还有一些独立模块的内容,这里也介绍一下。

      第十章, 模块

      “模块”是指将功能拆分成独立的、可复用的单独脚本或文件,一般会借助函数和类来完成。

      第十一章, JavaScript 标准库

      涵盖了 JavaScript 所有的内置函数和类,比如包含了Map和Set等重要的数据结构。

      第十二章, 迭代器和生成器

      介绍 for/of 的工作方式,以及如何用它进行迭代。同时包含了generator函数和yield语句。

      第十三章, JavaScript异步

      介绍了 JavaScript 的异步编程,涵盖了回调函数和事件,基于Promise的API,以及 async 和 await 的用法。虽然 JavaScript 本身不是异步语言,但是浏览器事件和Node.js的API都是默认异步的,本章主要介绍了这些API的使用。

      第十四章, Metaprogramming

      引入了很多 JavaScript 的高级功能,如果你是打算自己进行 JavaScript 库的封装,这一章不容错过!

      第十五章, 浏览器中的JavaScript

      浏览器是 JavaScript 的常见宿主环境,本章详细阐述了浏览器是如何进行 JavaScript 的代码解析,并涵盖浏览器中很多重要的API,本书中花了大量的笔墨来介绍这些内容。

      第十六章, Node.js中的JavaScript

      Node.js 也是 JavaScript 的常见宿主环境,本章涵盖了 Node.js 常用的 API 以及数据结构等信息。

      第十七章, JavaScript 工具和扩展

      涵盖了很多已经广泛应用,并且值得深入研究的工具和语言扩展,借助这些工具和扩展,可以让你写代码的效率如虎添翼。

      1.4 案例: 统计字母出现的频率并以直方图显示

      这一节介绍一个很实用的例子,下面 案例 1-1 中是一个基于 Node.js 的代码,主要功能是读取文本,统计出现的字母以及字母出现频率,并以直方图的形式展现出来。

      这个例子你现在只需要了解一下就可以了,不要求全部搞懂,因为这里面使用了很多 JavaScript 的高级语法。等学完后面的章节以后,你再回头来研究这个例子。

      案例 1-1. 使用 JavaScript 统计字母的频率直方图

      创建 charfreq.js 文件,然后输入下面代码:

      /**
       * 这段 Node.js 代码是以标准输入的形式读取文本,计算文本中每一个字母的出现频率
       * 并以直方图的形式展示出来;下面这段代码需要 Node.js 12 及以上。
       */
      
      // DefaultMap 是基于 Map 的类,这种方式可以让你在调用不存在的属性时不会返会 null
      class DefaultMap extends Map {
          constructor(defaultValue) {
              super();                            // 调用超类构造函数
              this.defaultValue = defaultValue;   // 将输入的值挂在实例的 defaultValue 属性上
          }
          get(key) {
              if (this.has(key)) {                // 如果要查询的属性已经在是实例上
                  return super.get(key);          // 通过超类返回对于的属性值
              }
              else {
                  return this.defaultValue;       // 否则返回默认值
              }
          }
      }
      
      // 这个类用来计算并显示字母的直方图
      class Histogram { 
          constructor() {
              this.letterCounts = new DefaultMap(0);  // 计算字母的出现次数
              this.totalLetters = 0;                  // 记录出现多少了字母
          }
      
          // 这个方法根据文本中的字母来更新直方图
          add(text) {
              // 删除空格,然后转为大写字母
              text = text.replace(/\s/g, "").toUpperCase();
      
              // 循环遍历文本的字母
              for(let character of text) {
                  let count = this.letterCounts.get(character);   // 获取上次计数
                  this.letterCounts.set(character, count+1);      // 在原有计数的基础上加1
                  this.totalLetters++;
              }
          }
          
          // 将直方图转换为 ASCII 的字符串
          toString() {
              // 将Map结构转换为 [key,value] 数组形式的数组 (数组内套数组)
              let entries = [...this.letterCounts];
      
              // 通过计数来排序,然后按字母顺序
              entries.sort((a,b) => {                 // 排序函数
                  if (a[1] === b[1]) {                // 如果两个字母的出现频率一样
                      return a[0] < b[0] ? -1 : 1;    // 按照字母的顺序返回.
                  }else{                              // 如果出现频率不一样
                      return b[1] - a[1];             // 返回出现频率高的值
                  }
              });
      
              // 将计数转化成百分比
              for(let entry of entries) {
                  entry[1] = entry[1] / this.totalLetters*100;
              }
      
              // 删除所有占比小于 2% 的数据
              entries = entries.filter(entry => entry[1] >= 2);
              
              // 转成一行文本
              let lines = entries.map(
                  ([l,n]) => `${l}: ${"#".repeat(Math.round(n))} ${n.toFixed(2)}%`
              );
      
              // 使用换行符拼成字符串输出
              return lines.join("\n"); 
          }
      }
      
      // 创建直方图的异步函数,使用数据流的方式读取文本,数据流读取完毕后,返回最终的直方图。
      async function histogramFromStdin() {
          process.stdin.setEncoding("utf-8"); // 读取 Unicode 字符串, 而不是字节
          let histogram = new Histogram();
          for await (let chunk of process.stdin) {
              histogram.add(chunk);
          }
          return histogram; 
      }
      
      // 这个脚本的入口,通过标准输入创建一个直方图对象,然后把它转成字符串输出
      histogramFromStdin().then(histogram => { 
          console.log(histogram.toString()); 
      });
      

      例子运行

      你可以用这个例子来分析当前代码文件本身的字母出现频率。

      Unix 系统:

      在 Unix 风格的系统中(Linux 和 Mac ),你可以用下面的方式调用:

      $ node charfreq.js < charfreq.js
      T: ########### 11.22%
      E: ########## 10.15%
      R: ####### 6.68%
      S: ###### 6.44%
      A: ###### 6.16%
      N: ###### 5.81%
      O: ##### 5.45%
      I: ##### 4.54%
      H: #### 4.07%
      C: ### 3.36%
      L: ### 3.20%
      U: ### 3.08%
      /: ### 2.88%
      

      Windows 系统:

      如果是 Windows 电脑,可以借助 Git Bash 来运行;

      Administrator@WIN-AXIHE-COM MINGW64 /e/xxxxx/code/010 (master)
      $ bash
      
      Administrator@WIN-AXIHE-COM MINGW64 /e/xxxxx/code/010 (master)
      $ node charfreq.js < charfreq.js
      T: ####### 7.02%
      E: ###### 6.18%
      R: #### 4.10%
      S: #### 4.10%
      /: #### 3.75%
      A: ### 3.45%
      N: ### 3.45%
      O: ### 2.91%
      I: ### 2.79%
      L: ### 2.73%
      U: ## 2.44%
      .: ## 2.20%
      (: ## 2.08%
      ): ## 2.08%
      

      注: 先输入 bash 然后再运行 node charfreq.js < charfreq.js,否则会报错 stdin is not a tty

      1.5 小结

      这一章带大家简单认识了 JavaScript 的基本用法,比如注释,操作符,变量,数据类型,语句,对象,函数等。如果你现在看不懂这些代码,不要感到焦虑,这是很正常的现象;因为这一章只是带你进行语法的预览,上面这些知识在后面章节都会详细的进行介绍。很多知识点都会介绍的非常深入和广泛,那时候你甚至会感觉讲的太细了;如果你想要深入的理解并掌握 JavaScript ,这些细节都是非常有必要了解的。认真看完本书的内容之后,你再回头看一下市场上流行框架的源码,你会很容易的理解。

      在后续章节的阅读中,如果你卡在某一个知识点里,感觉非常难以理解。建议你直接跳过它,继续阅读别的章节,等看完本书以后再回头来研究当初的难点。没有必要在一个"死胡同"里浪费大量时间,也许等你的知识面丰富以后,站在更高的角度来看当初的难题,可能就直接理解了。

      目录
      目录