ES6学习笔记(一)

133 阅读7分钟

ES6

let 和 const

  1. let 块级作用域,不存在变量提升,暂时性死区,不允许重复声明

    • for 循环有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。
    for (let i = 0; i < 3; i++) {
      let i = "abc";
      console.log(i);
    }
    // abc
    // abc
    // abc
    

    上面代码正确运行,输出了 3 次 abc。这表明函数内部的变量 i 与循环变量 i 不在同一个作用域,有各自单独的作用域。

    • 在代码块内,使用 let 命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”
    • 暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量
  2. 当 const 指向一个引用类型的数据时,其实指向的是他的地址,如果该数据发生改变,那么 const 的数据会随着改变

    • const 实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const 只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。
  3. globalThis

变量的解构赋值

  • 解构赋值允许指定默认值
  1. 数组的解构赋值

    1. 完全解构
    let [a, b, c] = [1, 2, 3];
    let [head, ...tail] = [1, 2, 3, 4];
    let [x, y, ...z] = ["a"];
    
    1. 不完全解构
    let [x, y] = [1, 2, 3];
    
    1. 对于 Set 结构,也可以使用数组的解构赋值;只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值。
  2. 对象的解构赋值

    1. 数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值
    let { bar, foo } = { foo: "aaa", bar: "bbb" };
    let { log, sin, cos } = Math;
    const { log } = console;
    log("hello"); // hello,将console.log赋值到log变量。
    let { foo: baz } = { foo: "aaa", bar: "bbb" }; // 变量名与属性名不一致,foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo
    
    1. 圆括号与大括号
    // 错误的写法
    let x;
    {x} = {x: 1};
    // SyntaxError: syntax error
    // JavaScript 引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免 JavaScript 将其解释为代码块,才能解决这个问题。
    
    // 正确的写法
    let x;
    ({x} = {x: 1});
    
  3. 字符串、数值和布尔值也可以解构赋值

  4. 函数参数也可以解构赋值

  5. 解构赋值的用途

    1. 交换变量的值
    2. 从函数返回多个值
    function example() {
      return [1, 2, 3];
    }
    let [a, b, c] = example();
    
    1. 输入模块的指定方法
    const { SourceMapConsumer, SourceNode } = require("source-map");
    
    1. 阮一峰 ES6 标准入门

字符串的扩展

  1. 模板字符串
  2. 标签模板
    1. 它可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。这被称为“标签模板”功能(tagged template)。
    alert`hello`;
    // 等同于
    alert(["hello"]);
    
    1. 标签模板其实不是模板,而是函数调用的一种特殊形式。“标签”指的就是函数,紧跟在后面的模板字符串就是它的参数。

字符串的新增方法

  1. includes(), startsWith(), endsWith()
    • includes():返回布尔值,表示是否找到了参数字符串。
    • startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
    • endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
  2. repeat() 重复
  3. padStart(),padEnd() 补全
  4. trimStart(),trimEnd() 消除空格
  5. matchAll() 正则

正则的扩展

  1. RegExp 函数

    • 如果 RegExp 构造函数第一个参数是一个正则对象,那么可以使用第二个参数指定修饰符。而且,返回的正则表达式会忽略原有的正则表达式的修饰符,只使用新指定的修饰符。
  2. 具名组匹配

    • ES2018 引入了具名组匹配(Named Capture Groups),允许为每一个组匹配指定一个名字,既便于阅读代码,又便于引用。
    • exec() 方法在一个指定字符串中执行一个搜索匹配。返回一个结果数组或 null。
    // 原来的
    const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/; // 使用exec方法,就可以将这三组匹配结果提取出来
    const matchObj = RE_DATE.exec("1999-12-31");
    const year = matchObj[1]; // 1999
    const month = matchObj[2]; // 12
    const day = matchObj[3]; // 31
    
    // 现在可以
    const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
    const matchObj = RE_DATE.exec("1999-12-31");
    const year = matchObj.groups.year; // 1999
    const month = matchObj.groups.month; // 12
    const day = matchObj.groups.day; // 31
    
    // 对象解构赋值
    let {
      groups: { one, two },
    } = /^(?<one>.*):(?<two>.*)$/u.exec("foo:bar");
    one; // foo
    two; // bar
    
    // 字符串替换
    let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
    "2015-01-02".replace(re, "$<day>/$<month>/$<year>");
    // '02/01/2015'
    
    • “具名组匹配”在圆括号内部,模式的头部添加“问号 + 尖括号 + 组名”(?),然后就可以在 exec 方法返回结果的 groups 属性上引用该组名。同时,数字序号(matchObj[1])依然有效。
    • 解构赋值和替换
      • 有了具名组匹配以后,可以使用解构赋值直接从匹配结果上为变量赋值。
      • 字符串替换时,使用$<组名>引用具名组。
    • 引用 没看懂 引用
  3. y 修饰符

    1. y 修饰符,叫做“粘连”(sticky)修饰符。
    2. y 修饰符的作用与 g 修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g 修饰符只要剩余位置中存在匹配就可,而 y 修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。
  4. s 修饰符

    1. s 修饰符,使得.可以匹配任意单个字符。
    2. /s 修饰符和多行修饰符/m 不冲突,两者一起使用的情况下,.匹配所有字符,而^和$匹配每一行的行首和行尾。
  5. 后行断言

    1. “先行断言”指的是,x 只有在 y 前面才匹配,必须写成/x(?=y)/。比如,只匹配百分号之前的数字,要写成/\d+(?=%)/。“先行否定断言”指的是,x 只有不在 y 前面才匹配,必须写成/x(?!y)/。比如,只匹配不在百分号之前的数字,要写成/\d+(?!%)/。

    2. “后行断言”正好与“先行断言”相反,x 只有在 y 后面才匹配,必须写成/(?<=y)x/。比如,只匹配美元符号之后的数字,要写成/(?<=$)\d+/。“后行否定断言”则与“先行否定断言”相反,x 只有不在 y 后面才匹配,必须写成/(?<!y)x/。比如,只匹配不在美元符号后面的数字,要写成/(?<!$)\d+/。

