学习方法:全部内容都要看,内容能理解换下一个,无法理解上b站看解说,找不到解说上Google看文章,依然无法理解,那么抱着疑问回到书中继续往下看吧

第一章 简介

1.2 JavaScript实现

  1. 完整的JavaScript实现包含:
    • 核心(ECMAScript)
    • 文档对象模型(DOM)
    • 浏览器对象模型(BOM)
  2. ECMAScript提供语言核心功能;DOM提供与网页内容交互的方法和接口;BOM提供与浏览器交互的方法和接口

第二章 HTML中的JavaScript

2.1 Script元素

  1. 标签位置:为了避免加载JS文件导致页面空白,现代Web应用程序通常将所有JS引用放在元素中的页面内容后面

  2. 延迟脚本:defer属性脚本在执行时不会影响页面加载,脚本会被延迟到整个页面都解析完毕后再运行;延迟脚本不一定总会按顺序执行,因此最好只包含一个脚本

    1
    <script defer src="example1.js"></script>
  3. 异步脚本:async属性脚本同样不会影响页面加载,脚本下载完不管页面是否解析完直接运行;同样不一定会按顺序执行,异步脚本不建议修改DOM,可能会出现BUG,它和延迟都是外部脚本

    1
    2
    <script async src="example1.js"></script>
    <script async src="example2.js"></script>

2.3 文档模式

  1. 最初的文档模式:

    • 混杂模式(quirks mode)
    • 标准模式(standards mode)
  2. 标准模式:

    1
    2
    <!-- HTML5 -->
    <!DOCTYPE html>

第三章 语言基础

3.1 语法

  1. 区分大小写:ECMAScript中一切都区分大小写,变量test和变量Test是两个不同的变量
  2. ECMAScript标识符使用驼峰大小写形式:firstSecond
  3. 严格模式:在严格模式下,ECMAScript3的一些不规范写法在这种模式下会被处理,对于不安全的活动将抛出错误;顶部加上这一行启用严格模式:
    1
    "use strict";

3.3 变量

  1. 严格模式下,如果像这样给未声明的变量赋值,则会导致抛出ReferenceError
  2. 不使用var,const优先,let次之;使用const声明保持常量,不合理的赋值操作会报错,只在未来有修改再使用let

3.4 数据类型

  1. 简单数据类型:Undefined、Null、Boolean、Number、String、Symbol;复杂数据类型:Object
  2. typeof操作符,用来检测变量数据类型
    • Undefined类型:当使用var或let声明了变量但没有初始化时,相当于给变量赋予了undefined
    • Null类型:null值表示一个空对象指针;只要变量要保存对象,而当时又没有那个对象可保存,就要用null来填充该变量
    • Boolean类型:其他类型的值转换为布尔值,可以调用特定的Boolean()转型函数
    • Number类型:其他类型的值转换为数值,常用parseInt()函数;如果第一个字符不是数值字符、加号或减号返回NaN,第二个参数用来指定进制数
    • String类型:字符串是不可变的;其他类型的值转换为字符串,使用函数toString()或String()
    • Symbol类型:符号实例是唯一、不可变的;创建Symbol()实例并将其用作对象的新属性,就可以保证它不会覆盖已有的对象属性,无论是符号属性还是字符串属性
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      // 定义Symbol永远都不会一样
      let a = Symbol('hello');
      let b = Symbol('hello');
      console.log(a === b); // false
      console.log(a.description); // hello

      // 如果声明过'hi',系统会拿过来用所以为true
      let c = Symbol.for('hi');
      let d = Symbol.for('hi');
      console.log(c === d); // true
      console.log(Symbol.keyFor(c)); // hi

      // 同名查询成绩应用场景
      let user1 = {
      name: '幽蓝',
      key: Symbol()
      };
      let user2 = {
      name: '幽蓝',
      key: Symbol()
      };
      let grade = {
      [user1.key]: {css: 100, js: 89},
      [user2.key]: {css: 57, js: 32}
      };
      console.log(grade);
    • Object类型

创建对象的方法:

1
let o = new Object();

Object实例都有如下属性和方法:

  • constructor: 用于创建当前对象的函数
  • hasOwnProperty(propertyName): 用于判断当前对象实例(不是原型)上是否存在给定的属性
  • isPrototypeOf(object): 用于判断当前对象是否为另一个对象的原型
  • propertyIsEnumerable(propertyName): 用于判断给定的属性是否可以使用for-in语句枚举
  • toLocaleString(): 返回对象的字符串表示,该字符串反映对象所在的本地化执行环境
  • toString(): 返回对象的字符串表示
  • valueOf(): 返回对象对应的字符串、数值或布尔值表示

3.5 操作符

  1. ECMAScript7新增指数操作符

    1
    2
    3
    console.log(3 ** 2);      // 3*3 = 9
    console.log(3 ** 3); // 3*3*3 = 27
    console.log(16 ** 0.5); // 4
  2. 字符串和字符串比较时,它们比较的是字符编码,所以看起来会很怪

    1
    console.log('23' < '3');    // true
  3. 数值和字符串比较时,字符串会先被转换为数值,这样能保证结果正确;如果不能转换成数值,就转换成NaN

  4. 任何操作数与NaN比较时,结果都会返回false

    1
    2
    3
    4
    console.log(NaN == NaN);      // false
    console.log(NaN === NaN); // false
    console.log(NaN > NaN); // false
    console.log(NaN < NaN); // false
  5. 相等(==)全等(===),相等比较时会转换数据类型,而全等不会转换数据类型直接比较

    1
    2
    console.log('55' == 55);      // true,转换后相等
    console.log('55' === 55); // false,不相等,因为数据类型不一样
  6. 条件操作符

    1
    2
    3
    4
    5
    variable = boolean_expression ? true_value : false_value;

    // 如果100大于50,则将'答对了'赋值给faqNumber,否则将'答错了'赋值给faqNumber
    let faqNumber = (100 > 50) ? '答对了':'答错了';
    console.log(faqNumber); // 答对了

3.6 语句

  1. for-in语句:用于枚举对象中的非符号键属性

    1
    2
    3
    for (property in expression) {
    ...
    }
  2. for-of语句:用于遍历可迭代对象的元素

    1
    2
    3
    for (property of expression) {
    ...
    }
  3. break和continue都可以与label语句一起使用;通常是在嵌套循环中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    let num = 0;
    outermost:
    for (let i = 0; i < 10; i++) {
    for (let j = 0; j < 10; j++) {
    if (i == 5 && j == 5) {
    break outermost;
    }
    num++;
    }
    }
    console.log(num); // 55

3.7 函数

  1. 函数参数:参数在内部是用一个数组来表示的,函数接收到的始终都是这个数组,而不关心数组中包含哪些函数;通过arguments对象来访问这个参数数组;命名的参数只提供便利,但不是必须的;arguments对象中的值与对应的命名参数的内存空间是独立的,但它们的值会同步
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function example(name, age) {
    console.log('arguments', arguments);
    console.log('name:', name, 'age:', age);
    name = '幽蓝';
    console.log(arguments[0]);
    }
    example('金涛', '20', 'myFriend');
    // arguments: ['金涛', '20', 'myFriend']
    // name: 金涛 age: 20
    // 幽蓝

第四章 变量、作用域与内存

4.1 原始值与引用值

  1. 在操作对象时,实际上操作的是对象的引用而不是对象的本身

  2. 从一个变量到另一个变量复制基本类型值时,会创建这个值的副本;从一个变量到另一个变量复制引用类型值时,复制的值实际上是一个指针,它指向存储在堆内存中的对象,复制之后两个变量指向同一个对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    let n1 = 1;
    let n2 = n1;
    n1 = 2;
    console.log(n2); // 1

    let o1 = {};
    let o2 = o1;
    o1.name = '幽蓝';
    console.log(o2.name); // 幽蓝
  3. 传递参数:参数只能按值传递,参数为对象时,在函数内部访问的是同一个对象

    1
    2
    3
    4
    5
    6
    7
    8
    function setName(o){
    o.name = '幽蓝';
    o = {};
    o.name = '金涛';
    }
    let p = {};
    setName(p);
    console.log(p.name); // 幽蓝
  4. 确定一个值是哪种基本类型可以使用typeof操作符,而确定一个值是哪种引用类型可以使用instanceof操作符

4.2 执行上下文与作用域

  1. 执行上下文(也称为作用域)分全局上下文、函数上下文和块级上下文;每个上下文都有一个关联的变量对象;上下文中的代码在执行的时候,会创建变量对象的一个作用域链;作用域链的最前端始终是正在执行上下文的变量对象,全局上下文的变量对象是作用域链的最后一个变量对象
  2. 标识符查找:从作用域链的最前端开始,向上逐级搜索,找到后搜索停止,没有找到则一直追溯到全局上下文的变量对象

4.3 垃圾回收

  1. 最常用的垃圾回收策略是标记清理,垃圾回收程序运行的时候,会标记内存中存储的所有变量,然后去掉上下文中的变量以及被上下文中的变量引用的变量标记,之后再被加上标记的变量就是待删除的,原因是这些变量无法被访问到了
  2. 优化内存占用:执行代码时只保存必要的数据,如果数据不再必要,那么把它设置为null,从而释放其引用——解除引用;解除引用的作用是让其脱离上下文,以便垃圾回收程序运行时将其回收

第五章 基本引用类型

5.1 Date

  1. 创建日期对象:月份从0开始(1月是0,2月是1…)

    1
    2
    let d1 = new Date();
    let d2 = new Date(2022, 3, 20, 7, 30, 30); // 2022年4月20日7点30分30秒
  2. 获取执行时日期和时间的毫秒数,用来分析代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 起始时间
    let start = Date.now();

    // 调用函数
    doSomething();

    // 结束时间
    let stop = Date.now();
    result = stop - start;
  3. 日期格式化方法:local表示以特定于地区的格式显示

    1
    2
    3
    4
    5
    6
    7
    let d2 = new Date(2022, 3, 20, 7, 30, 30);    // 2022年4月20日7点30分30秒
    d2.toString(); // Wed Apr 20 2022 07:30:30 GMT+0800 (中国标准时间)
    d2.toDateString(); // Wed Apr 20 2022
    d2.toTimeString(); // 07:30:30 GMT+0800 (中国标准时间)
    d2.toLocaleString(); // 2022/4/20 07:30:30
    d2.toLocaleDateString(); // 2022/4/20
    d2.toLocaleTimeString(); // 07:30:30

