【4w+】字概括ES6 - ES12新增语法特性、知识点补充🏃‍♂️

556 阅读31分钟

ECMAScript 是JavaScript 所基于的脚本语言;

开局一张图

ECMAScript思维导图.jpg

ES6

一、字面量增强的写法

var nickname = "网上冲浪不靠岸"
var age = 18

var obj = {
  // 1.property shorthand(属性的简写)
  nickname,
  age,

  // 2.method shorthand(方法的简写)
  foo: function() {
    console.log(this)
  },
  bar() {
    console.log(this)
  },
  baz: () => {
    console.log(this)
  },

  // 3.computed property name(计算属性名)
  [nickname + 123]: '哈哈哈'
}

// obj.baz()
// obj.bar()
// obj.foo()

// obj[nickname + 123] = "哈哈哈"
console.log(obj)

二、数组的解构

var names = ['ace', 'sabot', 'luffy'];

// var namea = names[0],
//     nameb = names[1],
//     namec = names[2];
// console.log(namea, nameb, namec); //ace sabot luffy

// 1. 对数组的解构: []
var [item1, item2, item3] = names;
console.log(item1, item2, item3); //ace sabot luffy

// 2. 解构最后一个;
var [, , namez] = names;
console.log(namez); //luffy

// 3. 解构第一个剩余放入新数组;
var [namex, ...newNames] = names;
console.log(namex, newNames); //ace [ 'sabot', 'luffy' ]

// 4. 解构的默认值
var [itema, itemb, itemc, itemy = 'long'] = names;
console.log(itemy); //long

三、对象的解构

var obj = {
  name: "yzh",
  age: 18,
  height: 1.80
}

var { name, age, height } = obj
console.log(name, age, height) //yzh 18 1.8

var { name, ...newObj } = obj
console.log(name, newObj); //yzh { age: 18, height: 1.8 }

var { age } = obj
console.log(age) //18

var { name: newName } = obj
console.log(newName) //yzh

var { address: newAddress = "深圳市" } = obj
console.log(newAddress) //深圳市

function foo(info) {
  console.log(info.name, info.age) //yzh 18
}
foo(obj)

function baz({ name, age }) {
  console.log(name, age) //yzh 18
}
baz(obj)

四、let 和 const

  • let 语句声明一个块级作用域的局部变量,并可以初始化为一个值(可选);
  • const 类似用 let语句定义的变量。但常量的值是无法(通过重新赋值)改变的,也不能被重新声明;

4.1. let和const基本使用

var nickName = "wangming";
let alias = "biecheng";
// 注意事项一: 通过let/const定义的变量名是不可以重复定义
// SyntaxError: Identifier 'foo' has already been declared
let alias = "biecheng";

// const constant(常量/衡量)
// 注意事项二: const本质上是传递的值不可以修改
const age = 18;
// TypeError: Assignment to constant variable.
age = 20;

// 但是如果传递的是一个引用类型(内存地址), 可以通过引用找到对应的对象, 去修改对象内部的属性, 这个是可以的
const obj = {
  name: "yzh"
};
obj.name = 'hzy';
console.log(obj);

4.2. let和const作用域提升

  • 作用域提升: 能提前被访问;
  • let和const它们是没有作用域提升的
    //undefined
    console.log(msg); 
    var msg = 'hello world';
    
    // ReferenceError: Cannot access 'l_desc' before initialization
    console.log(l_desc);
    // ReferenceError: Cannot access 'c_desc' before initialization
    console.log(c_desc);
    let l_desc = '这是一段描述!'
    const c_desc = '这是一段描述!'
    

4.3. 作用域的理解

  • ES5作用域
    // 声明对象的字面量
    var obj = {
      address: "shenzhenshi"
    }
    
    // ES5中没有块级作用域
    // 块代码(block code)
    {
      // 声明一个变量
      var address = "shenzhenshi"
    }
    
    // 所以能访问到
    console.log(address); //shenzhenshi  
    
    // 所以在ES5中只有两个东西会形成作用域
    // 1.全局作用域
    // 2.函数作用域
    
    function foo() {
      var able = 'able'
    };
    console.log(able); //ReferenceError: able is not defined
    
  • ES6块级作用域
    // 通过let、const、function、class声明的标识符是具备块级作用域的限制的
    {
      let box = 'box'
      const title = 'title'
      function test() {
        console.log('text function');
      }
      class Person {}
    }
    
    console.log(box); //ReferenceError: box is not defined
    console.log(title); //ReferenceError: title is not defined
    //不同的浏览器有不同实现的(大部分浏览器为了兼容以前的代码, 让function是没有块级作用域)
    test()  //text function
    var p = new Person() //ReferenceError: Person is not defined
    

let和const 在 if / switch / for 中都会产生块级作用域;

4.4. 块级作用域的应用场景

  • 场景:有一组按钮,实现当点击某个按钮时输出点击的是第几个按钮;
  • 四个按钮:按钮1 按钮2 按钮3 按钮4
  • 获取:const btnEls = document.getElementsByTagName('button');
    for (var i = 0; i < btnEls.length; i++) {
      btnEls[i].onclick = function() {
        /**
         * 输出:第4个按钮被点击!(因为函数去上层作用域找i,此时window.i = 4,
         * 所以点击任意一个按钮都会输出同样的内容);
         */
        console.log('第' + i + '个按钮被点击!');
      }  
    };
    console.log(i); //4
    

在全局通过var来声明一个变量,事实上会在window上添加一个属性

  • 解决1:以前解决办法是使用匿名立执行函数;
    for (var i = 0; i < btnEls.length; i++) {
      (function(n) {
        btnEls[n].onclick = function() {
          /**
           * 上层作用域是匿名函数;
           */
          console.log('第' + n + '个按钮被点击!');
        } 
      })(i); 
    };
    console.log(i); //4
    
  • 解决2:使用let在for循环里形成块级作用域;
    for (let i = 0; i < btnEls.length; i++) {
      btnEls[i].onclick = function() {
        console.log('第' + i + '个按钮被点击!');
      }  
    };
    

五、模板字符串

5.1. 模板字符串的基本使用

  • 在ES6之前,如果想要将字符串和一些动态的变量(标识符)拼接到一起,是非常麻烦和丑陋的;
  • ES6允许我们使用字符串模板来嵌入JS的变量或者表达式来进行拼接:
    • 首先,使用 `` 符号来编写字符串,称之为模板字符串;
    • 其次,在模板字符串中,我们可以通过 ${expression} 来嵌入动态的内容;
      const name = "yzh";
      const age = 18;
      const height = 1.80;
      
      // ES6之前拼接字符串和其他标识符
      console.log("my name is " + name + ", age is " + age + ", height is " + height)
      //my name is yzh, age is 18, height is 1.8
      
      // ES6提供模板字符串 ``
      const message = `my name is ${name}, age is ${age}, height is ${height}`
      console.log(message)
      //my name is yzh, age is 18, height is 1.8
      
      
      const ageMsg = `age double is ${age * 2}`
      console.log(ageMsg) //age double is 36
      
      function doubleAge() {
        return age * 2
      }
      const ageMsg2 = `double age is ${doubleAge()}`
      console.log(ageMsg2) //double age is 36
      

5.2. 标签模板字符串的使用

function foo(m, n, x) {
  console.log(m, n, x)
}