数值的扩展

  1. Number.isFinite(), Number.isNaN()
  2. Number.parseInt(), Number.parseFloat()
  3. Number.isInteger()
  4. Math 对象
    1. Math.trunc 方法用于去除一个数的小数部分,返回整数部分。
    2. Math.sign 方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。
    3. Math.cbrt()方法用于计算一个数的立方根。
    4. Math.clz32()、Math.imul()、Math.fround()、Math.hypot()
    5. 对数方法和双曲线方法
  5. **
    1. ES2016 新增了一个指数运算符(**)。
    2. 特点是右结合,而不是常见的左结合。多个指数运算符连用时,是从最右边开始计算的。
  6. BigInt 数据类型

函数的扩展

  1. 函数参数可以设置默认值
  2. rest 参数
    • 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用 arguments 对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
  3. 箭头函数
    1. 如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
    2. 注意:
      1. 函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。
      2. 不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误。
      3. 不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
      4. 不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。
    3. 箭头函数里面根本没有自己的 this,而是引用外层的 this
  4. 函数参数最后一个可以加逗号

尾调用优化

  1. 递归本质上是一种循环操作。纯粹的函数式编程语言没有循环操作命令,所有的循环都用递归实现,这就是为什么尾递归对这些语言极其重要。对于其他支持“尾调用优化”的语言(比如 Lua,ES6),只需要知道循环可以用递归代替,而一旦使用递归,就最好使用尾递归。

  2. ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。

  3. 这是因为在正常模式下,函数内部有两个变量,可以跟踪函数的调用栈。

    • func.arguments:返回调用时函数的参数。
    • func.caller:返回调用当前函数的那个函数。
  4. 尾调用优化发生时,函数的调用栈会改写,因此上面两个变量就会失真。严格模式禁用这两个变量,所以尾调用模式仅在严格模式下生效。

  5. 想在正常模式下可以自己实现尾调用优化,用“循环”代替“递归”

数组的扩展

  1. 扩展运算符
    1. 应用
      • 字符串:可以将字符串转换为真正的数组
      [..."hello"];
      // [ "h", "e", "l", "l", "o" ]
      
      • 能够正确识别四个字节的 Unicode 字符
      console.log("x\uD83D\uDE80y".length); // 4
      console.log([..."x\uD83D\uDE80y"].length); // 3
      
  2. 空位
    1. ES6 明确将空位转为 undefined
    2. 但有的数组方法会将空位跳过,所以尽量在数组中不使用空格
  3. Array.from()
  4. Array.of()
  5. copyWithin()
  6. find() 和 findIndex()
  7. fill() 使用给定值,填充一个数组。
  8. entries(),keys() 和 values() 遍历数组
  9. includes()
  10. flat(),flatMap()

对象的扩展

  1. ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。

    const x = 1;
    const y = 10;
    return { x, y };
    function clear() {
      let ms = {};
    }
    module.exports = { getItem, setItem, clear };
    
  2. 属性名表达式

    1. ES6 允许把表达式放在方括号内作为对象的属性名;
    2. 表达式还可以用于定义方法名。
    3. 属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object]。
  3. 属性的可枚举性和遍历

  4. super,指向当前对象的原型对象。

    1. super
  5. 解构赋值

    1. 变量声明语句之中,如果使用解构赋值,扩展运算符后面必须是一个变量名,而不能是一个解构赋值表达式
  6. 链判断运算符?.

    iterator.return?.(); // 判断函数方法是否存在,存在立即执行,否则返回undefined
    // 左侧的对象是否为null或undefined,如果是返回undefined
    // 等同于
    iterator.return == null || undefined ? undefined : iterator.return();
    
    1. 如果属性链有圆括号,链判断运算符对圆括号外部没有影响,只对圆括号内部有影响。
    2. 为了保证兼容以前的代码,允许 foo?.3:0 被解析成 foo ? .3 : 0,因此规定如果?.后面紧跟一个十进制数字,那么?.不再被看成是一个完整的运算符,而会按照三元运算符进行处理,也就是说,那个小数点会归属于后面的十进制数字,形成一个小数
  7. Null 判断运算符??

    1. 它的行为类似||,但是只有运算符左侧的值为 null 或 undefined 时,才会返回右侧的值。
    2. || 当左侧为 false 或''或 0 时,右侧的默认值也会生效
    const headerText = response.settings.headerText || "Hello, world!";
    
    1. 如果多个逻辑运算符一起使用,必须用括号表明优先级,否则会报错。

对象的新增方法

  1. Object.is() 在所有环境中,只要两个值是一样的,它们就应该相等。
  2. Object.assign() 用于对象的合并
    1. Object.assign 拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)。
    2. 如果希望合并后返回一个新对象,可以改写上面函数,对一个空对象合并。
    3. 为属性指定默认值
  3. Object.getOwnPropertyDescriptors()方法,返回指定对象所有自身属性(非继承属性)的描述对象。
    1. 该方法的引入目的,主要是为了解决 Object.assign()无法正确拷贝 get 属性和 set 属性的问题。
    2. 与其他方法的搭配
  4. 原型
  5. Object.keys(),Object.values(),Object.entries(),Object.fromEntries()
    1. **Object.entries()**方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for...in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环还会枚举原型链中的属性)。