5.2 RegExp

  1. pattern是正则表达式;flags是标记,用于控制正则表达式的行为。下面表示匹配模式的标记:

    • g:全局模式,表示查找字符串的全部内容,而不是找到第一个匹配的内容就结束
    • i:不区分大小写,表示在查找匹配时忽略pattern和字符串的大小写
    • m:多行模式,表示查找到一行文本末尾时会继续查找
    • y:粘附模式,表示只查找从lastIndex开始及之后的字符串
    • u:Unicode模式,启用Unicode匹配
    • s:dotAll模式,表示元字符.匹配任何字符(包括\n或\r)
      1
      2
      let exp1 = / pattern / flags;
      let exp2 = new RegExp('pattern', 'flags');
  2. 实例方法:

    • exec():返回包含第一个匹配信息的数组,数组第一个元素是匹配整个模式的字符串,其他元素是与表达式中的捕获组匹配的字符串;包含两个额外的属性:index和input
      1
      2
      3
      4
      5
      6
      7
      8
      let text = "I'm ulan, and this is newHome";
      let pattern = /and( this( is)?)?/gi;
      let matches = pattern.exec(text);
      console.log(matches.index); // 10
      console.log(matches.input); // I'm ulan, and this is newHome
      console.log(matches[0]); // and this is
      console.log(matches[1]); // this is
      console.log(matches[2]); // is
    • test:输入的文本与模式匹配,则返回true,否则返回false
      1
      2
      3
      4
      let text = "I'm ulan, and this is newHome";
      let pattern = /ulan/;
      let matches = pattern.test(text);
      console.log(matches); // true
  3. RegExp构造函数有部分属性,适用于作用域中的所有正则表达式,这些属性可以提取出与exec()和test()执行的操作相关的信息

5.3 原始值包装类型

  1. 三种特殊的引用类型:Boolean、Number、String

  2. Number的方法

    • toString():数值转字符串,参数可接收二、八进制…
    • toFixed():数值转字符串,参数指定包含小数点位数
    • isInteger():用来判断数值是否保存为整数
      1
      2
      3
      4
      5
      6
      let n1 = 10;
      let n2 = 10.005;
      console.log(n1.toString()); // "10"
      console.log(n2.toFixed(2)); // "10.01"
      console.log(Number.isInteger(n1)); // true
      console.log(Number.isInteger(n2)); // false
  3. String的方法

    • length属性:表示字符串的长度
    • charAt():查找索引位置返回字符,从索引0开始找,参数由整数指定位置
      1
      2
      3
      let s1 = 'hello world';
      s1.length // 11
      s1.charAt(6) // 'w'
    • slice():提取子字符串,第一个参数表示开始搜索的位置,第二个参数表示结束搜索的位置,负数从末尾计算
    • substr():提取子字符串,第一个参数表示开始搜索的位置,第二个参数是以length提取,按1、2、3…个字符计算
      1
      2
      3
      4
      5
      let s2 = '我喜欢JS,同时也爱HTML';
      s2.slice(3, 5) // 'JS'
      s2.slice(-4, -1) // 'HTM'
      s2.substr(3, 2) // 'JS'
      s2.substr(-4, 4) // 'HTML'
    • indexOf():查找字符索引返回位置,从前往后找,没找到返回-1
    • lastIndexOf():查找字符索引返回位置,从后往前找,没找到返回-1
    • includes():判断字符串中是否包含另一个字符串,第二个参数表示指定位置向末尾搜索
      1
      2
      3
      4
      5
      6
      7
      let s3 = 'abcdabcd';
      s3.indexOf('a') // 0
      s3.lastIndexOf('a') // 4
      s3.indexOf('a', 1) // 4
      s3.lastIndexOf('a', 1) // 0
      s3.includes('ab') // true
      s3.includes('df') // false
    • trim():创建字符串的副本,删除前后所有空格
      1
      2
      let s4 = '  hello world  ';
      let s5 = s4.trim(); // 'hello world'
    • repeat():字符串复制多少次,返回拼接后的副本,参数为复制次数
      1
      'hello '.repeat(3) + 'world'    // 'hello hello hello world'
    • toUpperCase()和toLowerCase():英文字符串转换大写和小写
      1
      2
      3
      let s6 = 'hello world', s7 = 'HELLO WORLD';
      s6.toUpperCase() // 'HELLO WORLD'
      s7.toLowerCase() // 'hello world'
    • localeCompare():按字母表顺序比较两个字符串,返回比较结果;例如a在b前面,b在c前面
      1
      2
      3
      4
      let s8 = 'betty';
      s8.localeCompare('alice') // 1
      s8.localeCompare('betty') // 0
      s8.localeCompare('claier') // -1

5.4 单例内置对象

  1. Global对象为一种兜底对象,不属于任何对象的属性和方法都是Global对象的方法,例如:isNaN()、parseInt()等;window对象是Global对象的代理,全局作用域中声明的变量和函数都变成了window的属性
  2. Math对象提供一些计算的属性和方法
    • min()、max():确定一组数值中的最小值和最大值,可接收任意多个参数
    • Math.ceil()、Math.floor()、Math.round()、 Math.fround():小数值舍入为整数
    • random():返回随机数
      1
      2
      3
      4
      5
      6
      let arr = [1, 2, 3, 4, 5, 6, 7, 8];
      console.log(Math.min(...arr)); // 1
      console.log(Math.max(...arr)); // 8

      let num = Math.floor(Math.random() * 10 + 1);
      console.log(num); // 随机1~10

第六章 集合引用类型

6.1 Object

  1. 创建Object实例:使用Object构造函数;对象字面量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // new操作符法
    let o1 = new Object();
    o1.name = '幽蓝';
    o1.age = 9;

    // 对象字面量表示法
    let o2 = {
    name: '幽蓝',
    age: 9
    };
  2. 访问对象属性:点表示法;方括号表示法;建议使用点表示法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 点表示法
    console.log(o.name);

    // 方括号表示法
    console.log(o['name']);

    let n = 'name';
    console.log(o[n]);
    console.log(o['first name'])

6.2 Array

  1. 创建数组:使用Array构造函数;使用数组字面量表示法

    1
    2
    3
    4
    5
    6
    let a1 = new Array();
    let a2 = new Array(20);
    let a3 = new Array('red', 'blue', 'green');

    let a4 = [];
    let a5 = ['red', 'blue', 'green'];
  2. 创建数组静态方法

    • from():可以将类数组结构转换为数组实例
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      // 字符串拆分单字符数组
      console.log(Array.from('ulan')); // [ 'u', 'l', 'a', 'n' ]

      // 数组浅复制
      const a1 = [1, 2, 3, 4];
      const a2 = Array.from(a1);
      console.log(a1 === a2); // false

      // 任何可迭代对象
      const iter = {
      * [Symbol.iterator]() {
      yield 1;
      yield 2;
      yield 3;
      yield 4;
      }
      };
      console.log(Array.from(iter)); // [ 1, 2, 3, 4 ]

      // 集合和映射转换为新数组
      const m = new Map() .set(1, 2)
      .set(3, 4);
      const s = new Set() .add(1)
      .add(2)
      .add(3)
      .add(4);
      console.log(Array.from(m)); // [[1, 2], [3, 4]]
      console.log(Array.from(s)); // [1, 2, 3, 4]

      // arguments对象可以被轻松地转换为数组
      function f1() {
      return Array.from(arguments);
      }
      console.log(f1(1, 2, 3, 4)); // [ 1, 2, 3, 4 ]

      // 转换带有必要属性的自定义对象
      const o1 = {
      0: 1,
      1: 2,
      2: 3,
      3: 4,
      length: 4
      };
      console.log(Array.from(o1)); // [ 1, 2, 3, 4 ]

      // 第二个参数可以映射函数,直接增强新数组的值
      // 第三个参数指定映射函数this的值,重写this值在箭头函数中不适用
      const a1 = [1, 2, 3, 4];
      const a2 = Array.from(a1, x => x ** 2);
      const a3 = Array.from(a1, function (x) {
      return x ** this.e
      }, {e: 2});
      console.log(a2); // [ 1, 4, 9, 16 ]
      console.log(a3); // [ 1, 4, 9, 16 ]
    • of():可以将一组参数转换为数组实例
      1
      2
      3
      // arguments对象转换为数组
      console.log(Array.of(1,2,3,4)); // [ 1, 2, 3, 4 ]
      console.log(Array.of(undefined)); // [ undefined ]
  3. 数组空位:一串空号来创建空位[,,,];尽量避免使用空位,有需要可用undefined代替

  4. 数组索引:利用length在末尾添加新项

    1
    2
    let a = ['a', 'b'];
    a[a.length] = 'c';
  5. 检测数组:Array.isArray()(解决了存在两个以上全局执行环境时instanceof检测结果出错的情况)

  6. 迭代器方法

    • keys():返回数组索引
    • values():返回数组元素
    • entries():返回索引/值对
      1
      2
      3
      const a = ['a', 'b', 'c'];
      const aEntries = Array.from(a.entries());
      console.log(aEntries); // [ [ 0, 'a' ], [ 1, 'b' ], [ 2, 'c' ] ]
  7. 复制和填充方法

    • fill():填充数组,向已有的数组插入全部或部分相同的值
    • copyWithin():批量复制,按指定范围浅复制数组中的内容,插入到指定索引开始位置
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      // 用7填充,索引大于等于1,且索引小于3的元素
      const z = [0, 0, 0, 0, 0];
      z.fill(7, 1, 3);
      console.log(z); // [ 0, 7, 7, 0, 0 ]

      // 从i中复制索引0开始到索引3(不包括3)结束的内容
      // 插入到索引4开始的位置
      let i,
      reset = () => i = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
      reset();
      i.copyWithin(4, 0, 3);
      console.log(i); // [0, 1, 2, 3, 0, 1, 2, 7, 8, 9]
  8. 转换方法

    • toString():返回一个逗号分隔的字符串
    • valueOf():返回数组本身
    • join():类似toString(),能把逗号换成其他
  9. 栈方法和队列方法

    • push():添加一项到数组末尾
    • pop():移除数组末尾一项
    • shift():移除数组第一项
    • unshift():添加一项到数组前端
  10. 排序方法

    • reverse():数组元素反向排列
    • sort():默认将数组元素转换成字符串,然后升序排列,可以接收一个比较函数作为参数
  11. 操作方法

    • concat():添加项
      1
      2
      3
      var a1 = ['red', 'green', 'blue'];
      var a2 = a1.concat('yellow', ['black', 'brown']);
      console.log(a2) // ["red", "green", "blue", "yellow", "black", "brown"]
    • slice():截取
      1
      2
      var a = ["red", "green", "blue", "yellow", "black", "brown"];
      console.log(a.slice(1), a.slice(1, 4)) // ["green", "blue", "yellow", "black", "brown"] ["green", "blue", "yellow"]
    • splice():删除插入替换
      1
      2
      3
      4
      var a = ["red", "green", "blue", "yellow", "black", "brown"];
      console.log(a.splice(2, 1), a); // 删除项; ["blue"] ["red", "green", "yellow", "black", "brown"]
      console.log(a.splice(1, 0, 'yellow', 'orange'), a); // 插入项; [] ["red", "yellow", "orange", "green", "yellow", "black", "brown"]
      console.log(a.splice(1, 1, 'red', 'purple'), a); // 替换项; ["yellow"] ["red", "red", "purple", "orange", "green", "yellow", "black", "brown"]
  12. indexOf() lastIndexOf() 接收两个参数:要查找的项和(可选)查找起点位置的索引;indexOf()从前往后查找,lastIndexOf()从后往前查找;返回要查找的项的位置,没找到则返回-1

    1
    2
    3
    4
    5
    var a = ["red", "purple", "orange", "green", "red", "yellow", "black", "brown"];
    console.log(a.indexOf('red')); // 0
    console.log(a.lastIndexOf('red')); // 4
    console.log(a.indexOf('red', 1)); // 4
    console.log(a.lastIndexOf('red', 1)); // 0
  13. 迭代方法

    • every():每一项函数都返回true,则这个方法返回true
    • some():有一项函数返回true,则这个方法返回true
    • filter():每一项都运行传入的函数,函数返回true的项,会组成新数组后返回
    • map():每一项都运行传入的函数,返回函数调用的结果构成新数组
    • forEach():每一项都运行传入的函数,没有返回值
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];

      let everyResult = numbers.every((item, index, array) => item > 2);
      console.log(everyResult); // false

      let someResult = numbers.some((item, index, array) => item > 2);
      console.log(someResult); // true

      // 返回一个所有数值都大于2的数组
      let filterResult = numbers.filter((item, index, array) => item > 2);
      console.log(filterResult); // [3, 4, 5, 4, 3]

      // 每一项都乘以2
      let mapResult = numbers.map((item, index, array) => item * 2);
      console.log(mapResult); // [2, 4, 6, 8, 10, 8, 6, 4, 2]

      // forEach()方法相当于使用for循环遍历数组
      numbers.forEach((item, index, array) => {
      // 执行某些操作
      });
  14. 归并方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    let values = [1, 2, 3, 4, 5];

    let sum1 = values.reduce((prev, cur, index, array) => prev + cur);
    console.log(sum1); // 15,过程1+2+3+4+5

    let sum2 = values.reduceRight(function(prev, cur, index, array){
    return prev + cur;
    });
    console.log(sum2); // 15,过程5+4+3+2+1