// 函数普通调方式
foo("Hello", "World") //Hello World undefined 

// 另外调用函数的方式: 标签模块字符串
foo`` //[ '' ] undefined undefined 

foo`Hello World` //[ 'Hello World' ] undefined undefined

// 第一个参数依然是模块字符串中整个字符串, 只是被切成多块,放到了一个数组中
// 第二个参数是模块字符串中, 第一个 ${} 嵌入动态的内容
// 第三个参数是模块字符串中, 第二个 ${} 嵌入动态的内容
const name = "yzh"
const age = 18
foo`Hello${name}Wo${age}rld` //[ 'Hello', 'Wo', 'rld' ] yzh 18

5.3. styled-components库

图:style.js 文件 styled.png

styled-components 是一个针对 React 的 css in js 类库;

六、函数的默认值

6.1. 写起来很麻烦, 并且代码的阅读性是比较差;

function foo() {
  var m = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'aaa';
  var n =  arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'bbb';

  console.log(m, n);
}

foo() //aaa bbb
foo('aaa', 'bbb') //aaa bbb

6.2. ES12逻辑或运算符

  • 写法有bug:比如给函数传入 0 ,''(空字符串);
    function foo(m, n) {
      m = m || "aaa"
      n = n || "bbb"
    
      console.log(m, n)
    }
    
    foo() //aaa bbb
    foo(0, "") //aaa bbb
    

6.3. ES6可以给函数参数提供默认值

//1.
function foo(m = "aaa", n = "bbb") {
  console.log(m, n)
}

foo() //aaa bbb
foo(0, "") //0 

//2.
function baz(x, y = x + 1) {
  console.log(x, y)
}

baz(10) // 10, 11

6.4. 函数的对象参数和默认值以及解构

  • 默认值也可以和解构一起来使用:
    function printInfo({ name, age } = { name: "yzh", age: 18 }) {
      console.log(name, age)
    }
    printInfo() //yzh 18
    printInfo({}) //undefined undefined
    printInfo({ name: "ace", age: 22 }) //ace 22
    
    // 另外一种写法
    function printInfo1({ name = "hzy", age = 18 } = {}) {
      console.log(name, age)
    }
    printInfo1() //hzy 18
    printInfo1({}) //hzy 18
    printInfo1({ name: 'sabot', age: 20 }) //sabot 20
    

6.5. 函数的长度(length)

  • 默认值会改变函数的length的个数,默认值以及后面的参数都不计算在length之内了;
    function baz(x, y, z, q, w) {
      console.log(x, y, z, q, w)
    }
    console.log(baz.length) //5
    
    function baz(x, y, z = 30, q, w) {
      console.log(x, y, z, q, w)
    }
    console.log(baz.length) //2
    

七、函数的剩余参数

  • ES6中引用了rest parameter,可以将不定数量的参数放入到一个数组中:
    • 如果最后一个参数是 ... 为前缀的,那么它会将剩余的参数放到该参数中,并且作为一个数组;
  • 剩余参数和arguments的区别:
    • 剩余参数只包含那些没有对应形参的实参,而 arguments 对象包含了传给函数的所有实参;
    • arguments对象不是一个真正的数组,而rest参数是一个真正的数组,可以进行数组的所有操作;
    function foo(m, n, ...args) {
      console.log(m, n) //20 30
      console.log(args) //[ 40, 50, 60 ]
    
      console.log(arguments) //[Arguments] { '0': 20, '1': 30, '2': 40, '3': 50, '4': 60 }
    }
    
    foo(20, 30, 40, 50, 60)
    

1)arguments是早期的ECMAScript中为了方便去获取所有的参数提供的一个数据结构,而rest参数 是ES6中提供并且希望以此来替代arguments的;

2)剩余参数必须放到最后一个位置,否则会报错;

function foo(...args) {
  console.log(args) //[ 20, 30, 40, 50, 60 ]
}

// 错误示例
// function foo(...args, x, y) {
//   console.log(args)
// }

foo(20, 30, 40, 50, 60)

八、箭头函数补充

  • 箭头函数表达式的语法比函数表达式更简洁,并且没有自己的this(不绑定this,只会往上层作用域找),arguments;
  • 箭头函数是没有显式原型的,所以不能作为构造函数,使用new来创建对象;
    const foo = () => {
      console.log('foo');
    };
    
    console.log(foo.prototype); //undefined
    
    const f = new foo(); //TypeError: foo is not a constructor
    

九、展开语法 (Spread syntax)

  • 在函数调用/数组构造时,将数组表达式或者string在语法层面展开;
  • 在构造字面量对象时, 将对象表达式按key-value的方式展开;
    const names = ["ace", "sabot", "luffy"]
    const name = "yzh"
    const info = { name: "hzy", age: 18 }
    
    // 1.函数调用时
    function foo(x, y, z) {
      console.log(x, y, z)
    }
    
    // foo.apply(null, names)
    foo(...names) //ace sabot luffy
    foo(...name) //y z h
    
    // 2.构造数组时
    const newNames = [...names, ...name]
    console.log(newNames) //[ 'ace', 'sabot', 'luffy', 'y', 'z', 'h' ]
    
    // 3.构建对象字面量时,也可以使用展开运算符,这个是在ES2018(ES9)中添加的新特性;
    const obj = { ...info, address: "shenzhenshi", ...names }
    console.log(obj)
    /*
    {
      '0': 'ace',
      '1': 'sabot',
      '2': 'luffy',
      name: 'hzy',
      age: 18,
      address: 'shenzhenshi'
    }
    */
    
  • 展开运算符其实是一种浅拷贝;
    const info = {
      name: "yzh",
      friend: { name: "ace" }
    }
    
    const obj = { ...info }
    console.log(obj) //{ name: 'yzh', friend: { name: 'ace' } }
    obj.friend.name = "luffy"
    
    console.log(info.friend.name) //luffy
    

十、Symbol数据类型

  • Symbol是ES6中新增的一个基本数据类型,翻译为符号;
  • 在ES6之前,对象的属性名都是字符串形式,那么很容易造成属性名的冲突;
    • 比如原来有一个对象,我们希望在其中添加一个新的属性和值,但是我们在不确定它原来内部有什么内容的情况下,很容易造成冲突,从而覆盖掉它内部的某个属性;
      var obj = {
        name: "yzh",
        friend: { name: "ace" },
        age: 18
      };
      
      var newObj = { ...obj, name: 'new yzh'};
      console.log(newObj); //{ name: 'new yzh', friend: { name: 'ace' }, age: 18 }
      
  • Symbol就是为了解决类似这样的问题,用来生成一个独一无二的值
    • Symbol值是通过Symbol函数来生成的,生成后可以作为属性名;
    • 也就是在ES6中,对象的属性名可以使用字符串,也可以使用Symbol值;

10.1. Symbol的基本使用

  • Symbol即使多次创建值,它们也是不同的:Symbol函数执行后每次创建出来的值都是独一无二的;
    const s1 = Symbol();
    const s2 = Symbol();
    
    console.log(s1 === s2); //false
    

10.2. Symbol作为属性名

前面:

const s1 = Symbol();
const s2 = Symbol();