6.3 定型数组

  1. ArrayBuffer():用于内存中分配特定数量的字节空间,创建就不能再调整大小

    1
    2
    const buf = new ArrayBuffer(16);    // 在内存中分配16字节
    console.log(buf.byteLength); // 16
  2. DataView:允许你读写ArrayBuffer的视图;DataView使用ElementType来实现JavaScript的Number类型到缓冲内二进制格式的转换;默认为大端字节序

    1
    2
    3
    4
    5
    const buf = new ArrayBuffer(16);    // 在内存中分配16字节

    const dv = new DataView(buf, 0, 8);
    console.log(dv.byteOffset); // 0,表示视图从缓冲起点开始
    console.log(dv.byteLength); // 8,限制视图为前 8 个字节
  3. 定型数组:另一种形式的ArrayBuffer视图;特定于一种ElementType且遵循系统原生的字节序;目的是提高WebGL等原生库交换二进制数据的效率;定型数组两个新方法

    • set():复制值到指定的索引位置
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      // 创建长度为8的int16数组
      const container = new Int16Array(8);

      // 把定型数组复制为前4个值
      // 偏移量默认为索引0
      container.set(Int8Array.of(1, 2, 3, 4));
      console.log(container); // [1,2,3,4,0,0,0,0]

      // 把普通数组复制为后4个值
      // 偏移量4表示从索引4开始插入
      container.set([5, 6, 7, 8], 4);
      console.log(container); // [1,2,3,4,5,6,7,8]
    • subarray():复制值返回一个新定型数组;参数可选开始索引和结束索引
      1
      2
      3
      4
      5
      const source = Int16Array.of(2, 4, 6, 8);

      // 从索引1开始复制到索引3
      const partialCopy = source.subarray(1, 3);
      console.log(partialCopy); // [4, 6]

6.4 Map

  1. 创建Map构造函数;建议键/值存放使用Map
  2. Map实例化方法和属性
    • set():添加键/值对
    • get():查询键,获取值
    • has():判断Map中是否存在键
    • size:获取键/值对数量
    • delete():删除参数中的键/值对,参数是键
    • clear():删除全部键/值对
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      const m1 = new Map([
      ['name', '幽蓝'],
      [true, 'true']
      ]);
      m1.set('age', 9);
      console.log(m1.get('name'), m1.get('age')); // 幽蓝 9
      console.log(m1.has('age'), m1.has('sex')); // true false
      console.log(m1.size); // 3

      m1.delete('name');
      console.log(m1); // { true => 'true', 'age' => 9 }

      m1.clear();
      console.log(m1, typeof m1); // {} object
    • entries():Map迭代器,能以插入顺序生成[key, value]形式的数组
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      const m = new Map([
      ['name', '幽蓝'],
      ['age', 9],
      [true, 'true']
      ]);

      for (let pair of m.entries()) {
      console.log(pair);
      }
      // [ 'name', '幽蓝' ]
      // [ 'age', 9 ]
      // [ true, 'true' ]

6.5 WeakMap

  1. 创建WeakMap构造函数;键只能是引用类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const k1 = {id: 1},
    k2 = {id: 2},
    k3 = {id: 3};
    const wm1 = new WeakMap([
    [k1, 'val1'],
    [k2, 'val2'],
    [k3, 'val3'],
    ]);
    console.log(wm1.get(k1), wm1.get(k2), wm1.get(k3)); // val1 val2 val3
  2. WeakMap实例化方法:set()、get()、has()、delete()

  3. 应用场景:DOM节点是引用类型,WeakMap可以绑定成键,如果绑定的DOM节点被删除,垃圾回收程序可以立即释放其内存,而Map则不会释放内存,这也是WeakMap一项优势

6.6 Set

  1. Set实例化方法和属性

    • add():增加值,重复值会被替换
    • has()、Set.size、delete()、clear()
    • entries():两个元素的数组
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      const s1 = new Set();
      s1.add(1)
      .add(2)
      .add(2)
      .add('幽蓝');
      console.log(s1); // { 1, 2, '幽蓝' }

      // 可以数组去重
      const s2 = [1, 1, 2, 3, 3, 5, 6, 6];
      const s3 = new Set(s2);
      console.log(s3); // { 1, 2, 3, 5, 6 }
  2. Set没有索引值,本身是无序的,但迭代器能以插入顺序生成集合内容;values()和keys()是相同的

6.7 WeakSet

  1. 创建WeakSet构造函数

    1
    2
    3
    4
    5
    6
    const k1 = {id: 1},
    k2 = {id: 2},
    k3 = {id: 3};

    const ws = new WeakSet([k1, k2, k3]);
    console.log(ws.has(k1), ws.has(k2), ws.has(k3)); // true ...
  2. WeakSet实例化方法和属性:add()、has()、delete()

  3. 总结:WeakMap使用较多,WeakSet使用较少,两者应用场景都在DOM节点上,能让垃圾回收程序立即释放其内存;而Map、Set则不会释放内存

    1
    2
    3
    4
    5
    6
    7
    8
    const ws1 = new WeakSet();
    ws1.add([1, 2, 3]);
    ws1.add({});

    // 设置定时器看垃圾回收触发,去浏览器控制台看
    setInterval(function (){
    console.log(ws1);
    }, 1000);

6.8 迭代与扩展操作

  1. 原生集合类型:Array、所有定型数组、Map、Set

第七章 迭代器与生成器

7.2 迭代器模式

  1. 迭代器模式:集合类型、类数组提供iterable接口;默认迭代器Symbol.iterator,调用之后会产生一个实现Iterator接口的对象
  2. 可迭代对象的原生语言特性
    • for-of循环
    • 数组解构
    • 扩展操作符
    • Array.from()
    • 创建集合
    • 创建映射
    • Promise.all()接收由期约组成的可迭代对象
    • Promise.race()接收由期约组成的可迭代对象
    • yield*操作符,在生成器中使用
  3. 迭代器协议:next()在可迭代对象中遍历数据,成功调用返回IteratorResult对象,这个对象包含value和done属性,持续调用next(),直到value为undefined且done为true,后面始终重复
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let arr = [1,2,3];
    let iter = arr[Symbol.iterator]();
    for (let i = 0; i < 5; i++) {
    console.log(iter.next());
    }
    // { value: 1, done: false }
    // { value: 2, done: false }
    // { value: 3, done: false }
    // { value: undefined, done: true }
    // { value: undefined, done: true }

7.3 生成器

  1. 生成器:函数名称前加一个型号(*)表示它是一个生成器,箭头函数不能定义生成器函数;yield关键字可以让生成器停止和开始执行,而给关键字加星号表示继续迭代
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    function* func1(...nums) {
    yield '--func1--';
    yield* nums;
    yield* func2(101, 102); // func2() 返回的函数参数也可以迭代
    yield '--end--';
    }

    function* func2(...nums) {
    yield '--func2--';
    yield nums;
    yield* nums;
    yield* '对不起';
    yield* func3('幽蓝', '9');
    }

    function* func3() {
    yield '--func3--';
    yield* arguments; // arguments也是可迭代对象
    yield* funcNoStar(arguments); // 函数返回值是可迭代对象
    }

    function funcNoStar(arr) {
    let res = ['--funcNoStar--'];
    for (let a of arr) {
    res.push(...a)
    }
    return res;
    }

    for (let i of func1(1, 2, 3)) {
    console.log(i);
    }

    // --func1--
    // 1
    // 2
    // 3
    // --func2--
    // [ 101, 102 ]
    // 101
    // 102
    // 对
    // 不
    // 起
    // --func3--
    // 幽蓝
    // 9
    // --funcNoStar--
    // 幽
    // 蓝
    // 9
    // --end--

第八章 对象、类与面向对象编程

8.1 理解对象

  1. 两种属性:数据属性和访问属性。特性:描述属性的特征,是为了JavaScript引擎的规范定义,不能直接访问
    1.1数据属性4个特性

    • [[Configurable]]:表示属性是否可以通过delete删除并重新定义,是否可以修改它的特性,以及是否可以把它改为访问器属性
    • [[Enumerable]]:表示属性是否可以通过for-in循环返回
    • [[Writable]]:表示属性的值是否可以被修改
    • [[Value]]:包含属性实际的值

    1.2 访问属性4个特性

    • [[Configurable]]:表示属性是否可以通过delete删除并重新定义,是否可以修改它的特性,以及是否可以把它改为数据属性
    • [[Enumerable]]:表示属性是否可以通过for-in循环返回
    • [[Get]]:获取函数,在读取属性时调用
    • [[Set]]:设置函数,在写入属性时调用
  2. 定义及读取特性

    • Object.defineProperty()
    • Object.defineProperties()
    • Object.getOwnPropertyDescriptor()
  3. 合并对象:Object.assign()

    1
    2
    3
    4
    5
    6
    let dest, src, result;
    dest = {};
    src = {id: '幽蓝'};
    result = Object.assign(dest, src, {age: 9}, {age: 10});
    console.log(dest === result, src !== result); // true true
    console.log(dest, result); // { id: '幽蓝', age: 10 } { id: '幽蓝', age: 10 }
  4. 对象标识及相等判定:Object.is(),改善全等(===)问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    console.log(Object.is(true, 1),
    Object.is({}, {}),
    Object.is('2', 2)); // false false false

    console.log(Object.is(+0, -0),
    Object.is(+0, 0),
    Object.is(-0, 0)); // false true false

    console.log(Object.is(NaN, NaN)); // true
  5. 对象解构:使用与对象匹配的结构来实现对象属性赋值

    1
    2
    3
    4
    5
    6
    7
    let p = {
    name: '幽蓝',
    age: 9
    };

    let {name, age, likeColor = 'blue'} = p;
    console.log(name, age, likeColor); // 幽蓝 9 blue

8.2 创建对象

  1. 工厂模式:虽然可以解决创建多个类似对象的问题,但没有解决对象标识问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function createPerson(name, age, job) {
    let o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function () {
    console.log(this.name);
    };
    return o;
    }
    let p1 = createPerson('幽蓝', 9, 'Software Engineer');
    let p2 = createPerson('金涛', 20, 'coach');
  2. 构造函数模式:构造函数应该以大写字母开头

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function Person (name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function (){
    console.log(this.name);
    };
    }
    let p1 = new Person('幽蓝', 9, 'Software Engineer');
    let p2 = new Person('金涛', 20, 'coach');

    p1.sayName(); // 幽蓝
    p2.sayName(); // 金涛

    // p1 p2 分别保存着Person的一个不同的实例,这两个对象都有一个constructor属性,该属性指向Person
    console.log(p1.constructor); // function Person(name, age, job) {...

    console.log(p1 instanceof Object); // true
    console.log(p1 instanceof Person); // true

    2.1 这种方法会经历5个步骤

    • 在内存中创建一个新对象
    • 这个新对象内部的[[Prototype]]特性被赋值为构造函数的 prototype属性
    • 构造函数内部的this被赋值为这个新对象(即this指向新对象)
    • 执行构造函数内部的代码(给新对象添加属性)
    • 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象

    2.2 构造函数的问题:每个方法会在每个实例上创建一遍

    1
    console.log(p1.sayName == p2.sayName);    // false

  3. 原型模式:每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象(函数的原型对象),这个对象包含可以由该类型的所有实例共享的属性和方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function Person() {}

    Person.prototype.name = '张三';
    Person.prototype.age = 18;
    Person.prototype.job = 'student';
    Person.prototype.sayName = function () {
    console.log(this.name);
    };

    let p1 = new Person();
    let p2 = new Person();
    console.log(p1.sayName === p2.sayName); // true

    3.1 理解原型

    • 只要创建一个函数,就会按照特定的规则为这个函数创建一个prototype属性(指向原型对象)
    • 默认所有原型对象自动获得一个名为constructor的属性,指向与之关联的构造函数
    • 调用构造函数创建新实例后,实例将有一个__proto__属性,指向构造函数的原型对象,指针叫[[Prototype]],默认原型指向Object
    • 实例与构造函数之间没有直接关系
    • 读取属性:搜索先从对象实例本身开始,如果没找到,搜索原型对象
    • 使用isPrototype()来检测构造函数和实例之间是否有关系
    • 使用hasOwnProperty()来检测属性存在于实例中还是原型中
    • 原型和in操作符
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      // in操作符通过实例或原型访问到属性时返回true
      console.log('name' in p1); // true

      // 枚举属性
      for (const prop in p1) {
      console.log(prop); // name age job sayName
      }

      // Object.keys 返回一个所有元素为字符串的数组
      p1.name = '幽蓝';
      p1.age = 9;
      p1.sayHello = 'hello';
      console.log(Object.keys(p1)) // [ 'name', 'age', 'sayHello' ]

      // Object.getOwnPropertyNames() 返回所有实例属性,无论是否可以枚举
      console.log(Object.getOwnPropertyNames(Person.prototype)); // [ 'constructor', 'name', 'age', 'job', 'sayName' ]
    • 用对象字面量重写原型对象
      1
      2
      3
      4
      5
      6
      function Person() {}
      Person.prototype = {
      constructor: Person, // 这里重写了prototype,不再默认有constructor属性
      name: '幽蓝',
      age: 9,
      };

8.3 继承

  1. JavaScript中使用最多的继承模式:组合继承,融合原形链和构造函数的优点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    function SuperType(name) {
    this.name = name;
    this.color = ['red', 'blue', 'green'];
    }
    SuperType.prototype.sayName = function (){
    console.log(this.name);
    }

    function SubType(name, age){
    SuperType.call(this, name); // 借用构造函数
    this.age = age;
    }
    SubType.prototype = new SuperType(); // 原型链
    SubType.prototype.constructor = SubType;
    SubType.prototype.sayAge = function (){
    console.log(this.age);
    }

    let instance = new SubType('幽蓝', 9);
    instance.sayName(); // 幽蓝
    instance.sayAge(); // 9
  2. 确定原型和实例的关系

    1
    2
    console.log(instance instanceof SuperType);   // true
    console.log(SuperType.prototype.isPrototypeOf(instance)); // true
  3. 原型式继承、寄生式继承、寄生组合式继承
    task

    8.4 类

  4. class类名的首字母要大写;类不能声明提升;类是块级作用域

  5. constructor关键字:定义块内部创建类的构造函数,用new调用类的构造函数操作

    • 在内存中创建一个新对象
    • 这个新对象内部的[[Prototype]]指针被赋值为构造函数的prototype属性
    • 构造函数内部的this被赋值为这个新对象(即this指向新对象)
    • 执行构造函数内部的代码(给新对象添加属性)
    • 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象
  6. 类构造函数与构造函数区别:调用类构造函数必须用new操作符;调用普通构造函数不用new操作符,以全局this作为内部对象

  7. 原型方法与访问器:实例间共享方法,类定义语法把在类块中定义的方法作为原型方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Person {
    constructor() {
    this.locate = () => console.log('instance');
    }
    locate() {
    console.log('prototype');
    }
    }

    let p = new Person();
    p.locate(); // instance
    Person.prototype.locate(); // prototype
  8. 类继承:extends关键字,继承任何拥有[[Construct]]和原型的对象;super关键字引用它们的原型,super只能使用在派生类构造函数和静态方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    class Person {
    constructor(name, age) {
    this.name = name;
    this.age = age;
    }

    sayHello() {
    console.log('hello,我是父类');
    }
    }

    class Ulan extends Person {
    constructor(name, age, job) {
    super(name, age);
    this.job = job;
    }

    sayHello() {
    super.sayHello(); // 调用父类方法
    console.log('hello,我是幽蓝');
    }
    }

    let ul = new Ulan('幽蓝', 9, 'software engineer');
    console.log(ul); // Ulan { name: '幽蓝', age: 9, job: 'software engineer' }
    ul.sayHello();
    // hello,我是父类
    // hello,我是幽蓝
  9. 静态方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    class Person {
    constructor() {}

    sayHello() {
    console.log('hello,我是父类');
    }

    static sayHello() {
    console.log('hello,我是静态父类');
    }
    }

    class Ulan extends Person {
    constructor() {
    super();
    }

    sayHello() {
    // 方法相同,子类覆盖父类
    console.log('hello,我是幽蓝');
    }

    static sayHello() {
    super.sayHello();
    console.log('hello,我是静态幽蓝');
    }
    }

    let ul = new Ulan();
    ul.sayHello(); // hello,我是幽蓝
    Ulan.sayHello();
    // hello,我是静态父类
    // hello,我是静态幽蓝

第九章 代理与反射

9.1 代理基础

  1. 创建Proxy构造函数,接收两个参数:目标对象和处理程序对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const target = {
    id: '幽蓝'
    };
    const handler = {};
    const proxy = new Proxy(target, handler);
    console.log(target.id, proxy.id); // 幽蓝 幽蓝

    // 目标属性赋值会反映到两个对象上
    target.id = 'ulan';
    console.log(target.id, proxy.id); // ulan ulan
    // 代理属性赋值会反映到两个对象上
    proxy.id = 'cat';
    console.log(target.id, proxy.id); // cat cat
  2. get()捕获器:会在获取属性值的操作中被调用

    • target:目标对象
    • property:引用的目标对象上的字符串键属性
    • receiver:代理对象或继承代理对象的对象
  3. get捕获器不变式:如果target.property不可写且不可配置,处理程序返回的值必须与target.property匹配;如果target.property不可配置且[[Get]]特性为undefined,处理程序的返回值也必须是undefined

  4. revocable():撤销代理对象与目标对象的关联

  5. 状态标记:反射方法返回称作”状态标记”的布尔值,表示要执行的操作是否成功

9.2 代理捕获器与反射方法

  1. set()捕获器:会在设置属性值的操作中被调用
    • target:目标对象
    • property:引用的目标对象上的字符串键属性
    • value:要赋给属性的值
    • receiver:接收最初赋值的对象
  2. set捕获器不变式:如果target.property不可写且不可配置,则不能修改目标属性的值;如果target.property不可配置且[[Set]]特性为undefined,则不能修改目标属性的值
  3. 其他捕获器
    • has()捕获器会在in操作符中被调用
    • defineProperty()捕获器会在Object.defineProperty()中被调用
    • getOwnPropertyDescriptor()捕获器会在Object.getOwnPropertyDescriptor()中被调用
    • deleteProperty()捕获器会在delete操作符中被调用
    • ownKeys()捕获器会在Object.keys()及类似方法中被调用
    • getPrototypeOf()捕获器会在Object.getPrototypeOf()中被调用
    • setPrototypeOf()捕获器会在Object.setPrototypeOf()中被调用
    • isExtensible()捕获器会在Object.isExtensible()中被调用
    • preventExtensions()捕获器会在Object.preventExtensions()中被调用
    • apply()捕获器会在调用函数时中被调用
    • construct()捕获器会在new操作符中被调用
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      const person = {name: '幽蓝', age: 9};
      const personProxy = new Proxy(person, {
      get(target, key) {
      return target[key].toUpperCase(); // 获取name值并返回大写后的值
      },
      set(target, key, value) {
      if (typeof value === 'string') {
      target[key] = value.trim();
      }
      }
      });

      personProxy.name = 'ulan';
      console.log(personProxy.name); // ULAN

      personProxy.love = ' I love cat ';
      console.log(personProxy.love); // I LOVE CAT

      console.log(person.name, person.age); // ulan 9

9.3 代理模式

  1. 跟踪属性访问、隐藏属性、属性验证、函数与构造函数参数验证、数据绑定与可观察对象

第十章 函数

10.1 箭头函数

  1. 箭头函数实例化,建议参数不管有多少加(),花括号也加上{};箭头函数不适用场景:arguments、super、new.target和构造函数
    1
    2
    3
    4
    5
    6
    7
    let sum = (a, b) => {
    return a + b;
    };
    console.log(sum(5, 8)); // 13

    let ints = [1, 2, 3];
    console.log(ints.map((i) => { return i + 1; })); // [2,3,4]

10.3 理解参数

  1. 函数的参数在内部表现为一个数组,接收参数就是在接收数组;非箭头函数可在内部访问arguments对象,arguments对象是一个类数组对象,可用中括号语法访问其中的元素(第一个参数是arguments[0],第二个参数是arguments[1]),函数的参数只是为了方便才写出来的,并不是必须写出来的
    1
    2
    3
    4
    function a() {
    console.log(arguments[0], arguments[1]); // hello world
    }
    a('hello', 'world');