const obj = {};
  • 1)在定义对象字面量时:
    const obj = {
      [s1]: "ace",
      [s2]: "sabot"
    }
    
  • 2)属性名赋值:
    obj[s3] = "luffy"
    
  • 3)Object.defineProperty:
    const s4 = Symbol()
    Object.defineProperty(obj, s4, {
      enumerable: true,
      configurable: true,
      writable: true,
      value: "long"
    })
    
    //ace sabot luffy long
    console.log(obj[s1], obj[s2], obj[s3], obj[s4])
    

注意: 不能通过.语法获取

console.log(obj.s1)

10.3. Object.getOwnPropertySymbols

  • 使用Symbol作为key的属性名,在遍历/Object.keys等中是获取不到这些Symbol值;
  • 需要Object.getOwnPropertySymbols来获取所有Symbol的key;
    • Object.getOwnPropertySymbols()  方法返回一个给定对象自身的所有 Symbol 属性的数组;
      console.log(Object.keys(obj));  //[]
      console.log(Object.getOwnPropertyNames(obj));  //[]
      
      console.log(Object.getOwnPropertySymbols(obj)); //[ Symbol(), Symbol(), Symbol(), Symbol() ]
      const sKeys = Object.getOwnPropertySymbols(obj);
      for (const sKey of sKeys) {
        console.log(obj[sKey]) //ace sabot luffy long
      };
      

Object.getOwnPropertyNames() 方法返回一个由指定对象的所有自身属性的属性名(包括不 可枚举属性但不包括 Symbol 值作为名称的属性)组成的数组;

10.4. 相同值的Symbol

  • 使用Symbol.for方法创建相同的Symbol;
  • 使用Symbol.keyFor方法来获取Symbol对应的key;
    • 语法:Symbol.for(key) / Symbol.keyFor(symbol)
      const sa = Symbol.for("aaa")
      const sb = Symbol.for("aaa")
      console.log(sa === sb); //true
      
      const key = Symbol.keyFor(sa);
      console.log(key); //aaa
      
      const sc = Symbol.for(key);
      console.log(sa === sc); //true
      

🍚补充

  • 可以在创建Symbol值的时候传入一个描述description:这个是ES2019(ES10)新增的特性;
    // ES2019(ES10)中, Symbol还有一个描述(description)
    const s = Symbol("miaoshu");
    console.log(s.description); //miaoshu
    

在ES6之前,我们存储数据的结构主要有两种:数组、对象;

在ES6中新增了另外两种数据结构:Set、Map,以及它们的另外形式WeakSet、WeakMap;

十一、数据结构Set

  • Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用,但是和数组的区别是元素不能重复
  • Set 对象是值的集合;
    • 创建Set我们需要通过Set构造函数;
      const set = new Set();
      
      console.log(set); //Set(0) {}
      
  • Set中存放的元素是不会重复的,Set有一个非常常用的功能就是给数组去重:
    const set = new Set([10, 12, 8, 10, 12, 6]);
    console.log(set); //Set(4) { 10, 12, 8, 6 }
    
    //对数组去重:
    const arr = [5, 8, 6, 5, 2, 4, 6, 8];
    
    const arrSet = new Set(arr);
    console.log(arrSet); //Set(5) { 5, 8, 6, 2, 4 }
    
    const newArr = Array.from(arrSet);
    console.log(newArr); //[ 5, 8, 6, 2, 4 ]
    
    const newArr2 = [...arrSet];
    console.log(newArr2); //[ 5, 8, 6, 2, 4 ]
    

11.1. Set常见的属性

  • size:返回Set中元素的个数;
    const arr = [5, 8, 6, 5, 2, 4, 6, 8];
    const arrSet = new Set(arr);
    
    console.log(arrSet.size); //5
    

11.2. Set常用的方法

前面:

const set = new Set();
  • add(value)方法:添加某个元素,返回Set对象本身;
    set.add('ace');
    set.add('sabot');
    set.add('luffy');
    set.add('yzh');
    const res = set.add(1);
    
    console.log(res); //Set(5) { 'ace', 'sabot', 'luffy', 'yzh', 1 }
    console.log(set); //Set(5) { 'ace', 'sabot', 'luffy', 'yzh', 1 }
    
  • delete(value):从set中删除和这个值相等的元素,返回boolean类型;
    const dSet = set.delete(1);
    
    console.log(dSet); //true
    
    console.log(set); //Set(4) { 'ace', 'sabot', 'luffy', 'yzh' }
    
  • has(value):判断set中是否存在某个元素,返回boolean类型;
    const hSet = set.has(1);
    const hSet1 = set.has('yzh');
    
    console.log(hSet); //false
    console.log(hSet1); //true
    
    console.log(set); //Set(4) { 'ace', 'sabot', 'luffy', 'yzh' }
    
  • clear():清空set中所有的元素,没有返回值;
    set.clear();
    console.log(set); //Set(0) {}
    
  • forEach():通过forEach遍历Map;
    set.forEach(item => {
      console.log(item);
    });
    
    //Set也是支持for of遍历的;
    for (const item of set) {
      console.log(item, 'for of');
    };
    

十二、数据结构WeakSet

  • 和Set类似的一个数据结构,也是内部元素不能重复的数据结构;
  • 和Set的区别:
    • 1)WeakSet中只能存放对象类型,不能存放基本数据类型;
    • 2)WeakSet对元素的引用是弱引用:如果没有其它引用对某个对象进行引用,那么GC(垃圾回收机制)可以对该对象进行回收;
      const wSet = new WeakSet();
      
      //TypeError: Invalid value used in weak set
      wSet.add(12)
      

12.1. WeakSet常用的方法

前面:

const wSet = new WeakSet();

const obj = {
  name: 'yzh',
  age: 18
};
const info = {
  address: '深圳市'
};
const desc = {
  hobby: 'game'
};
  • add(value):添加某个元素,返回WeakSet对象本身;
    wSet.add(obj);
    wSet.add(info);
    wSet.add(desc);
    
    console.log(wSet);
    
  • delete(value):从WeakSet中删除和这个值相等的元素,返回boolean类型;
    const res = wSet.delete(desc);
    console.log(res); //true
    
  • has(value):判断WeakSet中是否存在某个元素,返回boolean类型;
    console.log('has', wSet.has(desc)); //has false
    console.log('has2', wSet.has(info)); //has2 true
    

12.2. WeakSet应用场景

  • WeakSet没有size属性,所以不支持forEach、for of遍历;
  • WeakSet只是对对象的弱引用,如果我们遍历获取到其中的元素,那么有可能造成对象不能正常的销毁;
  • 应用场景:
    const personSet = new WeakSet();
    class Person {
      constructor() {
        personSet.add(this)
      }
    
      running() {
        if (!personSet.has(this)) {
          throw new Error('不能通过非构造方法出来的对象调用running方法');
        };
    
        console.log('runing', this);
      }
    };
    
    let p = new Person();
    
    p.running();
    
    // Error: 不能通过非构造方法出来的对象调用running方法
    // p.running.call({name: 'yzh'});
    

十三、数据结构Map

  • Map 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者基本类型)都可以作为一个键或一个值;
  • JS里对象中的key是不可以使用对象的,否则会转为字符串('[object Object]')对象,我们可能希望通过其他类型作为key,比如对象,这个时候就可以使用Map;

前面:

const obj = {
  name: 'yzh'
};
const obj2 = {
  name: 'ace'
};

const map = new Map([[obj, 'luffy'], [obj2, 'ace'], [2, 'sabo']]);
console.log(map); //Map(3) {{ name: 'yzh' } => 'luffy', { name: 'ace' } => 'ace', 2 => 'sabo'}

13.1. Map的常见的属性

  • size:返回Map中元素的个数;
    console.log(map.size); //3
    

13.2. Map的常用的方法

  • set(key, value):在Map中添加key、value,并且返回整个Map对象;

    map.set('t-bag', '111');
    console.log(map);
    /*
    Map(4) {
      { name: 'yzh' } => 'luffy',
      { name: 'ace' } => 'ace',
      2 => 'sabo',
      't-bag' => '111'
    }
    */
    
  • get(key):根据key获取Map中的value;

    console.log(map.get('t-bag')); //'111'
    
  • delete(key):根据key删除一个键值对,返回Boolean类型;

    map2.delete('t-bag');
    console.log(map2);
    /*
    Map(3) {
      { name: 'yzh' } => 'luffy',
      { name: 'ace' } => 'ace',
      2 => 'sabo'
    }
    */
    
  • has(key):判断是否包括某一个key,返回Boolean类型;

    console.log(map.has('t-bag')); //false
    console.log(map.has(obj)); //true
    
  • clear():清空所有的元素;

    // map.clear();
    // console.log(map); //Map(0) {}
    
  • forEach():通过forEach遍历Map;

    map.forEach((item, key) => {
      console.log(item, key, 'foreach');
    });
    /*
    luffy { name: 'yzh' } foreach
    ace { name: 'ace' } foreach
    sabo 2 foreach
    */
    
  • keys():返回一个引用的迭代器对象。它包含按照顺序插入 Map 对象中每个元素的 key 值;

    const map1 = new Map();
    
    map1.set('0', 'foo');
    map1.set(1, 'bar');
    
    const iterator1 = map1.keys();
    
    console.log(iterator1.next().value); //'0'
    console.log(iterator1.next().value); //1
    
  • values():方法返回一个新的迭代器对象。它包含按顺序插入 Map 对象中每个元素的 value 值;

    const map1 = new Map();
    
    map1.set('0', 'foo');
    map1.set(1, 'bar');
    
    const iterator1 = map1.values();
    
    console.log(iterator1.next().value); //"foo"
    console.log(iterator1.next().value); //"bar"
    
  • entries():方法返回一个新的迭代器对象。其中包含 Map 对象中按插入顺序排列的每个元素的 [key, value] 对;

    const map1 = new Map();
    
    map1.set('0', 'foo');
    map1.set(1, 'bar');
    
    const iterator1 = map1.entries();
    
    console.log(iterator1.next().value); //["0", "foo"]
    console.log(iterator1.next().value); //[1, "bar"]
    
  • 也可以使用 for...of 方法迭代 Map

    const myMap = new Map();
    myMap.set(0, 'zero');
    myMap.set(1, 'one');
    
    for (const item of myMap) {
      console.log(`${item[0]} = ${item[1]}`);
    }
    for (const [key, value] of myMap) {
      console.log(`${key} = ${value}`);
    }
    // 0 = zero
    // 1 = one
    
    for (const key of myMap.keys()) {
      console.log(key);
    }
    // 0
    // 1
    
    for (const value of myMap.values()) {
      console.log(value);
    }
    // zero
    // one
    
    for (const [key, value] of myMap.entries()) {
      console.log(`${key} = ${value}`);
    }
    // 0 = zero
    // 1 = one
    

十四、数据结构WeakMap

  • 与Map区别:
    • 1.WeakMap中的key只能使用对象类型,不接受其它的类型作为key;
    • 2.WeakMap的key对对象的引用是弱引用,如果没有其它引用对这个对象进行强引用,那么GC可以对该对象进行回收;
    • 没有clear()方法,没有遍历;
      const wMap = new WeakMap();
      
      //TypeError: Invalid value used as weak map key
      wMap.set('str', 'hello weakMap');
      //TypeError: Invalid value used as weak map key
      wMap.set(1, 'hello yzh');
      

14.1. WeakMap常用的方法

  • set(key, value):在Map中添加key、value,并且返回整个Map对象;
  • get(key):根据key获取Map中的value;
  • has(key):判断是否包括某一个key,返回Boolean类型;
  • delete(key):根据key删除一个键值对,返回Boolean类型;
    const obj = {name: "obj"};
    const wMap = new WeakMap();
    
    wMap.set(obj, "111obj");
    console.log(wMap);
    
    console.log(wMap.get(obj)); //111obj
    
    console.log(wMap.has(obj)); //true
    
    // console.log(wMap.delete(obj)); //true
    // console.log(wMap);
    

14.2. WeakMap的应用

// 响应式中的WeakMap

const obj1 = {
  name: 'obj1',
  age: 18
};
function obj1NameFn1() {
  console.log('Name-obj1NameFn1');
};
function obj1NameFn2() {
  console.log('Name-obj1NameFn2');
};
function obj1AgeFn1() {
  console.log('Age-obj1AgeFn1');
};
function obj1AgeFn2() {
  console.log('Age-obj1AgeFn2');
};

const obj2 = {
  name: 'obj2',
  age: 20
};
function obj2NameFn1() {
  console.log('obj2NameFn1');
};
function obj2NameFn2() {
  console.log('obj2NameFn2');
};
function obj2AgeFn1() {
  console.log('obj2AgeFn1');
};
function obj2AgeFn2() {
  console.log('obj2AgeFn2');
};


// 1.创建WeakMap
const wMap = new WeakMap();

// 2.收集依赖结构
//  2.1.对obj1收集的数据结构;
    const obj1Map = new Map();
    obj1Map.set("name", [obj1NameFn1, obj1NameFn2]);
    obj1Map.set("age", [obj1AgeFn1, obj1AgeFn2]);
    wMap.set(obj1, obj1Map);

//  2.2.对obj2收集的数据结构;
    const obj2Map = new Map();
    obj2Map.set("name", [obj2NameFn1, obj2NameFn2]);
    wMap.set(obj2, obj2Map);
  
  //console.log(wMap);

// 3.如果obj1.name发生了改变;
obj1.name = "yzh1";
const targetMap = wMap.get(obj1);
const fns = targetMap.get('name');
fns.forEach(fn => fn());

十五、Proxy

15.1. 监听对象的操作

  • 监听一个对象中的属性被获取或设置的过程;
    const obj = {
      name: 'yzh',
      age: 18
    };
    
    Object.keys(obj).forEach(key => {
      let value = obj[key];
    
      Object.defineProperty(obj, key, {
        get: function() {
          console.log(`监听到obj对象的${key}属性被访问了`);
          return value;
        },
        
        set: function(newVal) {
          console.log(`监听到obj对象的${key}属性被设置了`);
          value = newVal;
        }
      })
    });
    
    console.log(obj.name); //监听到obj对象的name属性被访问了 yzh
    console.log(obj.age); //监听到obj对象的age属性被访问了 18
    
    obj.name = 'ace'; //监听到obj对象的name属性被设置了
    obj.age = 22; //监听到obj对象的age属性被设置了
    
    //存储数据描述符并没有监听拦截到
    obj.height = 1.80;
    

这样做的缺点:

1)首先,Object.defineProperty设计的初衷,不是为了去监听拦截一个对象中所有的属性的; 在定义某些属性的时候,初衷其实是定义普通的属性,但是后面我们强行将它变成了数据属性描述符;