10.5 默认参数值

  1. 参数后面用=可以为参数赋一个默认值

    1
    2
    3
    4
    5
    6
    function p(name = '张三', age = 18) {
    return `个人信息: ${name} ${age}`;
    }
    console.log(p()); // 个人信息: 张三 18
    console.log(p('李四')); // 个人信息: 李四 18
    console.log(p('幽蓝', 9)); // 个人信息: 幽蓝 9
  2. 暂时性死区:前面定义的参数不能引用后面定义的参数;参数也有自己的作用域,它们不能引用函数体的作用域

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function a(name = age, age = 9) {
    return `名字:${name} ${age}`;
    }
    a(); // ReferenceError

    // 作用域链先从自己身上找,找不到再向外找,直到全局作用域找不到返回错误
    function b(name = '张三', age = getAge) {
    let getAge = 18;
    return `个人信息:${name},{age}`;
    }
    b(); // ReferenceError

10.6 参数扩展与收集

  1. 扩展操作符:对可迭代对象以一个参数传入,然后进行拆分,最后返回每个值在单独传入
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    let arr = [1, 2, 3, 4, 10];

    function getSum() {
    let sum = 0;
    for (let i = 0; i < arguments.length; ++i) {
    sum += arguments[i];
    }
    return sum;
    }

    console.log(getSum(arr)); // 01,2,3,4,10
    console.log(getSum(...arr)); // 20
    console.log(getSum(1, ...arr, ...[4, 5])); // 30
    console.log(getSum(...[2, 3])); // 5

10.9 函数内部

  1. 谁调用this,this就指向谁;箭头函数的this指向箭头函数的上下文
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function King() {
    this.royaltyName = 'Henry';
    // this 引用 King 的实例
    setTimeout(() => console.log(this.royaltyName), 1000);
    }

    function Queen() {
    this.royaltyName = 'Elizabeth';
    // this 引用 window 对象
    setTimeout(function () {
    console.log(this.royaltyName);
    }, 1000);
    }

    new King(); // Henry
    new Queen(); // undefined

10.10 函数属性与方法

  1. apply()和call()强大的地方并不是给函数传参,而是控制函数体内this值的能力;两个方法的功能一样,区别在怎么给调用的函数传参更方便

    • apply():第一个参数是函数内this值,第二个参数是Array的实例,也可以是arguments对象
    • call():第一个参数是函数内this值,第二个参数是将参数一个一个地列出来
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      function sum(num1, num2) {
      return num1 + num2;
      }

      function a(num1, num2) {
      return sum.apply(this, arguments);
      }

      function b(num1, num2) {
      return sum.apply(this, [num1, num2]);
      }

      function c(num1, num2) {
      return sum.call(this, num1, num2);
      }

      console.log(a(10, 10)); // 20
      console.log(b(10, 10)); // 20
      console.log(c(10, 10)); // 20
  2. bind():创建一个新的函数实例,其this值会被绑定到传给bind()的对象上

10.12 递归

  1. arguments.callee指向正在执行的函数的指针,因此可以在函数内部递归调用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function factorial(num) {
    if (num <= 1) {
    return 1;
    } else {
    return num * arguments.callee(num - 1);
    }
    }

    let a = factorial;
    console.log(factorial(5)); // 120, 5*4*3*2
    console.log(a(4)); // 24, 4*3*2

10.13 尾调用优化

  1. 尾调用优化的条件
    • 代码在严格模式下执行
    • 外部函数的返回值是对尾调用函数的调用
    • 尾调用函数返回后不需要执行额外的逻辑
    • 尾调用函数不是引用外部函数作用域中自由变量的闭包

10.14 闭包

  1. 闭包指的是引用另一个函数作用域中变量的函数

  2. 在调用一个函数时,会为这个函数调用创建一个执行上下文,并创建一个作用域链。然后用arguments和其他命名参数来初始化这个函数的活动对象。外部函数的活动对象是内部函数作用域链上的第二个对象。这个作用域链一直向外串起了所有包含函数的活动对象,直到全局执行上下文才终止

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 最外面是作用域链上的第三个对象
    function outer() { // outer函数是作用域链上的第二个对象
    let name = '幽蓝';
    return function () { // 匿名函数是作用域链上的第一个对象
    console.log(name);
    };
    }

    let inner = outer();
    inner(); // 幽蓝
    inner = null; // 解除对outer内部的匿名函数的引用,以便释放内存
  3. 匿名函数中的this在非严格模式下等于window

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    window.name = 'The Window';
    let object = {
    name: 'My Object',
    getNameFunc() {
    return function () {
    return this.name;
    };
    }
    };
    // The Window

10.16 私有变量

  1. 任何定义在函数或块中的变量,都可以认为是私有变量

  2. 特权方法:能够访问函数私有变量(及私有函数)的公有方法,缺点是每个实例都会重新创建一遍新方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    function MyObject() {
    // 私有变量和私有函数
    let privateVariable = '幽蓝';

    function privateFunction() {
    console.log('_(:з」∠)_');
    }

    // 特权方法
    this.publicMethod = function () {
    console.log(privateVariable);
    return privateFunction();
    };
    }

    let o = new MyObject();
    o.publicMethod(); // 幽蓝 _(:з」∠)_
    o.privateFunction(); // TypeError: o.privateFunction is not a function at Object
  3. 静态私有变量:通过使用私有作用域定义私有变量和函数来实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    (function () {
    // 私有变量和私有函数
    let privateVariable = '幽蓝';

    function privateFunction() {
    console.log('Σ(・□・;)');
    }

    // 不使用关键字声明的变量会创建在全局作用域中
    MyObject = function () {
    };

    // 公有和特权方法
    MyObject.prototype.publicMethod = function () {
    console.log(privateVariable);
    privateFunction();
    };
    })();
    let o = new MyObject();
    o.publicMethod(); // 幽蓝 Σ(・□・;)
    o.privateFunction(); // TypeError: o.privateFunction is not a function at Object
  4. 模块模式:创建一个要通过匿名函数返回的对象字面量,这个对象字面量中只包含可以公开访问的属性和方法

第十一章 期约与异步函数

11.1 异步编程

  1. 同步行为对应内存中顺序执行的处理器指令,每条指令都会严格按照它们出现的顺序来执行,而每条指令执行后也能立即获得存储在系统本地(如寄存器或系统内存)的信息
  2. 异步行为是为了优化因计算量大而时间长的操作,行为类似于系统中断,即当前进程外部的实体可以触发代码执行;早期支持定义回调函数来表明异步操作完成。串联多个异步操作是一个常见的问题,通常需要深度嵌套(俗称“回调地狱”)来解决

11.2 Promise期约

  1. 创建Promise构造函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // 男人和女人准备结婚,他们打算结婚前要个孩子
    // 如果女人怀孕了,成功状态返回孩子他爹,失败状态返回老公
    // 不论是否怀孕,最终都会结婚
    const isPregnant = true;
    const promise = new Promise((resolve, reject) => {
    if (isPregnant) {
    resolve(`孩子他爹`);
    } else {
    reject(`老公`);
    }
    });
    promise
    .then(name => {
    console.log(`男人成为了${name}!`);
    })
    .catch(name => {
    console.log(`男人成为了${name}!`);
    })
    .finally(() => {
    console.log(`男人和女人最终结婚了`);
    });
  2. Promise是一个有状态的对象,状态是私有的,不能直接通过js检测到,也不能被外部js修改,从而隔离外部同步代码;状态可能处于以下3种

    • 待定(pending)
    • 成功(fulfilled,也称resolve)
    • 失败(reject)
  3. Promise实例方法

    • then():实现了Thenable接口,接收两个参数onResolved处理程序和onRejected处理程序
    • catch():添加拒绝处理程序,接收一个参数onRejected处理程序
    • finally():添加onFinally处理程序,解决或拒绝状态时都会执行
  4. 两个静态方法:Promise.all()和 Promise.race()

    • Promise.all():接收多个Promise对象,全部对象状态成功,all才会成功,其中一个状态失败,all才会失败,最终返回一个数组
    • Promise.race():接收多个Promise对象,第一个对象状态成功,race才会成功,第一个对象状态失败,race才会失败
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      // 等待多少毫秒变化状态
      const delay = ms => {
      return new Promise(resolve => {
      setTimeout(resolve, ms);
      });
      };

      const p1 = delay(1000).then(() => {
      console.log('p1 完成了');
      return 'p1';
      });
      const p2 = delay(2000).then(() => {
      console.log('p2 完成了');
      return 'p2';
      });

      const pAll = Promise.all([p1, p2]);
      pAll.then(
      data => {
      console.log(data);
      },
      err => {
      console.log(err);
      });
      // p1 完成了
      // p2 完成了
      // [ 'p1', 'p2' ]


      const pRace = Promise.race([p1, p2]);
      pRace.then(data => {
      console.log(data);
      }, err => {
      console.log(err);
      });
      // p1 完成了
      // p1
      // p2 完成了

11.3 异步函数

  1. async关键字用于声明异步函数;await关键字可以暂停异步函数代码的执行,等待Promise解决
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const start = async () => {
    console.log(1);
    await setTimeout(() => {
    console.log(2);
    }, 1000);
    console.log(3);
    };
    start();
    // 1
    // 3
    // 2

第十二章 BOM

12.1 window对象

  1. screenLeft和screenTop属性,表示窗口相对于屏幕左侧和顶部的位置

  2. moveTo()接收要移动到的新位置的绝对坐标x和y;而moveBy()则接收相对当前位置在两个方向上移动的像素数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 把窗口移动到左上角
    window.moveTo(0,0);

    // 把窗口向下移动100像素
    window.moveBy(0, 100);

    // 把窗口移动到坐标位置(200, 300)
    window.moveTo(200, 300);

    // 把窗口向左移动50像素
    window.moveBy(-50, 0);
  3. 窗口大小

    • outerWidth和outerHeight:浏览器窗口自身的大小
    • innerWidth和innerHeight:浏览器窗口中页面视口的大小
    • document.documentElement.clientWidth和document.documentElement.clientHeight:页面视口的宽度和高度
    • resizeTo()接收新的宽度和高度值;resizeBy()接收宽度和高度要缩放多少
      1
      2
      3
      4
      5
      6
      7
      8
      // 缩放到100×100
      window.resizeTo(100, 100);

      // 缩放到200×150
      window.resizeBy(100, 50);

      // 缩放到300×300
      window.resizeTo(300, 300);
  4. 视口位置

    • scroll() 、scrollTo()表示要滚动到的坐标
    • scrollBy() 表示要滚动的距离
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      // 相对于当前视口向下滚动100像素
      window.scrollBy(0, 100);

      // 相对于当前视口向右滚动40像素
      window.scrollBy(40, 0);

      // 滚动到页面左上角
      window.scrollTo(0, 0);

      // 滚动到距离屏幕左边及顶边各100像素的位置
      window.scrollTo(100, 100);
  5. window.open():导航到指定URL,也可以用于打开新浏览器窗口接收4个参数:

    • 要加载的URL
    • 目标窗口
    • 特性字符串
    • 新窗口在浏览器历史记录中是否替代当前加载页面的布尔值
      1
      2
      // 与<a href="http://www.wrox.com" target="topFrame"/>相同
      window.open("http://www.wrox.com/", "topFrame");
  6. 定时器

    • setTimeout()指定在一定时间后执行某些代码;clearTimeout()可以取消超时任务,任务执行后再调用clearTimeout()没有效果
    • setInterval()指定每隔一段时间执行某些代码;clearInterval()可以取消循环定时,如果不设置,定时任务会一直执行到页面卸载
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      setTimeout(() => console.log('hi'), 1000);
      setInterval(() => console.log('hello'), 2000);
      // hi
      // hello
      // hello
      // ...

      let timeoutId = setTimeout(() => console.log('hi'), 1000);
      clearTimeout(timeoutId);

      let num = 0, max = 10;
      let incrementNumber = function () {
      num++;
      if (num === max) {
      clearInterval(intervalId);
      console.log('终止');
      }
      };
      let intervalId = setInterval(incrementNumber, 500);
      // ...5秒后
      // 终止

12.2 location对象

1
2
3
4
5
// 给属性赋值命令浏览器进行页面跳转
window.location = 'https://github.com/';

// 重新加载页面,参数true表示强制从服务器强制加载
window.location.reload(true);

12.3 navigator对象

1
2
3
4
5
6
7
8
// 浏览器版本
navigator.appVersion;

// 用户代理
navigator.userAgent;

// 操作系统
navigator.platform;

12.5 history对象

1
2
3
4
5
history.back();     // 后退一页
history.forward(); // 前进一页

history.go(-1); // 后退一页
history.go(1); // 前进一页

第十三章 客户端检测

13.2 用户代理检测

  1. 浏览器核心
    • Firefox的渲染引擎是Gecko
    • Safari的渲染引擎是WebKit
    • Chrome的渲染引擎是Blink,使用V8作为JavaScript引擎

13.3 软件与硬件检测

  1. 浏览器元数据
    • Geolocation API:查询宿主系统并尽可能精确地返回设备的位置信息
    • Connection State:通过navigator.onLine属性来确定浏览器的联网状态
    • NetworkInformation API:同上面一样,用来确定浏览器联网状态,通过navigator.connection属性使用
    • navigator.hardwareConcurrency:返回浏览器支持的逻辑处理器核心数量
    • navigator.deviceMemory:返回设备大致的系统内存大小

第十四章 DOM

  1. nodeType属性用来区分不同类型的节点

    • 值为1:元素节点
    • 值为3:文本节点
    • 值为9:Document节点
  2. 访问元素节点常用方法

    • document.getElementById():通过id得到元素
    • document.getElementsByTagName():通过标签名得到元素数组
    • document.getElementsByClassName():通过类名得到元素数组
    • document.querySelector():通过选择器得到元素
    • document.querySelectorAll():通过选择器得到元素数组
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <div id="box1">
      <p>123</p>
      <p class="spec">456</p>
      <p>789</p>
      </div>

      <script>
      let box1 = document.getElementById('box1');
      let spec = document.getElementsByClassName('spec');
      let p = box1.getElementsByTagName('p'); // [p, p.spec, p]
      let theP = document.querySelector('#box1 .spec'); // <p class="spec">456</p>
      let thePAll = document.querySelectorAll('#box1 p');
      console.log(thePAll[2]); // <p>789</p>
      </script>
  3. 节点关系

    • parentNode:父节点
    • children:子节点
    • firstElementChild:第一个子节点
    • lastElementChild:最后一个子节点
    • previousElementSibling:上一个兄弟节点
    • nextElementSibling:下一个兄弟节点
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      <div id="box1">
      <p>123</p>
      <p id="spec">456</p>
      <p>789</p>
      </div>

      <script>
      let box1 = document.getElementById('box1');
      let spec = document.getElementById('spec');
      console.log(box1.children); // [p, p.spec, p]
      console.log(box1.firstElementChild); // <p>123</p>
      console.log(box1.lastElementChild); // <p>789</p>
      console.log(spec.previousElementSibling); // <p>123</p>
      console.log(spec.nextElementSibling); // <p>789</p>
      </script>
  4. 改变元素节点中的内容

    • innerHTML:以HTML语法设置节点中的内容
    • innerText:以纯文本形式设置节点中的内容
      1
      2
      3
      4
      5
      6
      7
      8
      9
      <div id="box1"></div>
      <div id="box2"></div>

      <script>
      let box1 = document.getElementById('box1');
      let box2 = document.getElementById('box2');
      box1.innerHTML = '<ul><li>咖啡</li><li>牛奶</li></ul>';
      box2.innerText = '<ul><li>咖啡</li><li>牛奶</li></ul>';
      </script>
  5. 改变元素节点的CSS样式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <div id="box1">123</div>
    <a href="www.baidu.com" id="link">
    <img src="images/1.jpg" alt="不存在的图片" id="pic">
    </a>

    <script>
    let box1 = document.getElementById('box1');
    let pic = document.getElementById('pic');
    let link = document.getElementById('link');

    box1.style.bacgroudColor = 'red';
    box1.style.bacgroudImage = 'url(images/2.jpg)';
    box1.style.fontSize = '32px';

    // 修改图片路径
    pic.src = 'images/3.jpg';
    // 修改跳转链接
    link.href = 'diyulan.me';
    </script>
  6. 自定义属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <div id="box1"></div>

    <script>
    let box1 = document.getElementById('box1');
    box1.setAttribute('data-n', 10); // <div id="box1" data-n="10"></div>

    let getDIY = box1.getAttribute('data-n');
    console.log(getDIY); // 10
    </script>
  7. 创建节点和移动节点

    • document.createElement():创建tagname的HTML元素,并没有挂载到DOM树上
    • appendChild():可以将createElement()挂载到父节点内,成为父节点的最后一个子节点
    • insertBefore():可以将createElement()挂载到父节点内,第二个参数决定挂载位置
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      <div id="box1">
      <p>我是段落0</p>
      <p id="move1">我是段落1</p>
      <p id="move2">我是段落2</p>
      </div>
      <div id="box2">
      <p>我是老油条</p>
      </div>

      <script>
      let box1 = document.getElementById('box1');
      let box2 = document.getElementById('box2');
      let moveP1 = document.getElementById('move1');
      let moveP2 = document.getElementById('move2');
      let ps1 = box1.getElementsByTagName('p');
      let ps2 = box2.getElementsByTagName('p');

      // 创建节点
      let createP1 = document.createElement('p');
      let createP2 = document.createElement('p');
      createP1.innerText = '我是新来的';
      createP2.innerText = '我喜欢当第一';

      // 挂载节点
      box1.appendChild(createP1);
      box1.insertBefore(createP2, ps1[0]);

      // 移动节点
      box2.appendChild(moveP1);
      box2.insertBefore(moveP2, ps2[0]);
      </script>
  8. 克隆节点:参数是一个布尔值,表示是否深克隆,如果为true则节点的所有后代节点都会克隆,如果为false则只克隆节点本身

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <div id="box1">
    <ul>
    <li>咖啡</li>
    <li>牛奶</li>
    <li>可乐</li>
    </ul>
    </div>
    <div id="box2"></div>

    <script>
    let box1 = document.getElementById('box1');
    let box2 = document.getElementById('box2');

    // ul是一个类数组,就算只有一个ul标签也要写[0]
    let theUl = box1.getElementsByTagName('ul')[0];

    // 克隆节点,并添加到box2
    let newUl = theUl.cloneNode(true);
    box2.appendChild(newUl);
    </script>
  9. 删除节点:只能由父元素来删除子节点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <div id="box1">
    <p id="delete">123</p>
    </div>

    <script>
    let box1 = document.getElementById('box1');
    let delP = document.getElementById('delete');
    box1.removeChild(delP);
    </script>

第十五章 DOM扩展

15.1 Selectors API

  1. matches():接收一个CSS选择符参数,如果元素匹配返回true,否则返回false

15.3 HTML5

  1. 推荐id选择器负责JS,class选择器负责CSS
    • classList.add():class选择器内添加值
    • classList.remove():class选择器内删除值
      1
      2
      3
      4
      5
      6
      7
      8
      <div id="box1"></div>

      <script>
      let box1 = document.getElementById('box1');
      box1.classList.add('hi'); // <div id="box1" class="hi"></div>
      box1.classList.add('hello'); // <div id="box1" class="hi hello"></div>
      box1.classList.remove('hi'); // <div id="box1" class="hello"></div>
      </script>

第十六章 DOM2和DOM3

16.2 样式

  1. CSS属性名使用连字符表示法;JS中这些属性必须转换为驼峰大小写形式
CSS属性 JS属性
background-image style.backgroundImage
color style.color
display style.display
font-family style.fontFamily

第十七章 事件

17.1 事件流

  1. 事件冒泡:从最具体的元素(文档树中最深的节点)开始触发,然后向上传播至没有那么具体的元素(文档)
  2. 事件捕获:最不具体的节点应该最先收到事件,而最具体的节点应该最后收到事件
  3. DOM事件流分3个阶段:事件捕获、到达目标和事件冒泡

17.2 事件处理程序

  1. 事件处理程序:用户或浏览器执行的某种动作,比如单击、加载、鼠标悬停等事件,为响应事件而调用相关函数;事件处理程序的名字以”on”开头

  2. DOM0事件处理程序,以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <script>
    // 绑定事件处理函数
    let btn = document.getElementById("myBtn");
    btn.onclick = function() {
    console.log(this.id); // "myBtn"
    };

    // 移除事件处理函数
    btn.onclick = null;
    </script>
  3. DOM2事件处理程序

    • addEventListener():接收3个参数:事件名、事件处理函数和一个布尔值,true表示在捕获阶段调用事件处理程序,false(默认值)表示在冒泡阶段调用事件处理程序
    • removeEventListener():通过addEventListener()添加的事件处理程序只能使用removeEventListener()来移除;addEventListener()添加的匿名函数无法移除
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <script>
      let btn = document.getElementById("myBtn");
      btn.addEventListener("click", () => {
      console.log(this.id); // "myBtn"
      }, false);
      // 多个事件处理程序以添加顺序来触发
      btn.addEventListener("click", () => {
      console.log("Hello world!");
      }, false);
      </script>
  4. IE事件处理程序,以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理

    • attachEvent()和detachEvent():这两个方法接收两个同样的参数:事件处理程序的名字和事件处理函数
      1
      2
      3
      4
      5
      6
      <script>
      var btn = document.getElementById("myBtn");
      btn.attachEvent("onclick", function() {
      console.log(this === window); // true
      });
      </script>

17.3 事件对象

  1. 在DOM中发生事件时,所有相关信息都会被收集并存储在一个名为event的对象中。这个对象包含了一些基本信息,比如导致事件的元素、发生的事件类型,以及可能与特定事件相关的任何其他数据
  2. 属性方法
    • bubbles:表示事件是否冒泡
    • cancelable:表示是否可以取消事件的默认行为
    • currentTarget:当前事件处理程序所在的元素
    • defaultPrevented:true表示已经调用preventDefault()方法(DOM3Events中新增)
    • detail:事件相关的其他信息
    • eventPhase:表示调用事件处理程序的阶段:1代表捕获阶段,2代表到达目标,3代表冒泡阶段
    • preventDefault():用于取消事件的默认行为。只有cancelable为true才可以调用这个方法
    • stopImmediatePropagation():用于取消所有后续事件捕获或事件冒泡,并阻止调用任何后续事件处理程序(DOM3Events中新增)
    • stopPropagation():用于取消所有后续事件捕获或事件冒泡。只有bubbles为true才可以调用这个方法
    • target:事件目标
    • trusted:true表示事件是由浏览器生成的。false表示事件是开发者通过JavaScript创建的(DOM3Events中新增)
    • type:被触发的事件类型
    • View:与事件相关的抽象视图。等于事件所发生的window对象