2)其次,如果我们想监听更加丰富的操作,比如新增属性、删除属性,那么Object.defineProperty是无能 为力的;

3)所以,存储数据描述符设计的初衷并不是为了去监听一个完整的对象;

15.2. Proxy基本使用

  • Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等);
    • 也就是说,如果要监听一个对象的相关操作,那么可以先创建一个代理对象(Proxy对象);
    • 然后对该对象的所有操作,都通过代理对象来完成,代理对象可以监听我们想要对原对象进行哪些操作;
  • 语法:const p = new Proxy(target, handler)
  • 参数:
    • target:需要侦听的对象;要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理);
    • handler:处理对象;一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为;

1)Proxy的set和get捕获器

  • 侦听某些具体的操作,在handler中添加对应的捕捉器;
  • set和get分别对应的是函数类型;
    • set函数有四个参数:
      • target:目标对象(侦听的对象);
      • property:将被设置的属性key;
      • value:新属性值;
      • receiver:调用的代理对象;
    • get函数有三个参数:
      • target:目标对象(侦听的对象);
      • property:被获取的属性key;
      • receiver:调用的代理对象;
  • 监听一个对象中的属性被获取或设置的过程;
    const obj = {
      name: 'yzh',
      age: 18
    };
    
    const objProxy = new Proxy(obj, {
      //获取值时的捕捉器;
      get: function(target, key) {
        console.log(`监听到对象的${key}属性被get了`, target);
        return target[key];
      },
      
      //设置值时的捕捉器;
      set: function(target, key, newVal) {
        console.log(`监听到对象的${key}属性被set了`, target);
        target[key] = newVal;
      },
      
      // 监听in的捕获器
      has: function(target, key) {
        console.log(`监听到对象的${key}属性in操作`, target);
        return key in target;
      },
      
      //监听delete的捕获器
      deleteProperty: function(target, key) {
        console.log(`监听到对象的${key}属性delete操作`, target);
        delete target[key];
      }
    });
    
    console.log(objProxy.name); //监听到对象的name属性被get了 yzh
    console.log(objProxy.age); //监听到对象的age属性被get了 18
    
    objProxy.name = 'ace'; //监听到对象的name属性被set了
    objProxy.age = 22; //监听到对象的age属性被set了
    
    console.log('name' in objProxy); //监听到对象的name属性in操作 true
    
    delete objProxy.name; //监听到对象的name属性delete操作
    console.log(obj); //{ age: 22 }
    

2)Proxy所有捕获器

  • handler 对象是一个容纳一批特定属性的占位符对象。它包含有 Proxy 的各个捕获器;
  • 所有的捕捉器是可选的。如果没有定义某个捕捉器,那么就会保留源对象的默认行为; proxy.png

3)Proxy的construct和apply

  • proxy对函数对象的监听:
    function foo(a, b) {
      this.num = a;
      this.num2 = b;
    };
    
    const fooProxy = new Proxy(foo, {
      apply: function(target, thisArg, argArray) {
        console.log('函数apply的监听', thisArg, argArray);
        return target.apply(thisArg, argArray);
      },
      construct: function(target, argArray, newTarget) {
        console.log('函数new的监听', argArray, newTarget);
        return new target(...argArray);
      }
    });
    
    //函数apply的监听 { name: 'haha' } [ 'ace', 'sabo' ]
    fooProxy.apply({name: 'haha'}, ['ace', 'sabo']); 
    
    //函数new的监听 [ '123', '321' ] [Function: foo]
    const fn = new fooProxy('123', '321');
    
    console.log(fn.num); //123
    console.log(fn.num2); //321
    

十六、Reflect

16.1. Reflect的作用

  • Reflect也是ES6新增的一个API,它是一个对象,字面的意思是反射。
    • 它主要提供了很多操作JavaScript对象的方法,有点像Object中操作对象的方法;
    • 比如Reflect.getPrototypeOf(target) 类似于 Object.getPrototypeOf();
    • 比如Reflect.defineProperty(target, propertyKey, attributes) 类似Object.defineProperty();
  • 为什么还需要有Reflect这样的新增对象呢?
    • 这是因为在早期的ECMA规范中没有考虑到这种对 对象本身 的操作如何设计会更加规范,所以将这些API放到了Object上面;
    • 但是Object作为一个构造函数,这些操作实际上放到它身上并不合适;
    • 另外还包含一些类似于 in、delete操作符,让JS看起来是会有一些奇怪的;
    • 所以在ES6中新增了Reflect,让我们这些操作都集中到了Reflect对象上;

16.2. Reflect的静态方法

  1. Reflect.getPrototypeOf(target)
    • 类似于 Object.getPrototypeOf();
  2. Reflect.setPrototypeOf(target, prototype)
    • 设置对象原型的函数. 返回一个 Boolean, 如果更新成功,则返回true;
  3. Reflect.isExtensible(target)
    • 类似于 Object.isExtensible();
  4. Reflect.preventExtensions(target)
    • 类似于 Object.preventExtensions()。返回一个Boolean;
  5. Reflect.getOwnPropertyDescriptor(target, propertyKey)
    • 类似于 Object.getOwnPropertyDescriptor()。如果对象中存在该属性,则返回对应的属性描述符, 否则返回 undefined;
  6. Reflect.defineProperty(target, propertyKey, attributes)
    • 和 Object.defineProperty() 类似。如果设置成功就会返回 true;
  7. Reflect.ownKeys(target)
    • 返回一个包含所有自身属性(不包含继承属性)的数组。(类似于Object.keys(), 但不会受enumerable影响);
  8. Reflect.has(target, propertyKey)
    • 判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同;
  9. Reflect.get(target, propertyKey[, receiver])
    • 获取对象身上某个属性的值,类似于 target[name];
  10. Reflect.set(target, propertyKey, value[, receiver])
    • 将值分配给属性的函数。返回一个Boolean,如果更新成功,则返回true;
  11. Reflect.deleteProperty(target, propertyKey)
    • 作为函数的delete操作符,相当于执行 delete target[name];
  12. Reflect.apply(target, thisArgument, argumentsList)
    • 对一个函数进行调用操作,同时可以传入一个数组作为调用参数。和Function.prototype.apply() 功能类似;
  13. Reflect.construct(target, argumentsList[, newTarget])
    • 对构造函数进行 new 操作,相当于执行 new target(...args);

16.3. Reflect的使用

const obj = {
  name: 'yzh',
  age: 18
};

const objProxy = new Proxy(obj, {
  //获取值时的捕捉器;
  get: function(target, key) {
    console.log(`监听到对象的${key}属性被get了`, target);
    return Reflect.get(target, key);
  },
  
  //设置值时的捕捉器;
  set: function(target, key, newVal) {
    console.log(`监听到对象的${key}属性被set了`, target);
    Reflect.set(target, key, newVal);
  },
  
  // 监听in的捕获器
  has: function(target, key) {
    console.log(`监听到对象的${key}属性in操作`, target);
    return Reflect.has(target, key);
  },
  
  //监听delete的捕获器
  deleteProperty: function(target, key) {
    console.log(`监听到对象的${key}属性delete操作`, target);
    Reflect.deleteProperty(target, key);
  }
});

console.log(objProxy.name);
console.log(objProxy.age);

objProxy.name = 'ace';
objProxy.age = 22;
// console.log(obj);

console.log('name' in objProxy);

delete objProxy.name;
// console.log(obj);

16.4. Receiver的作用

  • 如果源对象(obj)有setter、getter的访问器属性,那么可以通过receiver来改变里面的this;
    const obj = {
      _name: "yzh",
      get name() {
        return this._name
      },
      set name(newValue) {
        this._name = newValue
      }
    };
    
    const objProxy = new Proxy(obj, {
      get: function(target, key, receiver) {
        // receiver是创建出来的代理对象
        console.log("get方法被访问--------", key, receiver);
        console.log(receiver === objProxy);
        return Reflect.get(target, key, receiver);
      },
      set: function(target, key, newValue, receiver) {
        console.log("set方法被访问--------", key);
        Reflect.set(target, key, newValue, receiver);
      }
    });
    
    console.log(objProxy.name)
    objProxy.name = "ace";
    
    console.log(objProxy.name)
    

16.5. Reflect的construct

  • 语法:Reflect.construct(target, argumentsList, newTarget);
  • 参数:
    • target:被运行的目标构造函数;
    • argumentsList:类数组,目标构造函数调用时的参数;
    • newTarget:作为新创建对象的原型对象的 constructor 属性。默认值为 target
  • Reflect.construct() 方法的行为有点像 new 操作符构造函数,相当于运行 new target(...args)
    function Student(name, age) {
      this.name = name;
      this.age = age;
    };
    
    function Teacher() {
    
    };
    
    // 执行Student函数中的内容, 但是创建出来对象是Teacher对象
    const teacher = Reflect.construct(Student, ["yzh", 18], Teacher);
    console.log(teacher); //Teacher { name: 'yzh', age: 18 }
    console.log(teacher.__proto__ === Teacher.prototype); //true
    

十七、Generator(生成器函数)

十八、Iterator(迭代器对象)

ES7

一、Array.prototype.includes()

  • ES7之前,如果我们想判断一个数组中是否包含某个元素,需要通过 indexOf 获取结果,并且判断是否为 -1;
  • ES7中可以通过 includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回 false
  • 语法:includes(searchElement) / includes(searchElement, fromIndex);
    • searchElement:需要查找的元素值;
    • fromIndex:从fromIndex 索引处开始查找 searchElement。如果为负值,则按升序从 array.length + fromIndex 的索引开始搜(从末尾开始往前跳 fromIndex 的绝对值个索引,然后往后搜寻)。默认为 0;
      const names = ['ace', 'sabo', NaN, 'luffy', 'long', 'yzh'];
      
      console.log(names.indexOf(NaN)); //-1
      console.log(names.includes(NaN)); //true
      
      //末尾开始往前跳 `fromIndex` 的绝对值个索引,然后往后搜寻
      console.log(names.includes(NaN, -3)); //false
      
      if (names.indexOf("yzh") !== -1) {
        console.log("包含abc元素")
      } //包含abc元素
      
      if (names.includes("yzh", 2)) {
        console.log("包含abc元素")
      } //包含abc元素
      
      if (names.indexOf(NaN) !== -1) {
        console.log('indexOf: ', "包含NaN")
      }
      
      if (names.includes(NaN)) {
        console.log('includes: ', "包含NaN")
      } //includes: 包含NaN
      

二、指数(乘方)的运算方法

  • ES7之前,计算数字的乘方需要通过 Math.pow 方法来完成;
    • Math.pow()  函数返回基数(base)的指数(exponent)次幂,即 base^exponent
  • ES7中,增加了 ** 运算符,可以对数字来计算乘方:
    const res1 = Math.pow(3, 3);
    console.log(res1); //27
    
    //es7
    const res2 = 3 ** 3;
    console.log(res2); //27
    
    console.log((2 ** 3) ** 2); //64
    

ES8

一、Object values

  • 之前我们可以通过 Object.keys 获取一个对象所有的key,在ES8中提供了 Object.values 来获取所有的value值;
  • Object.values()  方法返回一个给定对象自身的所有可枚举属性值的数组,值的顺序与使用for...in循环的顺序相同(区别在于 for-in 循环枚举原型链中的属性)
    const obj = {
      name: 'yzh',
      age: 18
    };
    
    console.log(Object.values(obj)); //[ 'yzh', 18 ]
    
    console.log(Object.values(['ace', 'sabo'])); //[ 'ace', 'sabo' ]
    console.log(Object.values('string')); //[ 's', 't', 'r', 'i', 'n', 'g' ]
    

二、Object entries

  • Object.entries() 方法返回一个给定对象自身可枚举属性的键值对数组;
    const obj = {
      name: 'yzh',
      age: 18
    };
    
    console.log(Object.entries(obj)); //[ [ 'name', 'yzh' ], [ 'age', 18 ] ]
    const objEnts = Object.entries(obj);
    // objEnts.forEach(item => {
    //   console.log(item[0], item[1]);
    // });
    objEnts.forEach(([key, value]) => {
      console.log(key, value);
    });
    
    console.log(Object.entries(['ace', 'sabo'])); //[ [ '0', 'ace' ], [ '1', 'sabo' ] ]
    console.log(Object.entries('abc')); //[ [ '0', 'a' ], [ '1', 'b' ], [ '2', 'c' ] ]
    

三、字符串填充(String padding)

  • padStart()  方法用另一个字符串填充当前字符串(如果需要的话,会重复多次),以便产生的字符串达到给定的长度。从当前字符串的左侧开始填充;
  • 语法:padStart(targetLength) / padStart(targetLength, padString)
    • targetLength当前字符串需要填充到的目标长度。如果这个数值小于当前字符串的长度,则返回当前字符串本身;
    • padString填充字符串。如果字符串太长,使填充后的字符串长度超过了目标长度,则只保留最左侧的部分,其他部分会被截断;
      'abc'.padStart(10);         // "       abc"
      'abc'.padStart(10, "foo");  // "foofoofabc"
      'abc'.padStart(6,"123465"); // "123abc"
      'abc'.padStart(8, "0");     // "00000abc"
      'abc'.padStart(1);          // "abc"
      
  • padEnd()  方法会用一个字符串填充当前字符串(如果需要的话则重复填充),返回填充后达到指定长度的字符串。从当前字符串的末尾(右侧)开始填充;
  • 语法:和padStart()一样;
    'abc'.padEnd(10);          // "abc       "
    'abc'.padEnd(10, "foo");   // "abcfoofoof"
    'abc'.padEnd(6, "123456"); // "abc123"
    'abc'.padEnd(1);           // "abc"
    
  • 应用场景:
    const message = 'Hello';
    const newMsg = message.padStart(9, 'yzh ').padEnd(15 ,' World');
    console.log(newMsg) //yzh Hello World;
    console.log(newMsg.length) //15;
    
    //身份证、银行卡的前面位数进行隐藏
    const cardNumber = '681815233646548897';
    const lastCardNumber = cardNumber.slice(-4);
    const finalCardNumber = lastCardNumber.padStart(cardNumber.length, '*');
    console.log(finalCardNumber); //**************8897
    
    time.png

四、尾随逗号(Trailing Commas)

  • 在ES8中,我们允许在函数定义和调用时多加一个逗号:
    function foo(a, b,) {
      console.log(a, b);
    };
    
    foo(1, 2,)
    