17.4 事件类型

  1. DOM3事件类型:用户界面事件、焦点事件、鼠标事件、滚轮事件、输入事件、键盘事件、合成事件

17.5 内存与性能

  1. 事件委托:可利用事件冒泡,只使用一个事件处理程序来管理一种类型的事件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <ul id="myLinks">
    <li id="goGithub">去GitHub社区</li>
    <li id="doSomething">做点什么</li>
    <li id="sayName">说名字</li>
    </ul>

    <script>
    let list = document.getElementById("myLinks");
    list.addEventListener("click", (event) => {
    let target = event.target;
    switch(target.id) {
    case "goGithub":
    location.href = "https://github.com";
    break;
    case "doSomething":
    document.title = "我更改了文档的标题";
    break;
    case "sayName":
    console.log("幽蓝");
    break;
    } });
    </script>

17.6 模拟事件

  1. 创建模拟事件:document.createEvent()方法创建一个event对象,参数接收一个创建事件类型的字符串
  2. 模拟鼠标事件:initMouseEvent()方法,用于为新对象指定鼠标的特定信息
  3. 模拟键盘事件:initKeyboardEvent()方法,用于为新对象指定键盘的特定信息
  4. 模拟其他事件:initEvent()方法

第十八章 动画与Canvas图形

18.1 使用requestAnimationFrame

  1. requestAnimationFrame():告诉浏览器要执行动画了,浏览器可以通过最优方式确定重绘的时序
  2. cancelAnimationFrame():取消重绘任务
    1
    2
    3
    4
    5
    6
    <script>
    let requestID = window.requestAnimationFrame(()=>{
    console.log('Repaint!');
    });
    window.cancelAnimationFrame(requestID);
    </script>

18.2 基本的画布功能

  1. canvas标签设置宽高,默认画布不可见
    • getContext():参数”2d”,表示获取2D上下文对象
    • toDataURL():导出<canvas>元素上的图像
    • fillStyle:设置填充颜色
    • strokeStyle:设置边框颜色
    • fillRect:绘制一个填充的矩形
    • strokeRect:绘制一个边框的矩形
    • clearRect:设置透明像素擦除一个矩形区域
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      <canvas id="drawing" width="500px" height="300px"></canvas>

      <script>
      let drawing = document.getElementById('drawing');
      let ctx = drawing.getContext('2d');
      ctx.fillStyle = '#95e1d3';
      ctx.strokeStyle = '#b83b5e';
      ctx.fillRect(10, 10, 50, 50);
      ctx.strokeRect(30, 30, 50, 50);
      // 擦除一个钜形区域
      // ctx.clearRect(10, 10, 50, 50);
      </script>

18.3 2D绘图上下文

  1. 绘制路径属性/方法
    • beginPath():新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径
    • closePath():闭合路径之后图形绘制命令又重新指向到上下文中
    • stroke():通过线条来绘制图形轮廓
    • fill():通过填充路径的内容区域生成实心的图形
    • moveTo():从一个点到另一个点的移动过程
    • lineTo():绘制一条从当前位置到指定x以及y位置的直线
    • arc():画一个以(x,y)为圆心的以radius为半径的圆弧(圆)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<canvas id="drawing" width="500px" height="300px"></canvas>

<script>
let drawing = document.getElementById('drawing');
let ctx = drawing.getContext('2d');
// 创建路径
ctx.beginPath();
// 绘制外圆
ctx.arc(100, 100, 99, 0, 2 * Math.PI, false);
// 绘制内圆
ctx.moveTo(194, 100);
ctx.arc(100, 100, 94, 0, 2 * Math.PI, false);
// 绘制分针
ctx.moveTo(100, 100);
ctx.lineTo(100, 15);
// 绘制时针
ctx.moveTo(100, 100);
ctx.lineTo(35, 100);
// 描画路径
ctx.stroke();
</script>
  1. 绘制文本:fillText()和strokeText()

18.4 WebGL

  1. WebGL是画布的3D上下文;WebGL2.0上下文的名字叫”webgl2”,WebGL1.0上下文的名字叫”webgl1”

第十九章 表单脚本

19.1 表单基础

  1. preventDefault():阻止表单提交

    1
    2
    3
    4
    5
    6
    7
    <script>
    let form = document.getElementById("myForm");
    form.addEventListener("submit", (event) => {
    // 阻止表单提交
    event.preventDefault();
    });
    </script>
  2. 表单字段:表单元素可以使用elements属性来访问,elements集合是一个有序列表,通过索引位置和name属性来访问

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <form id="form1" action="">
    <button type="submit">点我提交</button>
    <input type="text" value="请输入内容" name="textbox1">
    <div></div>
    </form>

    <script>
    let form = document.getElementById("form1");
    // 取得表单中的第一个字段
    let field1 = form.elements[0];
    // 取得表单中名为"textbox1"的字段
    let field2 = form.elements["textbox1"];
    // 取得字段的数量
    let fieldCount = form.elements.length;

    console.log(field1); // <button type="submit">点我提交</button>
    console.log(field2); // <input type="text" value="请输入内容" name="textbox1">
    console.log(fieldCount); // 2,只计算表单功能的子标签,div标签不计算在内
    </script>
  3. 表单字段的公共属性和方法

    • disabled:布尔值,表示表单字段是否禁用
    • form:指针,指向表单字段所属的表单。这个属性是只读的
    • name:字符串,这个字段的名字
    • readOnly:布尔值,表示这个字段是否只读
    • tabIndex:数值,表示这个字段在按Tab键时的切换顺序
    • type:字符串,表示字段类型,如”checkbox”、”radio”等
    • value:要提交给服务器的字段值。对文件输入字段来说,这个属性是只读的,仅包含计算机上某个文件的路径
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      <form id="myForm" action="">
      <button type="submit" name="submit-btn">点我提交</button>
      </form>

      <script>
      // 第一次点击之后禁用提交按钮。通过监听submit事件来实现
      // 只有提交按钮才能触发submit事件
      let form = document.getElementById('myForm');
      form.addEventListener('submit', (event)=>{
      let target = event.target;
      // 取得提交按钮
      let btn = target.elements["submit-btn"];
      // 禁用提交按钮
      btn.disabled = true;
      });
      </script>

19.2 文本框编程

  1. 屏蔽字符,只允许输入数字
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <form id="myForm" action="">
    请输入数字:<input type="text" value="" id="textbox">
    </form>

    <script>
    let text = document.getElementById('textbox');
    textbox.addEventListener("keypress", (event) => {
    if (!/\d/.test(String.fromCharCode(event.charCode)) &&
    event.charCode > 9 &&
    !event.ctrlKey){
    event.preventDefault();
    }
    });
    </script>

第二十章 JavaScript API

  1. Atomics API:用于保护代码在多线程内存访问模式下不发生资源争用
  2. postMessage() API:支持从不同源跨文档发送消息,同时保证安全和遵循同源策略
  3. Encoding API:用于实现字符串与缓冲区之间的无缝转换(越来越常见的操作)
  4. File API:提供了发送、接收和读取大型二进制对象的可靠工具
  5. 媒体元素
  6. 拖放 API:支持方便地将元素标识为可拖动,并在操作系统完成放置时给出回应。可以利用它创建自定义可拖动元素和放置目标
  7. Notifications API:提供了一种浏览器中立的方式,以此向用户展示消通知弹层
  8. Streams API:支持以全新的方式读取、写入和处理数据
  9. Timing API:提供了一组度量数据进出浏览器时间的可靠工具
  10. Web Components API:为元素重用和封装技术向前迈进提供了有力支撑
  11. Web Cryptography API:让生成随机数、加密和签名消息成为一类特性

第二十一章 错误处理与调试

21.2 错误处理

  1. 错误类型
    • InternalError类型的错误会在底层JavaScript引擎抛出异常时由浏览器抛出。例如,递归过多导致了栈溢出。这个类型并不是代码中通常要处理的错误,如果真发生了这种错误,很可能代码哪里弄错了或者有危险了
    • EvalError类型的错误会在使用eval()函数发生异常时抛出
    • RangeError错误会在数值越界时抛出
    • ReferenceError会在找不到对象时发生
    • SyntaxError经常在给eval()传入的字符串包含JavaScript语法错误时发生
    • TypeError主要发生在变量不是预期类型,或者访问不存在的方法时
    • URIError在使用encodeURI()或decodeURI()但传入了格式错误的URI时发生
  2. 大多数情况下,最好使用严格相等(===)和严格不相等(!==)操作符来避免类型转换
  3. 使用 try/catch 语句,可以通过更合适的方式对错误做出处理,避免浏览器处理

第二十二章 处理XML

  1. DOMParser类型是简单的对象,可以将XML字符串解析为DOM文档
  2. XMLSerializer类型执行相反操作,将DOM文档序列化为XML字符串
  3. DOM Level 3新增了针对XPath API的规范。该API可以让JavaScript针对DOM文档执行任何XPath查询并得到不同数据类型的结果

第二十三章 JSON

23.1 语法

  1. JSON作为替代XML的一个方案提出,解决XML冗余和啰唆问题,JSON不属于JavaScript,它们只是拥有相同的语法
  2. JSON支持3种类型的值
    • 简单值:字符串、数值、布尔值和null,特殊值undefined不可以
    • 对象:第一种复杂数据类型,对象表示有序键/值对。每个值可以是简单值,也可以是复杂类型
    • 数组:第二种复杂数据类型,数组表示通过索引访问的有序列表。数组的值可以是任意类型
  3. JSON与JS区别:JSON字符串必须使用双引号;JSON没有变量、函数或对象实例的概念;JSON的所有记号都只表示结构化数据

23.2 解析与序列化

  1. JSON的优势:能解析成可用的JS对象,JS开发者可以非常方便地使用JSON数据

  2. JSON对象有两个方法:stringify()和parse()

    • JSON.stringify():能把JavaScript序列化为JSON字符串;值为undefined的任何属性也会被跳过
    • JSON.parse():能把JSON解析为原生JavaScript值;新对象接收解析的值与原对象不是同一个引用
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      let book = {
      title: "Professional JavaScript",
      authors: [
      "Nicholas C. Zakas",
      "Matt Frisbie"
      ],
      edition: 4,
      year: 2017
      };

      let jsonText = JSON.stringify(book);
      // {"title":"Professional JavaScript","authors":["Nicholas C. Zakas","Matt Frisbie"],"edition":4,"year":2017}

      let bookCopy = JSON.parse(jsonText);
      console.log(book === bookCopy); // false
  3. 过滤结果:JSON.stringify()第二个参数缩进想要的JSON字符串;如果第二参数是函数,还能修改值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    let jsonText = JSON.stringify(book, ['title', 'edition']);
    // {"title":"Professional JavaScript","edition":4}

    let jsonText1 = JSON.stringify(book, (key, value) => {
    switch (key) {
    case "authors":
    return value.join(",");
    case "year":
    return 5000;
    case "edition":
    return undefined;
    default:
    return value;
    }
    });
    // {"title":"Professional JavaScript","authors":"Nicholas C. Zakas,Matt Frisbie","year":5000}
  4. 字符串索引:JSON.stringify()第三个参数控制缩进和空格

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 每级缩进4个空格
    let jsonText = JSON.stringify(book, null, 4);
    console.log(jsonText);
    // {
    // "title": "Professional JavaScript",
    // "authors": [
    // "Nicholas C. Zakas",
    // "Matt Frisbie"
    // ],
    // "edition": 4,
    // "year": 2017
    // }