无聊的知识又增加了!

五、对象属性描述符(Object Descriptors)

  • ES8中增加了一个 Object.getOwnPropertyDescriptors() 方法用来获取一个对象的所有自身属性的描述符;
    const obj = {
      name: 'yzh',
      age: 18
    }
    
    console.log(Object.getOwnPropertyDescriptors(obj));
    /*
    {
      name: {
        value: 'yzh',
        writable: true,
        enumerable: true,
        configurable: true
      },
      age: { value: 18, writable: true, enumerable: true, configurable: true }
    }
    */
    

修改属性描述符

六、aysnc的function使用

ES9

一、迭代器(Async iterators)

二、对象展开运算符

三、promise finally

ES10

一、flat、flatMap

flat:

  • flat()  方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回;
  • 语法:flat() / flat(depth)
    • depth(可选)指定要提取嵌套数组的结构深度,默认值为 1;
  • 返回值:一个包含数组与子数组中所有元素的新数组;
    var arr1 = [1, 2, [3, 4]];
    console.log(arr1.flat());
    // [1, 2, 3, 4]
    
    var arr2 = [1, 2, [3, 4, [5, 6]]];
    console.log(arr2.flat());
    // [1, 2, 3, 4, [5, 6]]
    
    var arr3 = [1, 2, [3, 4, [5, 6]]];
    console.log(arr3.flat(2));
    // [1, 2, 3, 4, 5, 6]
    
    //使用 Infinity,可展开任意深度的嵌套数组
    var arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];
    console.log(arr4.flat(Infinity));
    // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    
    //flat()方法会移除数组中的空项
    var arr5 = [1, 2, , 4, 5];
    console.log(arr5.flat());
    // [1, 2, 4, 5]
    

flatMap:

  • flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。它与map()连着深度值为 1 的flat()几乎相同,但 flatMap 通常在合并成一种方法的效率稍微高一些;
    • 1)flatMap是先进行map操作,再做flat的操作;
    • 2)flatMap中的flat相当于深度为1;
  • 语法:flatMap(callbackFn) / flatMap(callbackFn, thisArg)
  • 参数:
    • callbackFn可以生成一个新数组的函数,可以传入三个参数:
      • currentValue当前正在数组中处理的元素;
      • index可选的。数组中正在处理的当前元素的索引;
      • array可选的。被调用的 map 数组;
    • thisArg可选。执行 callbackFn 函数时 使用的this 值;
      // 箭头函数
      flatMap((currentValue) => { } )
      flatMap((currentValue, index) => { } )
      flatMap((currentValue, index, array) => { } )
      
      // 回调函数
      flatMap(callbackFn)
      flatMap(callbackFn, thisArg)
      
      // 行内回调函数
      flatMap(function(currentValue) { })
      flatMap(function(currentValue, index) { })
      flatMap(function(currentValue, index, array){ })
      flatMap(function(currentValue, index, array) { }, thisArg)
      
  • flatMap的应用场景
    const message = ['Hello FlatMap', 'hello yzh', 'my name is yzh'];
    
    const mapMsg = message.map(item => item.split(' '));
    console.log(mapMsg);
    //[[ 'Hello', 'FlatMap' ], [ 'hello', 'yzh' ], [ 'my', 'name', 'is', 'yzh' ]]
    const mapFMsg = mapMsg.flat();
    console.log(mapFMsg);
    //['Hello', 'FlatMap', 'hello', 'yzh', 'my', 'name', 'is', 'yzh']
    
    // 对每一项split之后的数组自动做一个降维
    const words = message.flatMap(item => {
      return item.split(' ')
    });
    console.log(words); 
    //['Hello', 'FlatMap', 'hello', 'yzh', 'my', 'name', 'is', 'yzh']
    

二、Object.fromEntries

  • Object.fromEntries()  方法把键值对列表转换为一个对象;
  • 语法:Object.fromEntries(iterable);
  • 参数:iterable类似 ArrayMap或者其它实现了可迭代协议的可迭代对象
    const entries = new Map([
      ['foo', 'bar'],
      ['baz', 42]
    ]);
    
    const obj = Object.fromEntries(entries);
    
    console.log(obj);
    //{ foo: "bar", baz: 42 }
    
  • 场景1:在ES8可以通过 Object.entries 将一个对象转换成 entries 数组,现在有一个entries了,要把它转换成对象;
    const obj = {
      name: 'yzh',
      age: 18,
      height: 1.88
    };
    
    const objEnts = Object.entries(obj);
    console.log(objEnts);
    // [ [ 'name', 'yzh' ], [ 'age', 18 ], [ 'height', 1.88 ] ]
    
    • 1)循环
      const newObj = {};
      for (const [key, value] of objEnts) {
        newObj[key] = value;
      };
      console.log(newObj);
      //{ name: 'yzh', age: 18, height: 1.88 }
      
    • 2)API
      const newObj = Object.fromEntries(objEnts);
      console.log(newObj); //{ name: 'yzh', age: 18, height: 1.88 }
      
  • 场景2
    const queryString = 'name=yzh&age=18&height=1.88';
    const queryParams = new URLSearchParams(queryString);
    // console.log(queryParams); //{ 'name' => 'yzh', 'age' => '18', 'height' => '1.88' }
    
    for  (const param of queryParams) {
      // console.log(param);
      /**
       * [ 'name', 'yzh' ]
       * [ 'age', '18' ]
       * [ 'height', '1.88' ]
       */
    };
    
    const paramsObj = Object.fromEntries(queryParams);
    console.log(paramsObj); //{ name: 'yzh', age: '18', height: '1.88' }
    

三、trimStart和trimEnd

  • trimStart()  方法会删除字符串开头的空白字符。trimLeft() 是此方法的别名;
    const msg = '   Hello world!   ';
    
    console.log(msg);
    //'   Hello world!   ';
    
    console.log(msg.trimStart());
    //'Hello world!   ';
    

String.prototype.trimLeft.name === "trimStart";

  • trimEnd()  方法会删除字符串末尾的空白字符。trimRight() 是这个方法的别名;
    const msg = '   Hello world!   ';
    
    console.log(msg);
    //'   Hello world!   ';
    
    console.log(msg.trimEnd());
    //'   Hello world!';
    

String.prototype.trimRight.name === "trimEnd";

补充:

  • trim()  方法从字符串的两端清除空格,返回一个新的字符串,而不修改原始字符串。此空格是指所有的空白字符(空格、tab、不换行空格等);
    const msg = '   Hello world!   ';
    
    console.log(msg);
    //'   Hello world!   ';
    
    console.log(msg.trim());
    //'Hello world!';
    

四、Symbol description

五、try...catch

ES11

一、BigInt

  • ES11之前 MAX_SAFE_INTEGER,大于这个数就不一定是安全的、准确的;
    const maxInt = Number.MAX_SAFE_INTEGER;
    console.log(maxInt); // 9007199254740991 大于这个数表示的可能是不正确的;
    console.log(maxInt + 1); //9007199254740992
    console.log(maxInt + 2); //9007199254740992
    
  • ES11中,引入了新的数据类型BigInt,用于表示大的整数;
    • BitInt的表示方法是在数值的后面加上n
      const bigInt = 900719925474099100n;
      console.log(bigInt + 10n); //900719925474099110n
      
      const num = 100;
      console.log(bigInt + BigInt(num)); //900719925474099200n
      
      const smallNum = Number(bigInt);
      console.log(smallNum); //900719925474099100
      

二、空值合并运算符

  • 逻辑运算符,当左侧的操作数为null或者undefined时,返回其右侧操作数,否则返回左侧操作数
    // ?? 只有值为undefined或null才赋值运算符后面
    
    // const kname = 0;
    // const kname = '';
    // const kname = null;
    const kname = undefined;
    
    const newName = kname ?? 'default value';
    console.log(newName); //default value
    
    
    // 逻辑或缺陷 0或者空字符串
    // const age = 0;
    const age = '';
    const newAge = age || 'd v';
    // console.log(newAge); //d v
    console.log(newAge); //d v
    

三、Optional Chaining(可选链)

  • 可选链运算符( ?. )允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效;
  • 在引用为空 (null或者undefined) 的情况下不会引起错误,该表达式短路返回值是 undefined。与函数调用一起使用时,如果给定的函数不存在,则返回 undefined
    const data = {
      name: 'yzh',
      cat: {
        name: '年年'
      }
    };
    
    const dogName = data.dog?.name;
    console.log(dogName) //undefined
    
    console.log(data.foo?.()) //undefined
    
  • 场景:
    const info = {
      name: "yzh",
      friend: {
        girlFriend: {
          name: "hzy"
        }
      }
    }
    
    //1.
    //console.log(info.friend.girlFriend.name)
    if (info && info.friend && info.friend.girlFriend) {
      console.log(info.friend.girlFriend.name)
    }
    
    //2.
    console.log(info.friend?.girlFriend?.name)
    

让我们的代码在进行null和undefined判断时更加清晰和简洁;

四、Global This

  • 在之前我们希望获取JavaScript环境的全局对象,不同的环境获取的方式是不一样的
    • 在浏览器中可以通过this、window来获取;
    • 在Node中我们需要通过global来获取;
  • 在ES11中对获取全局对象进行了统一的规范:globalThis
    //浏览器上
    console.log(this);
    console.log(window);
    
    //Node中
    console.log(global);
    
    console.log(globalThis);
    

五、for..in标准化

  • 在ES11之前,虽然很多浏览器支持for...in来遍历对象类型,但是并没有被ECMA标准化;
  • 在ES11中,对其进行了标准化,for...in是用于遍历对象的key的:
    const info = {
      name: 'yzh',
      age: 18,
      height: 1.8
    };
    
    for (const key in info) {
      console.log(key);
      //name age height
    }
    

六、Promise.allSettled

七、Dynamic Import(动态引入)

  • 关键字 import 可以像调用函数一样来动态的导入模块;
    // index.js
    // 默认先加载foo.js模块 拿变量,在执行后面代码;
    // import { name, age, foo } from './foo.js'
    // console.log(name)
    
    // import函数返回的结果是一个Promise
    // 不阻塞后面代码运行;
    import("./foo.js").then(res => {
      console.log("res:", res.name)
    })
    
    // foo.js
    const name = "yzh"
    const age = 18
    
    const foo = "foo value"
    
    export {
      name,
      age,
      foo
    }
    

八、import meta

  • import.meta是一个给 JavaScript 模块暴露特定上下文的元数据属性的对象;
    • 它包含了这个模块的信息,比如说这个模块的 URL;
      // index.html
      <!DOCTYPE html>
      <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
      </head>
      <body>
        <script src="./main.js" type="module"></script>
      </body>
      </html>
      
      
      // main.js
      // meta属性本身也是一个对象: { url: "当前模块所在的路径" }
      console.log(import.meta)
      

ES12

一、FinalizationRegistry

  • FinalizationRegistry 对象可以让你在对象被垃圾回收时请求一个回调:
    • FinalizationRegistry 提供了这样的一种方法:当一个在注册表中注册的对象被回收时,请求在某个时间点上调用一个清理回调;
    • 你可以通过调用register方法,注册任何你想要清理回调的对象,传入该对象和所含的值;
      /**
       * FinalizationRegistry类
       * 监听对象的销毁
       */
      
      const finalizaReg = new FinalizationRegistry(value => {
        console.log('注册在finalizaReg的对象,被销毁了!', value);
      });
      
      let obj = { name: '1zh' };
      let info = { name: 'ace' };
      
      finalizaReg.register(obj, 'obj');
      finalizaReg.register(info, 'info');
      
      obj = null;
      info = null;
      
      finalizaReg.png

二、WeakRefs

  • 如果我们默认将一个对象赋值给另外一个引用,那么这个引用是一个强引用:
    • 如果我们希望是一个弱引用的话,可以使用WeakRef;
      // ES12: WeakRef类;
      // WeakRef.prototype.deref: 
      // > 如果原对象没有销毁, 那么可以获取到原对象;
      // > 如果原对象已经销毁, 那么获取到的是undefined;
      const finalRegistry = new FinalizationRegistry((value) => {
        console.log("注册在finalRegistry的对象, 某一个被销毁", value);
      });
      
      let obj = { name: "1zh" };
      //let info = obj; //强引用,obj不会被垃圾回收机制回收
      let info = new WeakRef(obj); //弱引用
      
      finalRegistry.register(obj, "obj");
      
      obj = null;
      
      setTimeout(() => {
        //console.log(info..name); //强引用 1zh
        console.log(info.deref()?.name); //弱引用 undefined
      }, 10000); //确保走了finalRegistry,再获取info的信息
      

三、逻辑赋值运算符

  • 逻辑或赋值;
    let message = '';
    
    message ||= 'default value';
    // 等同于
    // message = message || 'defaut value';
    
    console.log(message); //defalut value
    
  • 逻辑与赋值;
    let obj = { name: '1zh' };
    
    obj &&= obj.name;
    // 等同于
    // obj = obj && obj.name;
    
    console.log(obj); //1zh
    
  • 逻辑空赋值;
    let msg = undefined;
    
    msg ??= 'd v';
    // 等同于
    // msg = msg ?? 'd v';
    
    console.log(msg); //d v
    

四、Numeric Separator(数字分隔符)

  • _ 不会改变数字的真实值,只是为了方便代码阅读;
    let num = 1_000_000;
    console.log(num); //1000000
    

注意事项:

1)不能连着两个下划线(如100__000)

2)不能出现在数字末尾(如100_)

3)不能出现在以0开头的数字0后面(如0_1)

五、Promise.any

六、String.prototype.replaceAll()

  • replaceAll(pattern, replacement) 方法返回一个新字符串,新字符串所有满足 pattern 的部分都已被replacement 替换;
    • pattern可以是一个字符串或一个RegExp
    • replacement可以是一个字符串或一个在每次匹配被调用的函数;
      let msg = '这是一段很长很长的描述x所讲的内容是....';
      
      //这是一段很长很长的描述,所讲的内容是....
      console.log(msg.replaceAll('x', ','));
      
      let p = 'aaabbbccc';
      const regex = /b/g;
      console.log(p.replaceAll(regex, '.')); //aaa...ccc
      

当使用一个 regex 时,必须设置全局(“g”)标志, 否则报错 TypeError:“使用非全局RegExp参数调用了replaceAll”;

错误示例如:const regex = /b/; p.replaceAll(regex, '.');