第二十四章 网络请求与远程资源

24.1 XMLHttpRequest对象

  1. 创建XHR对象let xhr = new XMLHttpRequest();

    • open():启动一个请求以备发送;3个参数:请求类型(“get”、”post”等)、请求URL、请求是否异步的布尔值;不会真正发送请求
    • send():发送请求;1个参数:发送的数据;不需要发送数据则必须传入null
  2. XHR对象的属性

    • responseText:作为响应体返回的文本
    • responseXML:如果响应的内容类型是”text/xml”或”application/xml”,那就是包含响应数据的XML DOM文档
    • status:响应的HTTP状态
    • statusText:响应的HTTP状态描述
  3. HTTP状态码

    • 2xx,表示成功
    • 304,表示资源未修改过,是从浏览器缓存中直接拿取的
  4. readyState属性

    • 0:未初始化
    • 1:启动,已调用open()
    • 2:发送,已调用send()
    • 3:接收到部分响应数据
    • 4:接收到全部响应数据
    • readystatechange事件:readystate属性的值由一个值变成另一个值,都会触发readystatechange事件
    • onreadystatechange事件处理程序不会收到event对象
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      let xhr = new XMLHttpRequest();
      xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
      if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
      console.log(xhr.responseText);
      } else {
      console.log("Request was unsuccessful: " + xhr.status);
      }
      }
      };
      xhr.open("get", "example.txt", true);
      xhr.send(null);
  5. XHR请求

    • setRequestHeader():发送额外的请求头部;接收两个参数:头部字段的名称和值;必须在open()之后、send()之前调用setRequestHeader()
    • getResponseHeader():从XHR对象获取响应头部;传入要获取头部的名称
    • getAllResponseHeaders():返回字符串,通过解析头部字段的输出,可以知道服务器发送的所有头部
    • GET请求:向服务器查询某些信息
    • POST请求:向服务器发送应该保存的数据

24.3 跨源资源共享

  1. CORS:使用自定义的HTTP头部允许浏览器和服务器相互了解,以确实请求或响应应该成功还是失败

24.4 替代性跨源技术

  1. 图像Ping:只能发送GET请求;无法访问服务器的响应文本

    1
    2
    3
    4
    5
    let img = new Image();
    img.onload = img.onerror = function () {
    console.log("Done!");
    };
    img.src = "http://www.example.com/test?name=Nicholas";
  2. JSONP:两部分组成回调函数和数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function handleResponse(response) {
    console.log(`
    You're at IP address ${response.ip}, which is in
    ${response.city}, ${response.region_name}`);
    }

    let script = document.createElement("script");
    script.src = "http://freegeoip.net/json/?callback=handleResponse";
    document.body.insertBefore(script, document.body.firstChild);
  3. Fetch API是作为对XHR对象的一种端到端的替代方案而提出的。这个API提供了优秀的基于期约的结构、更直观的接口,以及对Stream API的最好支持

  4. Web Socket是与服务器的全双工、双向通信渠道。与其他方案不同,Web Socket不使用HTTP,而使用了自定义协议,目的是更快地发送小数据块。这需要专用的服务器,但速度优势明显

第二十五章 客户端存储

  1. 每个浏览器限制cookie的数量不同,超过上限浏览器会删除之前设置的cookie
    • 每个域不超过20个cookie
    • 每个域不超过81920字节
    • 单个cookie不超过4096字节
  2. cookie的构成
    • 名称:cookie名不区分大小写,但服务器软件可能会区分大小写
    • 值:存储在cookie里的字符串值。这个值必须经过URL编码
    • 域:cookie有效的域
    • 路径:请求URL中包含这个路径才会把cookie发送到服务器
    • 过期时间:表示何时删除cookie的时间戳;默认浏览器会话结束后删除所有cookie
    • 安全标志:设置之后,只在使用SSL安全连接的情况下才会把cookie发送到服务器
  3. cookie的使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 创建cookie
    document.cookie = 'name=ulan';

    // 名或值非英文,写入时需要encodeURIComponent()编码,读取时decodeURIComponent()解码
    document.cookie = `username=${encodeURIComponent('幽蓝')}`;

    // 过期时间,默认会话时间指关闭浏览器消失
    // expires,值为Data类型,可以指定过期时间
    // max-age,值为数字,表示当前时间多少秒后过期,单位是秒
    document.cookie = `age=9; expires=${new Date(
    '2099-01-01 00:00:00'
    )}`;
    document.cookie = 'job=softwareEngineer; max-age=5'; // 5秒后过期
    document.cookie = `job=softwareEngineer; max-age=${24 * 3600 * 7}`; // 7天后过期

    // 删除cookie
    document.cookie = 'name=ulan; max-age=0';

25.2 Web Storage

  1. Web Storage解决通过客户端存储不需要频繁发送回服务器的数据时使用cookie的问题;localStorage永久存储机制,sessionStorage是跨会话的存储机制
  2. Storage的实例方法
    • setItem(name, value):设置给定name的值
    • getItem(name):取得给定name的值,不存在返回null
    • removeItem(name):删除给定name的名/值对
    • clear():删除所有值
    • key(index):取得给定数值位置的名称
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      // 创建
      localStorage.setItem('username', 'ulan');
      localStorage.setItem('age', '19');
      localStorage.setItem('job', 'softwareEngineer');

      // 获取
      console.log(localStorage.getItem('username')); // ulan
      console.log(localStorage.getItem('friend')); // null

      // 删除job
      localStorage.removeItem('job');
      console.log(localStorage);

      // 全删除
      localStorage.clear();
      console.log(localStorage);

      // sessionStorage使用基本一样
      sessionStorage.setItem('friend', 'hello');
      sessionStorage.getItem('friend');
      sessionStorage.removeItem('friend');
      sessionStorage.clear();

25.3 IndexedDB

  1. IndexedDB是类似于SQL数据库的结构化数据存储机制。不同的是,IndexedDB存储的是对象,而不是数据表。对象存储是通过定义键然后添加数据来创建的。游标用于查询对象存储中的特定数据,而索引可以针对特定属性实现更快的查询

第二十六章 模块

26.1 理解模块模式

  1. 按逻辑分块,各自封装,相互独立,每个块自行决定对外暴露什么,同时自行决定引入执行哪些外部代码

26.4 使用ES6模块

  1. 导出与导入
  • demo.js文件
    1
    2
    3
    4
    export {age, fn, p};
    const age = 9;
    function fn() {}
    const p = {name: '幽蓝'};
  • 第一种导入方式,导入部分内容
    1
    2
    3
    4
    <script type="module">
    import {age} from './demo.js';
    console.log(age); // 9
    </script>
  • 第二种导入方式,导入全部内容,按对象方式来访问
    1
    2
    3
    4
    5
    <script type="module">
    import * as obj from './demo.js';
    console.log(obj.age); // 9
    console.log(obj.p.name); // 幽蓝
    </script>

第二十七章 工作者线程

27.1 工作者线程简介

  1. 工作者线程的价值所在:允许把主线程的工作转嫁给独立的实体,而不会改变现有的单线程模型
  2. JavaScript环境实际上是运行在托管操作系统中的虚拟环境。在浏览器中每打开一个页面,就会分配一个它自己的环境;使用工作者线程,浏览器可以在原始页面环境之外再分配一个完全独立的二级子环境。这个子环境不能与依赖单线程交互的API(如DOM)互操作,但可以与父环境并行执行代码
  3. 主要的工作者线程:专用工作者线程、共享工作者线程和服务工作者线程

27.2 专用工作者线程

  1. worker实例化

    • 比如下方html是父环境,多线程计算3次斐波那契数列,可能需要3秒;单线程计算3次斐波那契数列,可能需要9秒
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      <script>
      const worker1 = new Worker('demo1.js');
      const worker2 = new Worker('demo2.js');
      const worker3 = new Worker('demo3.js');
      worker1.onmessage = e => {
      console.log(e.data);
      }
      worker2.onmessage = e => {
      console.log(e.data);
      }
      worker3.onmessage = e => {
      console.log(e.data);
      }
      // fb执行时间: 2616.596923828125 ms
      // worker2
      // fb执行时间: 2623.839111328125 ms
      // worker1
      // fb执行时间: 2625.576904296875 ms
      // worker3
      </script>
    • demo1.js
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      // 斐波那契数列,当成是相当耗时的函数
      function fb(n) {
      if (n === 1 || n === 2) {
      return 1;
      }
      return fb(n - 1) + fb(n - 2);
      }

      console.time('fb执行时间1');
      const result = fb(43);
      console.timeEnd('fb执行时间1');
      self.postMessage('worker1');
    • demo2.js
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      // 斐波那契数列,当成是相当耗时的函数
      function fb(n) {
      if (n === 1 || n === 2) {
      return 1;
      }
      return fb(n - 1) + fb(n - 2);
      }

      console.time('fb执行时间2');
      const result = fb(43);
      console.timeEnd('fb执行时间2');
      self.postMessage('worker2');
    • demo3.js
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      // 斐波那契数列,当成是相当耗时的函数
      function fb(n) {
      if (n === 1 || n === 2) {
      return 1;
      }
      return fb(n - 1) + fb(n - 2);
      }

      console.time('fb执行时间3');
      const result = fb(43);
      console.timeEnd('fb执行时间3');
      self.postMessage('worker3');
  2. Worker对象支持的事件处理程序属性

    • onerror:在工作者线程中发生ErrorEvent类型的错误事件时会调用指定给该属性的处理程序
    • onmessage:在工作者线程中发生MessageEvent类型的消息事件时会调用指定给该属性的处理程序
    • onmessageerror:在工作者线程中发生MessageEvent类型的错误事件时会调用指定给该属性的处理程序
    • postMessage():用于通过异步消息事件向工作者线程发送信息
    • terminate():用于立即终止工作者线程。没有为工作者线程提供清理的机会,脚本会突然停止

27.5 小结

  1. 工作者线程可以运行异步JavaScript而不阻塞用户界面。这非常适合复杂计算和数据处理,特别是需要花较长时间因而会影响用户使用网页的处理任务。工作者线程有自己独立的环境,只能通过异步消息与外界通信
  2. 工作者线程可以是专用线程、共享线程。专用线程只能由一个页面使用,而共享线程则可以由同源的任意页面共享
  3. 服务工作者线程用于让网页模拟原生应用程序。服务工作者线程也是一种工作者线程,但它们更像是网络代理,而非独立的浏览器线程。可以把它们看成是高度定制化的网络缓存,它们也可以在PWA中支持推送通知

第二十八章 最佳实践

  1. 可维护性与可读性,不同团队有不同开发规范,入乡随俗
  2. 不要修改不属于你的对象。只有你自己创建的才是你的对象,包括自定义类型和对象字面量
  3. 优化代码,先能走在考虑怎么跑吧?