最全ES6-ES12新特性总结,再也不怕面试官问了

目录
文章目录隐藏
  1. ES6
  2. 24. Generator
  3. ES7
  4. ES8
  5. ES9
  6. ES10
  7. ES11
  8. ES12
  9. 结语

最全 ES6-ES12 新特性总结

接下来为大家分享最新整理的最全 ES6-ES12 新特性总结,再也不怕面试官问了。内容有点多,建议先马后看。

ES6

1. 新的声明方式:let

变量

  • 不属于顶层对象 window
  • 不允许重复声明
  • 不存在变量提升
  • 暂时性死区
  • 块级作用域

1.1 不属于顶层对象 window

//声明的是变量,具有作用域
var a = 5
console.log(windeow.a) //可以输出
//没有 var,是一个对象
b = 6
console.log(windeow.b) //可以输出

let的出现是为了弥补var将变量挂在window上的缺陷

static 文件夹下的文件是原封不动地上传到浏览器

而 src 文件夹下的文件会经过 webpack 打包,会规避一些问题

1.2 不允许重复声明

var可以多次重复声明(最后一次声明会覆盖前面的声明),而let不能(会报错)

可以避免重复命名

1.3 不存在变量提升

console.log(a)
var a = 5

//相当于
var a
console.log(a)
a = 5

而 let 不存在变量提升

1.4 暂时性死区

var a = 5
if(true){
    a = 6
    let a
}
//会报错,a 没有进行声明,在 if{}里是一个暂时性死区

1.5 块级作用域

for(var i=0;i<3;i++){
    console.log(i)  //输出 0 1 2
}
console.log(i)  //只会输出 3,因为 var 不存在块级作用域,i=3 时不满足条件则结束循环,跳出循环之后被外部的 console.log 输出
//将 var 改成 let,则外部的 console 报错

块级作用域使得代码更加安全

  • 允许在块级作用域内声明函数
  • 函数声明类似于var,即会提升到全局作用域或函数作用域的头部
  • 同时,函数声明还会提升到所在的块级作用域的头部

参考:这篇文章

2. 新的声明方式:const

常量,不能被重新赋值

const a
a = 5 //报错,应 const a = 5

对于引用类型,const不能改变其引用地址,但是可以改变堆内存中的值

const obj = {
    name:'yl',
    age:11
}
//这里添加一行 Object.freeze(obj),后面的就无法改变(但只能冻结第一层,如果多层嵌套需要 obj.name)
obj.sex = 'G'  //obj 中会添加这一值(堆内存可以改变,栈不能改变)
  • 不属于顶层对象 window
  • 不允许重复声明
  • 不存在变量提升
  • 暂时性死区
  • 块级作用域
var let const
函数级作用域 块级作用域 块级作用域
变量提升 不存在变量提升 不存在变量提升
值可更改 值可更改 值不可更改

let VS const

默认情况下优先使用 const,如果需要被改变再考虑 let

let 变量 const 常量。

3. ==解构赋值(常用)==

  • 按照一定模式,从数组和对象中提取值,对变量进行赋值
  • 数组解构
  • 对象解构
  • 字符串解构
  • 应用

默认参数的使用(当没有传这个值的时候,默认赋该值)

等号左右两边的结构一样即可

const [a,b,c,d = 5] = [1,2,[3,4]] //输出[1,2,[3,4],5]
const [a,b,c,d = 5] = [1,2,[3,4],6] //输出[1,2,[3,4],6]
//即如果右边有值则为右边的值,否则输出左边赋的默认值;如果右边没有值,左边也没有默认值,则 underfined
  • 数组通过索引进行配对(按顺序解构)
  • 对象通过键名进行配对(变量必须和属性同名)
  • 解构也适用于嵌套结构的对象(要使用一样的结构)
let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"

let { baz } = { foo: 'aaa', bar: 'bbb' };
baz // undefined //不同名,取不到

嵌套赋值:

let obj = {};
let arr = [];

({ foo: obj.prop, bar: arr[0] } = { foo: 123, bar: true });

obj // {prop:123}
arr // [true]
  • 字符串的解构和数组相似
function foo([a,b,c]) {
    console.log(a,b,c)
}
let arr = [1,2,3]
foo(arr)

对于 json:

let json = '{"a":"hello","b":"world"}'
let {a,b} = JSON.parse(json)  //将 json 格式输出成对象,再进行解构赋值

使用了别名之后,真正被赋值的是后者:

let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
foo // error: foo is not defined

注意:

// 错误的写法
let x;
{x} = {x: 1};
// 正确的写法
let x;
({x} = {x: 1});  //将解构赋值语句放在一个原括号里
  • 数值和布尔值的解构赋值:
    会先转换为对象

    解构赋值的规则:

    只要等号右边的值不是对象或数组,就先转换为对象

    undefined 和 null 无法转为对象,故无法进行解构赋值

  • 函数的参数也可以使用解构赋值
    用途:

    1. 交换变量的值
      let x = 1;
      let y = 2;
      
      [x, y] = [y, x];
      
    2. 从函数返回多个值
      // 返回一个数组
      function example() {
        return [1, 2, 3];
      }
      let [a, b, c] = example();
      
      // 返回一个对象
      function example() {
        return {
          foo: 1,
          bar: 2
        };
      }
      let { foo, bar } = example();
      
    3. 函数参数的定义
      // 参数是一组有次序的值
      function f([x, y, z]) { ... }
      f([1, 2, 3]);
      
      // 参数是一组无次序的值
      function f({x, y, z}) { ... }
      f({z: 3, y: 2, x: 1});
      
    4. 提取 JSON 数据
    5. 函数参数的默认值
    6. 遍历 Map 结构
    7. 输入模块的指定方法
      const { SourceMapConsumer, SourceNode } = require("source-map");
      

4. 数组的各种遍历方式

ES5 中的数组遍历方式

  • for循环
  • forEach():没有返回值,只是针对每个元素调用 func
  • map():返回新的 Array,每个元素为调用 func 的结果
  • filter():返回符合 func 条件的元素数组
  • some():返回布尔,判断是否有元素符合 func 条件
  • every():返回布尔,判断每个元素是否符合 func 条件
  • reduce():接收一个函数作为累加器
  • for in ???
arr = [1,2,3]

//for 
for(let i = 0; i<arr.length;i++) {
    //....
}

//forEach(不支持 break continue)
arr.forEach(function(elem,index,array){
    //.....
})

//map
let result = arr.map(function(value){
    value += 1
    return value
})
console.log(arr,result)  //map 循环之后会生成新的数组,不会去更改之前的 arr

//filter(过滤)
let result = arr.filter(function(value){
    return value == 2
})
console.log(arr,result)  //会生成一个新的数组,这个新的数组只会保存满足条件的值

//some
let result = arr.some(function(value){
    return value == 4
})
console.log(arr,result)  //返回的是一个布尔值,因为 arr 中没有 4,所以返回 false(只要找到一个满足条件的值就会返回 true)

//every
let result = arr.every(function(value){
    return value == 2
})
console.log(arr,result)  //所有元素都满足条件时才会返回 true

//reduce
//0 初始值 prev 上一个处理的元素 cur 当前处理的元素 index 当前处理元素的索引 array 原数组
let sum = arr.reduce(function(prev,cur,index,array){
    return prev + cur
},0) //得到的就是求和的结果
//reduce 可以实现求 max min 去重等
//去重
let res = arr.reduce(function(prev,cur){
    prev.indexOf(cur) == -1 && prev.push(cur)
    return prev
},[])

//for in xx
//这种方法遍历数组会将 arr 上的所有东西遍历出来(包括原型上的方法)
for(let index in arr){
    //....
}

ES6 中数组遍历方法

  • find():返回第一个通过测试的元素
  • findIndex():返回的值为该通过第一个元素的索引
  • for of
  • values()
  • keys()
  • entries()
arr = [1,2,3,2,4]

//find
let res = arr.find(function(value){
    return value == 2
})
console.log(arr,res) //res 返回的 2 为 arr 的第一个 2

//findIndex
let res = arr.findIndex(function(value){
    return value == 2
})
console.log(arr,res) //res 返回的是为 arr 的第一个 2 的索引

//for of
for(let item of arr){
    console.log(item)
}
//for(let item of arr.values()){} 和上面的效果一样
//arr.values() 为内容
//arr.keys() 为索引
//arr.entries() 为两者都输出
for(let [index,item] of arr.entries()){
    console.log(index,item)
}

相关文章推荐:JavaScript 中有哪些循环遍历方法,你知道吗?

5. 数组的扩展

  • 类数组/伪数组有长度,但不能使用数组的方法
  • Array.from()
  • Array.of()
  • copyWithin()
  • fill()
  • includes()

es5 中,可以通过slice方法将伪数组转换成数组

let arr = Array.prototype.slice.call(divs3)
arr.push(123) //此时已经转换成了真正的数组,使用数组方法不会报错

es6 中:

//Array.from() 将其转换为数组
Array.from(arrayLike)

//Array.of()
let arr = Array.of(1,2)
let arr = Array.of(3)
//let arr = new Array(3) 这个返回的是 3 个空白,并不是数组[3]。这种方法会随着传入的参数个数不同而得到不同的数组

//copyWithin()替换元素
let arr = [1,2,3,4,5]
console.log(arr.copyWithin(1,3))  //从第一个位置开始读取,再读取下标为 3 的数组,(因为没有第三个参数,所有默认到结尾),于是就用 4,5 来替换 2,3

//fill()填充
//1.
let arr = new Array(3).fill(7) //数组长度为 3,用 7 进行填充,于是得到[7,7,7]
//2.
let arr = [1,2,3,4,5]
arr.fill('yl',1,3) //从下标为 1 开始替换,直到下标为 3(不包括) 得到[1,'yl','yl',4,5]
arr.fill(0)  //全部被替换成 0

//includes()是否包含

NAN == NAN 不相等。

6. 函数的参数

  • 参数的默认值
  • 与解构赋值结合
  • length 属性
  • 作用域
  • 函数的 name 属性

6.1 参数的默认值

//es5
function foo(x,y){
    y = y || 'world'  //判断参数是否存在,但存在问题
    console.log(x,y)
}
foo('hello',0) //如果不传 y 值,则打印'world';而 0 由于是 false,所以打印出来的是 world

//es6
function foo(x, y = 'world'){
    console.log(x,y)
}
foo('hello',0)  //此时打印出来的是 hello,0

//函数内部的参数已经默认声明过了,使用 const 或 let 再次声明会报错
//函数内部的参数不能重名 eg.foo(x,x,y)报错
function foo(x = 5){
    //这里不能再声明 x
}
foo()

//参数的默认值放最后面
function foo(x,z,y=5){}

6.2 与解构赋值结合

function foo({x,y = 5}){
    console.log(x,y)
}
foo({})  //打印出 underfined 5(x 没有赋值),这里符合解构赋值
//foo() 报错,结构要一样才可以

与默认值一同使用:

function ajax(url,{
    body = '',
    method = 'GET',
    headers = {}
} = {}){  //如果不传入第二个参数,则默认值为空值
    console.log(method)
}
ajax('https://mybj123.com',{
    method: 'POST'
})  //POST

6.3 length 属性

返回没有指定默认值的个数

6.4 作用域

let x = 1
function foo(x,y=x){   //()中形成了一个作用域,故 y 取到的值为这个作用域里面的 x 值
    console.log(y)  //2
}
foo(2)
let x = 1
function foo(y=x){ 
    let x = 2
    console.log(y)  //1
}
foo()  
//没有传入参数,此时 y 会沿着作用域链**往外**找,找到全局变量中有一个 x 的值,然后赋值得到
//如果没有声明全局变量,则返回的是 underfined

6.5 函数的 name 属性

(new Function).name //输出 anonymous

7. 拓展运算符 与 rest 参数

  • ...
  • 扩展运算符:把数组或者类数组展开成用逗号隔开的值
  • rest 参数:把逗号隔开的值组合成一个数组

互逆操作

如果…放在等号左边或是形参上,则 rest 参数

如果…放在等号右边或是实参上,则扩展运算符

function foo(a,b,c){
    console.log(a,b,c)
}
let arr = [1,2,3]
foo(...arr)
// 如果使用 foo(arr)需要使用解构赋值,而使用拓展运算符则会将 arr 变成 1,2,3
// 合并数组
let arr1 = [1,2,3]
let arr2 = [4,5,6]

// es5
Array.prototype.push.apply(arr2,arr2)  //在原型上进行 push apply

// es6
arr1.push(...arr2)  //...可以打散 arr2,再通过 push 加上去
// 打散字符串
let str = 'hello'
var arr = [...str]  //得到["h","e","l","l","o"]

作用域:

//es5
function foo(x,y,z) {
    let sum = 0
    Array.prototype.forEach.call(arguments,function(item){  //arguments 返回的是伪数组
        sum += item
    })
    return sum
}
console.log(foo(1,2))  //3
console.log(foo(1,2,3))  //6

//使用 es6 中 Array.from 转换数组
//Array.from(arguments).forEach(function(item){})

//使用 reset 参数(对于不确定参数) 参数要放在最后
function foo(...args) {
    console.log(args)
    let sum = 0
    args.forEach(function(item){
        sum += item
    })
    return sum
}

//reset 提取剩余的参数
function foo(x,...args) {
    console.log(x)  //1
    console.log(args)  //[2,3,4,5]
}
foo(1,2,3,4,5)
//同样适用于解构赋值中
let [x,...y] = [1,2,3]
console.log(x)  //1
console.log(y)  //[2,3]

8. 箭头函数

  • this 指向定义时所在的对象,而不是调用时所在的对象箭头函数里没有 this,会往外一层去找 this
  • 不可以当作构造函数
  • 不可以使用 arguments 对象
// 箭头函数的写法:箭头左边是参数,右边是方法体

let sum = (x,y) => {
    return x + y
}

// 可以简写成 let sum = (x,y) => x + y (方法体只有一行代码)
//es5 中构造函数
function People(name,age){
    console.log(this)
    this.name = name
    this.age = age
}
let p1 = new People('yl',11)
let foo = (..args) => {
    //console.log(arguments) 浏览器会报错
    //可以使用 reset 参数进行输出
    console.log(args)
}
foo(1,2,3)

9. 对象的扩展

  • 属性简洁表示法
  • 属性名表达式
  • Object.is()===Object.is() 即===
  • 拓展运算符 与 Object.assign()
  • in
  • 对象的遍历方式

9.1 属性简洁表示法 属性名表达式

let name = 'yl'
let age = 11
let s = 'school'
let obj = {
    name,
    age,
    [s]:'gdut'  // 如果想要使用变量,则加上[]
    study(){  // es6 为对象提供了一种简写的方式,如果使用箭头函数会报错,this 指代的是 window
        console.log(this.name + 'studying')
    }
}

9.2 Object.is()

obj1 == obj2 //false

obj 存储的是一个引用地址,每一个 obj 都会进行一次new Object(),在堆内存中进行存储,所以哪怕两个对象内容一模一样,在堆内存中的位置也是不一样的,故返回 false。

同样 Object.is(obj1 == obj2) //false

let obj1 = obj2

Object.is(obj1 == obj2) //true

9.3 拓展运算符 与 Object.assign()

let x = {
    a: 3 
    b: 4
}
let y = {..x}
console.log(y)  //{a:3,b:4}

//Object.assign()
let x = {
    a: 3,  //后面的值会覆盖前面的,所以 a:3
    b: 4
}
let y = {
    c:5,
    a:6 
}
Object.assign(y,x)
console.log(y)  //{a:3,b:4,c:5}

9.4 in

判断对象中是否存在

如果是数组:

console.log(3 in arr) //下标为 3 是否存在

9.5 对象的遍历方式

//1
for (let key in obj){
    console.log(key,obj[key])
}

//2
Object.keys(obj).forEach(key => {
    console.log(key,obj[key])
})

//3
Object.getOwnPropertyNames(obj).forEach(key =>{
    console.log(key,obj[key])
})

//4
Reflect.ownKeys(obj).forEach(key => {
    console.log(key,obj[key])
})

10. 深拷贝与浅拷贝

相关文章推荐:理解 js 的深拷贝和浅拷贝原理和实现的方法

10.1 浅拷贝

let Foo = {
    a: 3,
    b: 4
}
let newFoo = Foo
newFoo.a = 5
//使用 object.assign()
let Foo = {
    a: 3,
    b: 4
}
// let newFoo = Foo
Object.assign(newFoo, Foo)
newFoo.a = 5

改变内容,都会改变(因为改变的是引用地址)

10.2 深拷贝

  • JSON 方式JSON.parse() 将 JSON 字符串转换成 JavaScript 对象JSON.stringify() 将 JavaScript 对象转换成 JSON 字符串
    let Foo = {
        a: {
          c:1
        },
        b: 4
    }
    let str = JSON.stringify(Foo)
    let newFoo = JSON.parse(str)
    newFoo.a.c = 5
    
  • 递归
    let checkType = data => {
        return Object.prototype.toString.call(data).slice(8, -1)
    }
    
    let deepClone = target => {
        let targetType = checkType(target)
        let result
        // 初始化操作
        if (targetType === 'Object') {
            result = {}
        } else if (targetType === 'Array') {
            result = []
        } else {
            // 都不是的话证明是基本数据类型,基本数据
            // 类型只会有一个值,所以直接返回这个值就可以了
            return target
        }
        // target 不是基本类型,进入遍历
        for (let i in target) {
            let value = target[i]
            let valueType = checkType(value)
            if (valueType === 'Object' || valueType === 'Array') {
                result[i] = deepClone(value) // 递归
            } else {
                // 是基本类型直接赋值
                result[i] = value
            }
        }
        return result
    }
    

11. 面向过程与面向对象

面向过程:强调实现需求的步骤

面向对象:对象的属性、方法

面向过程与面向对象

JavaScript 是一种基于对象的语言

类是对象的模板,定义了同一组对象共有的属性和方法。

12. ES5 中的类与继承

组合式继承:

function Animal(name) {
    this.name = name;
}
Animal.prototype.showName = function () {
    console.log('名字为' + this.name);
}

//子类
function Dog(name,color) {
    Animal.call(this,name);  //继承父类的属性,**但不继承父类的方法**
    this.color = color;
}
Dog.prototype = new Animal();  //组合继承,既能继承属性又能继承方法
Dog.prototype.constuctor = Dog;
lett d = new Dog('wangcai','white');
console.log(d1);

13. ES6 中的类与继承

13.1 class 是语法糖

class People {
    constructor(name,age) {
        this.name = name;
        this.age = age;
    }
    showName(){
        console.log(this.name);
    }
}
let p1 = new People('yl',11);
console.log(p1);

10.2 继承 extends

class Coder extends People {
    constructor(name,age,company){
        super(name,age);
        this.company = company;
    }
    showCompany(){
        console.log(this.company);
    }
}

10.3 Setters&Getters

class Animal {
    constructor(type, age) {
        this.type = type;
        this._age = age;
    }
    get age() {  //只读
        return this._age;
    }
    set age(val) {  //可写
        this._age = val;
    }
}

使用这种方式可以在里面写语句

eg.

set age(val) { if (val > 0 && val < 10) { #age = val } }

13.4 静态方法

使用static来标记:

class Animal {
    constructor(type) {
        this.type = type
    }
    walk() {
        console.log( `I am walking` )
    }
    static eat() {
        console.log( `I am eating` )
    }
}
  1. 类中的构造器不是必须写的,要写实例进行一些初始化的操作,如添加指定属性时才写
  2. 如果 A 类继承了 B 类,且 A 类中写了构造器,那么 A 类构造器中的 super 是必须要调用的
  3. 类中所定义的方法,都是放在了类的原型对象上,供实例去使用

1、

//传统方法
function Point(x, y) {
  this.x = x;
  this.y = y;
}
Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};
var p = new Point(1, 2);

//class 方法
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  toString() {    //方法必须使用该语法,方法名(){}
    return '(' + this.x + ', ' + this.y + ')';
  }
}

2. Object.assign()一次向类添加多个方法

class Point {
  constructor(){
    // ...
  }
}

Object.assign(Point.prototype, {
  toString(){},
  toValue(){}
});

3. 类必须使用new调用

es5 里,实例的属性是函数原型的属性

在 es6 中,static 声明静态属性,属性属于类不属于实例

function Phone(){
}
Phone.name = '手机';   //name 属性属于函数对象的,不属于实例对象,称为静态属性
Phone.prototype.size = '5.5inch';  //原型
let nokia = new Phone();   //实例化
console.log(nokia.name);   //报错
console.log(nokia.size);   //输出 5.5inch
//构造方法
constructor(brand, price){
    this.brand = brand;
    this.price = price;
}
//父类的成员属性
call(){
    console.log("我可以打电话!!");
}
}
class SmartPhone extends Phone {   //用 extends 来继承
//构造方法
constructor(brand, price, color, size){
    super(brand, price);// Phone.call(this, brand, price)  关键字 super
    this.color = color;
    this.size = size;
}
photo(){
    console.log("拍照");
}
playGame(){
    console.log("玩游戏");
}
//call(){
//    console.log('我可以进行视频通话');   //子类对父类方法的重写
}
}
const xiaomi = new SmartPhone('小米',799,'黑色','4.7inch');

5. 取值 get 存值 set

14. 新的原始数据类型 Symbol

let s = new Symbol() 错误,不能使用 new

Symbol 不是对象,不能添加属性(是一种类似于字符串的数据类型)

14.1 独一无二

这个可以保证相同 key 值的也保存下来(比如重名学生):

let s1 = Symbol();
console.log(s1);  //Symbol()
let s2 = Symbol();
console.log(s1 === s2);  //false

14.2 自动调用 toString()函数

const obj = {
    name: 'yl',
    toString(){
        return this.name
    }
}
let s = Symbol(obj);
console.log(s);  //Symbol(yl)

14.3 Symbol.for()

在全局中注册的

不会每次调用都返回一个新的 Symbol 类型的值,而是先检查给定的key是否已经存在,不存在才新建。

let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
console.log(s1 === s2);//true

14.4 Symbol.keyFor()

返回一个已经登记的Symbol类型值的key

const s1 = Symbol('foo')
console.log(Symbol.keyFor(s1)) // undefined

const s2 = Symbol.for('foo')
console.log(Symbol.keyFor(s2)) // foo

14.5 属性遍历

const sym = Symbol('imooc')
class User {
    constructor(name) {
        this.name = name
        this[sym] = 'imooc.com'
    }
    getName() {
        return this.name + this[sym]
    }
}
const user = new User('xiecheng')
console.log(user.getName())

for (let key in user) {   //不能遍历 symbol 类型的值
    console.log(key)
}

for (let key of Object.keys(user)) {  //不能遍历 symbol 类型的值
    console.log(key)
}

for (let key of Object.getOwnPropertySymbols(user)) {  //只能遍历 symbol 类型的值
    console.log(key)
}

for (let key of Reflect.ownKeys(user)) {  //全都能遍历
    console.log(key)
}

可以很好地保护symbol

14.6 消除魔术字符串

function getArea(shape) {
    let area = 0
    switch (shape) {
        case 'Triangle'://魔术字符串
            area = 1
            break
        case 'Circle':
            area = 2
            break
    }
    return area
}
console.log(getArea('Triangle'))
const shapeType = {
    triangle: Symbol(),//使用 symbol 赋一个独一无二的值
    circle: Symbol()
}

function getArea(shape) {
    let area = 0
    switch (shape) {
        case shapeType.triangle:
            area = 1
            break
        case shapeType.circle:
            area = 2
            break
    }
    return area
}
console.log(getArea(shapeType.triangle))

15. 新的数据结构 Set

数据结构 Set 类似于数组,但是成员的值都是唯一的,没有重复的值

15.1 基本语法

生成 Set 实例:

let s = new Set()

let s = new Set([1, 2, 3, 4])

添加数据:

s.add('hello')
s.add('goodbye')

或者:
s.add('hello').add('goodbye')  //写在一起

添加重复的数据是无效的

删除数据:

s.delete('hello')  // 删除指定数据
s.clear()  // 删除全部数据

统计数据:

// 判断是否包含数据项,返回 true 或 false
s.has('hello') // true
// 计算数据项总数
s.size // 2

数组去重:

let arr = [1, 2, 3, 4, 2, 3]
let s = new Set(arr)

合并去重:

let arr1 = [1, 2, 3, 4]
let arr2 = [2, 3, 4, 5, 6]
let s = new Set([...arr1, ...arr2])
console.log(s)
console.log([...s])
console.log(Array.from(s))

交集:

let s1 = new Set(arr1)
let s2 = new Set(arr2)
let result = new Set(arr1.filter(item => s2.has(item)))
console.log(Array.from(result))

差集:

let arr3 = new Set(arr1.filter(item => !s2.has(item)))
let arr4 = new Set(arr2.filter(item => !s1.has(item)))
console.log(arr3)
console.log(arr4)
console.log([...arr3, ...arr4])

15.2 遍历方式

console.log(s.keys()) // SetIterator {"hello", "goodbye"}
console.log(s.values()) // SetIterator {"hello", "goodbye"}
console.log(s.entries()) // SetIterator {"hello" => "hello", "goodbye" => "goodbye"}
s.forEach(item => {
    console.log(item) // hello // goodbye
})

for (let item of s) {
    console.log(item)
}
for (let item of s.keys()) {
    console.log(item)
}

for (let item of s.values()) {
    console.log(item)
}

for (let item of s.entries()) {
    console.log(item[0], item[1])  //key 值和 value 值都是一样的
}

15.3 WeakSet

区别:

  • 成员只能是对象,而不能是其他类型的值。
  • 没有 size 属性,不能遍历。
  • 弱引用

所谓垃圾回收机制:

如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在

const ws = new WeakSet()
ws.add(1)
// TypeError: Invalid value used in weak set
ws.add(Symbol())
// TypeError: invalid value used in weak set
let ws = new WeakSet()
const obj1 = {
    name: 'imooc'
}
const obj2 = {
    age: 5
}
console.log(ws)
console.log(ws.has(obj2))

16. 新的数据类型 Map

类似于对象,键值对的集合

“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键

也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应

是一种更完善的 Hash 结构实现

如果你需要“键值对”的数据结构,MapObject 更合适。

16.1 基本语法

实例化:

let map = new Map();

let map = new Map([
    ['name','yl'],
    ['age',5]
])
console.log(map);  //Map(2) {"name" => 'yl',"age" => 5}

添加数据:

let obj = {
    name: 'yl'
}
map.set(obj,'66');

删除数据:

map.delete(keyObj);    // 删除指定的数据
map.clear();           // 删除所有数据

统计数据:

console.log(map.size) //2
console.log(map.has(keyObj)) //判断是否有 key-value

查询数据:

console.log(map.get(keyObj)) // 和键'keyObj'关联的值

16.2 遍历方式

map.forEach((value, key) => console.log(value, key))  //value, key

for (let [key, value] of map) {  //key, value
    console.log(key, value)
}

for (let key of map.keys()) {
    console.log(key)
}

for (let value of map.values()) {
    console.log(value)
}

for (let [key, value] of map.entries()) {
    console.log(key, value)
}

Map VS Object:

  • 键的类型Object的键: 字符串或者 SymbolsMap 的键: 任意值
  • 键的顺序Object的键:无序Map的键值:有序

    进行遍历时,Map 对象是按插入的顺序返回键值。

  • 键值对的统计Object 的个数:只能手算Map的个数:用size
  • 键值对的遍历Object:先获取键数组,再进行迭代Map:可直接进行迭代
  • 性能在涉及频繁增删键值对的场景下,Map 会有些性能优势

16.3 WeekMap

// WeakMap 可以使用 set 方法添加成员
const wm1 = new WeakMap()
const key = {
    foo: 1
}
wm1.set(key, 2)
wm1.get(key) // 2

// WeakMap 也可以接受一个数组
// 作为构造函数的参数
const k1 = [1, 2, 3]
const k2 = [4, 5, 6]
const wm2 = new WeakMap([
    [k1, 'foo'],
    [k2, 'bar']
])
wm2.get(k2) // "bar"

区别:

  • WeakMap 只接受对象作为键名(null 除外),不接受其他类型的值作为键名
  • 不计入垃圾回收机制

17. 字符串的扩展

17.1 Unicode 表示法(少用)

Unicode 有啥用:

保证简便高效和保持与已有编码标准兼容之间的平衡。

在内部使用 Unicode 的应用程序,能够同时存储和处理世界上所有的字符,这消除了传统的国际化方法所面临的一些困难。

es5 中

"\u0061"    // "a"

只限于码点在\u0000~\uFFFF 之间的字符

超出须用两个双字节的形式表示

"\uD842\uDFB7"   // ""

es6 中

将码点放入大括号:

"\u{20BB7}"   // ""
'\z' === 'z' // true
'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true
'\u{7A}' === 'z' // true

17.2 遍历器接口

for (let item of 'imooc') {
    console.log(item)
}

17.3 ==模板字符串==

  • 多行字符串使用后,不需要使用/n 换行
  • 插入表达式
    var a = 5;
    var b = 10;
    console.log(`Fifteen is ${a + b} and
    not ${2 * a + b}.`);
    

    如果模板字符串中的变量没有声明,会报错:

    // 变量 place 没有声明
    let msg = `Hello, ${place}`; // 报错
    
  • 嵌套模板
  • 标签模板
    ==tag 函数(?)==

17.4 扩展方法

  • String.fromCodePoint()从 Unicode 码点返回对应字符(可以识别大于 0xFFFF 的字符)

    弥补了 String.fromCharCode()方法的不足

  • String.includes()是否包含该字符串(es5 中使用 indexOf)
    const str = 'mybj'
    console.log(str.includes('my'))  //true
    
  • String.startsWith()判断是否在头部
    const str = 'mybj'
    console.log(str.endsWith('myb'))  //true
    
  • String.endsWith()判断是否在尾部

    上述三个方法都有第二个参数 n

    includesstartsWith从第n个位置直到字符串结束

    endsWith是对前n个字符

  • String.repeat(n)将原字符串重复n次后返回一个字符串如果是小数,会被取整如果是负数或者infinity,报错NaN等同 0
    const str = 'yl'
    const newStr = str.repeat(10)
    console.log(newStr)  //ylylylylylylylylylyl
    
  • String.raw() 在斜杆前面再加一个斜杆
    String.raw`Hi\n${2+3}!`  //"Hi\\n5!"
    
    // 等同于`foo${1 + 2}bar`  "foo3bar"
    String.raw({ raw: ['foo', 'bar'] }, 1 + 2) 
    
  • String.codePointAt() 返回码点的十进制值.
  • String.normalize()
  • String.trimStart()【trimLeft()】 消除头部的空格,尾部会被保留
  • String.trimEnd() 【trimRight()】消除尾部的空格,头部会被保留
  • String.matchAll() 返回一个正则表达式在当前字符串的所有匹配
  • String.replaceAll(searchValue, replacement) 替换掉所有匹配值searchValue不能是不带 g 修饰符的正则表达式,会报错replacement为替换的文本,也可以是函数,或是以下特殊字符串:
    • $&:匹配的子字符串。
    • $ `:匹配结果前面的文本。
    • $':匹配结果后面的文本。
    • $n:匹配成功的第n组内容,n是从 1 开始的自然数。这个参数生效的前提是,第一个参数必须是正则表达式。
    • $$:指代美元符号$
      // $& 表示匹配的字符串,即`b`本身
      // 所以返回结果与原字符串一致
      'abbc'.replaceAll('b', '$&')
      // 'abbc'
      
      // $` 表示匹配结果之前的字符串
      // 对于第一个`b`,$` 指代`a`
      // 对于第二个`b`,$` 指代`ab`
      'abbc'.replaceAll('b', '$`')
      // 'aaabc'
      
      // $' 表示匹配结果之后的字符串
      // 对于第一个`b`,$' 指代`bc`
      // 对于第二个`b`,$' 指代`c`
      'abbc'.replaceAll('b', `$'`)
      // 'abccc'
      
      // $1 表示正则表达式的第一个组匹配,指代`ab`
      // $2 表示正则表达式的第二个组匹配,指代`bc`
      'abbc'.replaceAll(/(ab)(bc)/g, '$2$1')
      // 'bcab'
      
      // $$ 指代 $
      'abc'.replaceAll('b', '$$')
      // 'a$c'
      

在 es5 中使用replace()如果想要匹配所有,需要使用正则表达式

18. 正则表达式的拓展

作用:检索、替换那些符合某个模式(规则)的文本

eg. 验证表单(匹配)、过滤页面内容中的一些敏感词(替换),或从字符串中获取我们想要的特定部分(提取)

18.1 RegExp 构造函数

  1. 利用 RegExp 对象来创建
  2. 利用字面量创建
    var regex = new RegExp('xyz', 'i');
    // 等价于 var regex = /xyz/i;
    //ES5 不允许此时使用第二个参数添加修饰符
    
    var regex = new RegExp(/xyz/i);
    // 等价于 var regex = /xyz/i;
    

测试正则表达式 test() 返回布尔值 regexObj.test(str) 检测是否符合正则表达式要求的规范

正则表达式里面不需要使用引号

18.2 y 修饰符

“粘连”修饰符。

后一次匹配都从上一次匹配成功的下一个位置开始。

与 g 修饰符类似,全局匹配。

不同:

  • g 修饰符只要剩余位置中存在匹配就可
  • y 修饰符确保匹配必须从剩余的第一个位置开始
var s = 'aaa_aa_a';
var r1 = /a+/g;
var r2 = /a+/y;

r1.exec(s) // ["aaa"]
r2.exec(s) // ["aaa"]

r1.exec(s) // ["aa"]
r2.exec(s) // null
//y 修饰符号隐含了头部匹配的标志^

检测 y 标志 => sticky

var pattern = /hello\d/y;
console.log(patten.sticky);
  • lastIndex 指定从 xx 位置开始匹配

18.3 u 修饰符

Unicode 模式

  • 处理大于 \uFFFF 的 Unicode 字符
  • 点字符 除了换行符以外的任意单个字符
    var s = '';
    
    /^.$/.test(s)   // false
    /^.$/u.test(s)  // true,需要添加 u 字符
    
  • i 修饰符
    /[a-z]/i.test('\u212A') // false
    /[a-z]/iu.test('\u212A') // true
    
  • unicode 是否设置了 u 修饰符
  • 处理不兼容 es6:
    function hasRegExpU() {
        try {
        	var pattern = new RegExp(".", "u");
        	return true;
        } catch (ex) {
        	return false;
    	}
    }
    

18.4 flags 属性

  • source 获取正则表达式的文本
  • flags 返回正则表达式中是由标志组成的字符串形式
    var re = /ab/g;
    console.log(re.source); // "ab"
    console.log(re.flags); // "g"
    

18.5 后行断言

  • 先行断言:x只有在y前面才匹配,必须写成/x(?=y)/
  • 先行否定断言:x只有不在y前面才匹配,必须写成/x(?!y)/
  • 后行断言:x只有在y后面才匹配,必须写成/(?<=y)x/
  • 后行否定断言:x只有不在y后面才匹配,必须写成/(?<!y)x/

18.6 具名组匹配

用圆括号分组:

const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/;

const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj[1]; // 1999
const month = matchObj[2]; // 12
const day = matchObj[3]; // 31

ES2018 引入了具名组匹配

/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/

18.7 引用

如果要在正则表达式内部引用某个“具名组匹配”,可以使用\k<组名>的写法

18.8 正则匹配索引

  • indices 返回每个组
  • indices.groups 提供具名组匹配Z的开始位置和结束位置

    获取组匹配不成功,均返回undefined

const text = 'zabbcdef';
const re = /ab+(cd(ef))/;
const result = re.exec(text);

result.indices // [ [1, 8], [4, 8], [6, 8] ]
const text = 'zabbcdef';
const re = /ab+(?cd)/;
const result = re.exec(text);

result.indices.groups // { Z: [ 4, 6 ] }

19. 数值的拓展

19.1 二进制 0B 八进制 0O

const a = 5;
console.log(a.toString(2));  //十进制转换成二进制 101

const b = 101;
console.log(parseInt(b,2));  //二进制转换成十进制
const a = 0B0101  //二进制
console.log(a)

const b = 0O777  //八进制
console.log(b)  //输出的是十进制

19.2 新增方法

  • Number.isFinite() 检查一个数值是否为有限的:
    //数值就会返回 true,其他的都是 false
    Number.isFinite(15) // true
    Number.isFinite(0.8) // true
    Number.isFinite(NaN) // false
    Number.isFinite(Infinity) // false
    Number.isFinite(-Infinity) // false
    Number.isFinite('foo') // false
    Number.isFinite('15') // false
    Number.isFinite(true) // false
    
  • Number.isNaN() 检查一个值是否为 NaN:
    //NAN 值就返回 true
    Number.isNaN(NaN) // true
    Number.isNaN(15) // false
    Number.isNaN('15') // false
    Number.isNaN(true) // false
    Number.isNaN(9 / NaN) // true
    Number.isNaN('true' / 0) // true
    Number.isNaN('true' / 'true') // true
    
  • Number.parseInt()在 es5 中,parseInt 是 window 上的
  • Number.parseFloat()同上
  • Number.isInteger() 判断一个数值是否为整数

    JavaScript 内部,整数和浮点数采用的是同样的储存方法,所以 25 和 25.0 被视为同一个值。

    存在误判的情况 例如精度丢失、小于Number.MIN_VALUE

    Number.isInteger(25) // true
    Number.isInteger(25.1) // false
    
    Number.isInteger() // false
    Number.isInteger(null) // false
    Number.isInteger('15') // false
    Number.isInteger(true) // false
    
  • Number.MAX_SAFE_INTEGER 最大安全数:2^53 = 9007199254740991
  • Number.MIN_SAFE_INTEGER -9007199254740991
  • Number.isSafeInteger() 在-2^53^到 2^53^之间(不含两个端点)
  • Number.EPSILON 表示 1 与大于 1 的最小浮点数之间的差 [可接受的最小误差范围]

    最小精度。误差如果小于这个值,就可以认为已经没有意义了,即不存在误差

19.3 Math 拓展

ES6 在 Math 对象上新增了 17 个与数学相关的方法。所有这些方法都是静态方法,只能在 Math 对象上调用

  • Math.trunc() 去除一个数的小数部分,返回整数部分 true 代表 1,false 代表 0,其余非数值的返回 NaN
    console.log(Math.trunc(5.5))
    console.log(Math.trunc(-5.5))
    console.log(Math.trunc(true)) // 1
    console.log(Math.trunc(false)) // 0
    console.log(Math.trunc(NaN)) // NaN
    console.log(Math.trunc(undefined)) // NaN
    console.log(Math.trunc()) // NaN
    
  • Math.sign() 判断正数、负数、零,true 和 false 会转换为数值后进行判断
    console.log(Math.sign(5)) // 1
    console.log(Math.sign(-5)) // -1
    console.log(Math.sign(0)) // 0
    console.log(Math.sign(NaN)) // NaN
    console.log(Math.sign(true)) // 1
    console.log(Math.sign(false)) // 0
    
  • Math.cbrt() 计算一个数的立方根,非数的返回 NaN。
  • Math.clz32() 将参数转为 32 位无符号整数的形式,返回 32 位值里面有多少个前导 0 只考虑整数部分
    Math.clz32(1000) // 22 1000 的二进制形式是 0b1111101000,一共有 10 位,所以 32 位之中有 22 个前导 0
    Math.clz32(0b01000000000000000000000000000000) // 1
    Math.clz32(0b00100000000000000000000000000000) // 2
    

    左移运算符 <<Math.clz32方法直接相关

  • Math.imul() 效果和(a*b)|0 相同,可以处理溢出的情况
    Math.imul(-2, -2) // 4
    (0x7fffffff * 0x7fffffff)|0 // 0
    Math.imul(0x7fffffff, 0x7fffffff) // 1
    
  • Math.fround() 将 64 位双精度浮点数转为 32 位单精度浮点数
    // 丢失精度
    Math.fround(0.7)   // 0.699999988079071
    
    //对于 NaN 和 Infinity,此方法返回原值
    Math.fround(NaN)      // NaN
    Math.fround(Infinity) // Infinity
    
    //先将其转为数值,再返回单精度浮点数
    Math.fround('5')      // 5
    Math.fround(true)     // 1
    Math.fround(null)     // 0
    Math.fround([])       // 0
    Math.fround({})       // NaN
    
  • Math.hypot() 返回所有参数的平方和的平方根,先将非数值的转换为数值,无法转换的返回 NaN
    Math.hypot(3, 4);        // 5
    Math.hypot(3, 4, 5);     // 7.0710678118654755
    Math.hypot();            // 0
    Math.hypot(NaN);         // NaN
    Math.hypot(3, 4, 'foo'); // NaN
    Math.hypot(3, 4, '5');   // 7.0710678118654755
    Math.hypot(-3);          // 3
    
  • Math.expm1() Math.expm1(x) => ex – 1 == Math.exp(x) - 1
  • Math.log1p() Math.log1p(x) == Math.log(1 + x)
  • Math.log10() 返回以 10 为底的x的对数
  • Math.log2() 返回以 2 为底的x的对数

以上三个方法,如果x小于 0,则返回 NaN

  • 双曲函数方法:
    • Math.sinh(x) 返回x的双曲正弦
    • Math.cosh(x) 返回x的双曲余弦
    • Math.tanh(x) 返回x的双曲正切
    • Math.asinh(x) 返回x的反双曲正弦
    • Math.acosh(x) 返回x的反双曲余弦
    • Math.atanh(x) 返回x的反双曲正切

20. 代理 proxy

自定义一些常用行为如查找、赋值、枚举、函数调用等

20.1 基本语法

let p = new Proxy(target, handler)

target :用来代理的“对象”,被代理之后不能直接被访问

handler :实现代理的过程

20.2 拦截操作场景

let o = {
    name: 'xiaoming',
    age: 20
}

let handler = {
    get(obj, key) {
        return Reflect.has(obj, key) ? obj[key] : ''
    }
}

let p = new Proxy(o, handler)

console.log(p.from)

场景 1

从服务端获取的数据希望是只读,不允许在任何一个环节被修改。

// response.data 是 JSON 格式的数据,来自服务端的响应
// 在 ES5 中只能通过遍历把所有的属性设置为只读
for (let [key] of Object.entries(response.data)) {
    Object.defineProperty(response.data, key, {
        writable: false
    })
}

使用 Proxy :

let data = new Proxy(response.data, {
    set(obj, key, value) {
        return false
    }
})

场景 2

校验:

// Validator.js
export default (obj, key, value) => {
    if (Reflect.has(key) && value > 20) {
        obj[key] = value
    }
}

import Validator from './Validator'
let data = new Proxy(response.data, {
    set: Validator
})

场景 3

对读写进行监控:

let validator = {
    set(target, key, value) {
        if (key === 'age') {
            if (typeof value !== 'number' || Number.isNaN(value)) {  // 非数值、空值
                throw new TypeError('Age must be a number')
            }
            if (value <= 0) { // 输入的值小于等于 0 throw new TypeError('Age must be a positive number') } } return true } } const person = { age: 27 } const proxy = new Proxy(person, validator) // 添加监控 window.addEventListener( 'error', e => {
        console.log(e.message) // Uncaught TypeError: Age must be a number
    },
    true
)

场景 4

实例一个对象,每个对象都有一个自己的 id 而且只读。

class Component {
    constructor() {
        this.proxy = new Proxy({
            id: Math.random().toString(36).slice(-8)
        })
    }
    get id() {
        return this.proxy.id
    }
}

20.3 常用拦截操作

get: 拦截对象属性的读取

let arr = [7, 8, 9]
arr = new Proxy(arr, {
    get(target, prop) {
        return prop in target ? target[prop] : 'error'
    }
})
console.log(arr[1])  //8
console.log(arr[10])  //error

set: 拦截对象属性的设置

let arr = []
arr = new Proxy(arr, {
    set(target, prop, val) {
        if (typeof val === 'number') {
            target[prop] = val
            return true  //需要返回一个布尔值
        } else {
            return false
        }
    }
})
arr.push(5)
arr.push(6)
console.log(arr[0], arr[1], arr.length)

has: 拦截 propKey in proxy 的操作,返回一个布尔值。

let range = {
    start: 1,
    end: 5
}

range = new Proxy(range, {
    has(target, prop) {
        return prop >= target.start && prop <= target.end
    }
})
console.log(2 in range)
console.log(9 in range)

ownKeys:

拦截Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in循环,返回一个数组。

该方法返回目标对象所有自身的属性的属性名。

Object.keys()的返回结果仅包括目标对象自身的可遍历属性:

let obj = {
    name: 'imooc',
    [Symbol('es')]: 'es6'
}
console.log(Object.getOwnPropertyNames(obj))  //["name"]
console.log(Object.getOwnPropertySymbols(obj))  //[Symbol(es)]
console.log(Object.keys(obj))
for (let key in obj) {
    console.log(key)   //name
}

let userinfo = {
    username: 'zhangsna',
    age: 34,
    _password: '***'
}
userinfo = new Proxy(userinfo, {
    ownKeys(target) {
        return Object.keys(target).filter(key => !key.startsWith('_')) // 过滤
    }
})
console.log(Object.keys(userinfo))

deleteProperty:

拦截delete proxy[propKey]的操作,返回一个布尔值。

let user = {
    name: 'zhangsna',
    age: 34,
    _password: '***'
}
user = new Proxy(user, {
    get(target, prop) {
        if (prop.startsWith('_')) {
            throw new Error('不可访问')
        } else {
            return target[prop]
        }
    },
    set(target, prop, val) {
        if (prop.startsWith('_')) {
            throw new Error('不可访问')
        } else {
            target[prop] = val
            return true  //返回一个布尔值
        }
    },
    deleteProperty(target, prop) { // 拦截删除
        if (prop.startsWith('_')) {
            throw new Error('不可删除')
        } else {
            delete target[prop]
            return true
        }
    },
    ownKeys(target) {
        return Object.keys(target).filter(key => !key.startsWith('_'))
    }
})
console.log(user.age)
console.log(user._password)
user.age = 18
console.log(user.age)
try {
    user._password = 'xxx'
} catch (e) {
    console.log(e.message)
}

try {
    // delete user.age
    delete user._password
} catch (e) {
    console.log(e.message)
}
console.log(user.age)

for (let key in user) {
    console.log(key)
}

apply

拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)proxy.call(object, ...args)proxy.apply(...)

let sum = (...args) => {
    let num = 0
    args.forEach(item => {
        num += item
    })
    return num
}

sum = new Proxy(sum, {
    apply(target, ctx, args) {
        return target(...args) * 2
    }
})
console.log(sum(1, 2))
console.log(sum.call(null, 1, 2, 3))
console.log(sum.apply(null, [1, 2, 3]))  //需要是数组

construct

拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)

let User = class {
    constructor(name) {
        this.name = name
    }
}
User = new Proxy(User, {
    construct(target, args, newTarget) {
        console.log('construct')
        return new target(...args)
    }
})
console.log(new User('imooc'))

 

21. 反射 Reflect

和 Proxy 一起使用

21.1 设计目的

  • 将 Object 属于语言内部的方法放到 Reflect 上:
    let obj = {}
    let newVal = ''
    Reflect.defineProperty(obj, 'name', {
        get() {
            return newVal
        },
        set(val) {
            console.log('set')
            // this.name = val
            newVal = val
        }
    })
    obj.name = 'es'
    console.log(obj.name)
    
  • 修改某些 Object 方法的返回结果,让其变得更合理
    // 老写法
    try {
        Object.defineProperty(target, property, attributes)
        // success
    } catch (e) {
        // failure
    }
    
    // 新写法
    if (Reflect.defineProperty(target, property, attributes)) {
        // success
    } else {
        // failure
    }
    
  • 让 Object 操作变成函数行为
    // 老写法
    'assign' in Object // true
    
    // 新写法
    Reflect.has(Object, 'assign') // true
    
  • Reflect对象的方法与Proxy对象的方法一一对应(只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法)
    Proxy(target, {
        set: function(target, name, value, receiver) {
            var success = Reflect.set(target, name, value, receiver)
            if (success) {
                console.log('property ' + name + ' on ' + target + ' set to ' + value)
            }
            return success
        }
    })
    

Reflect是一个内置的对象,提供拦截 JavaScript 操作的方法,这些方法与处理器对象的方法相同

Reflect不是一个函数对象,因此它是不可构造的。

Reflect没有构造函数(不能与 new 使用,或将 Reflect 对象作为函数调用

Reflect 的所有属性和方法都是静态的

21.2 常用方法

Reflect.apply()

Reflect.apply(target, thisArgument, argumentsList)
参数 含义 必选
target 目标函数 Y
thisArgument target 函数调用时绑定的 this 对象 N
argumentsList target 函数调用时传入的实参列表,该参数应该是一个类数组的对象 N
Reflect.apply(Math.floor, undefined, [1.75])    // 1

Reflect.apply(String.fromCharCode, undefined, [104, 101, 108, 108, 111])  // "hello"

Reflect.apply(RegExp.prototype.exec, /ab/, ['confabulation']).index   // 4

Reflect.apply(''.charAt, 'ponies', [3])  // "i"

ES5 对比

与 ES5 中Function.prototype.apply()方法类似:

Function.prototype.apply.call(Math.floor, undefined, [1.75])

Reflect.construct()

允许使用可变的参数来调用构造函数

Reflect.construct(target, argumentsList[, newTarget])
参数 含义 必选
target 被运行的目标函数 Y
argumentsList 调用构造函数的数组或者伪数组 Y
newTarget 该参数为构造函数, 参考 new.target 操作符,如果没有 newTarget 参数, 默认和 target 一样 N

如果target或者newTarget不是构造函数,抛出 TypeError。

function someConstructor() {}
var result = Reflect.construct(Array, [], someConstructor)

Reflect.getPrototypeOf(result) // 输出:someConstructor.prototype
Array.isArray(result) // true

Reflect.defineProperty()

静态方法 Reflect.defineProperty() 基本等同于 Object.defineProperty() 方法,唯一不同是返回 Boolean 值。

Reflect.defineProperty(target, propertyKey, attributes)
参数 含义 必选
target 目标对象 Y
propertyKey 要定义或修改的属性的名称 Y
attributes 要定义或修改的属性的描述 Y
const student = {}
Reflect.defineProperty(student, 'name', {
    value: 'Mike'
}) // true
student.name // "Mike"

Reflect.deleteProperty()

Reflect.deleteProperty 允许你删除一个对象上的属性。

返回一个 Boolean 值表示该属性是否被成功删除。

Reflect.deleteProperty(target, propertyKey)
参数 含义 必选
target 删除属性的目标对象 Y
propertyKey 将被删除的属性的名称 Y
var obj = {
    x: 1,
    y: 2
}
Reflect.deleteProperty(obj, "x") // true
obj // { y: 2 }

var arr = [1, 2, 3, 4, 5]
Reflect.deleteProperty(arr, "3") // true
arr // [1, 2, 3, , 5]

// 如果属性不存在,返回 true
Reflect.deleteProperty({}, "foo") // true

// 如果属性不可配置,返回 false
Reflect.deleteProperty(Object.freeze({
    foo: 1
}), "foo") // false

Reflect.get()

Reflect.get() 方法的工作方式,就像从 object (target[propertyKey]) 中获取属性,但它是作为一个函数执行的。

Reflect.get(target, propertyKey[, receiver])
参数 含义 必选
target 需要取值的目标对象 Y
propertyKey 需要获取的值的键值 Y
receiver 如果遇到 getter,此值将提供给目标调用 N
// Object
var obj = {
    x: 1,
    y: 2
}
Reflect.get(obj, 'x') // 1

// Array
Reflect.get(['zero', 'one'], 1) // "one"

// Proxy with a get handler
var x = {
    p: 1
}
var obj = new Proxy(x, {
    get(t, k, r) {
        return k + 'bar'
    }
})
Reflect.get(obj, 'foo') // "foobar"

Reflect.getOwnPropertyDescriptor()

Object.getOwnPropertyDescriptor() 方法相似

如果在对象中存在,则返回给定的属性的属性描述符,否则返回 undefined

Reflect.getOwnPropertyDescriptor(target, propertyKey)
参数 含义 必选
target 需要寻找属性的目标对象 Y
propertyKey 获取自己的属性描述符的属性的名称 N
Reflect.getOwnPropertyDescriptor({
    x: 'hello'
}, 'x')
// {value: "hello", writable: true, enumerable: true, configurable: true}

Reflect.getOwnPropertyDescriptor({
    x: 'hello'
}, 'y')
// undefined

Reflect.getOwnPropertyDescriptor([], 'length')
// {value: 0, writable: true, enumerable: false, configurable: false}

对比

如果该方法的第一个参数不是一个对象(一个原始值),那么将造成 TypeError 错误。

而对于 Object.getOwnPropertyDescriptor,非对象的第一个参数将被强制转换为一个对象处理。

Reflect.getOwnPropertyDescriptor("foo", 0)
// TypeError: "foo" is not non-null object

Object.getOwnPropertyDescriptor("foo", 0)
// { value: "f", writable: false, enumerable: true, configurable: false }

Reflect.getPrototypeOf()

Object.getPrototypeOf() 方法是一样,返回指定对象的原型

Reflect.getPrototypeOf(target)
参数 含义 必选
target 获取原型的目标对象 Y

Reflect.has()

检查一个对象是否拥有某个属性, 相当于in操作符

Reflect.has(target, propertyKey)
参数 含义 必选
target 获取原型的目标对象 Y
propertyKey 属性名,需要检查目标对象是否存在此属性 Y

Reflect.isExtensible()

Reflect.isExtensible判断一个对象是否可扩展(即是否能够添加新的属性)。

Object.isExtensible()方法一样。

Reflect.isExtensible(target)
参数 含义 必选
target 获取原型的目标对象 Y

Reflect.ownKeys()
返回一个由目标对象自身的属性键组成的数组。

返回值等同于 Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))

Reflect.ownKeys(target)
参数 含义 必选
target 获取原型的目标对象 Y
Reflect.ownKeys({
    z: 3,
    y: 2,
    x: 1
}) // [ "z", "y", "x" ]
Reflect.ownKeys([]) // ["length"]

var sym = Symbol.for("comet")
var sym2 = Symbol.for("meteor")
var obj = {
    [sym]: 0,
    "str": 0,
    "773": 0,
    "0": 0,
    [sym2]: 0,
    "-1": 0,
    "8": 0,
    "second str": 0
}
Reflect.ownKeys(obj)
// [ "0", "8", "773", "str", "-1", "second str", Symbol(comet), Symbol(meteor) ]
// Indexes in numeric order,
// strings in insertion order,
// symbols in insertion order

Reflect.preventExtensions()

阻止新属性添加到对象 (eg. 防止将来对对象的扩展被添加到对象中)

Object.preventExtensions() 方法一致

Reflect.preventExtensions(target)
参数 含义 必选
target 获取原型的目标对象 Y
// Objects are extensible by default.
var empty = {}
Reflect.isExtensible(empty) // === true

// ...but that can be changed.
Reflect.preventExtensions(empty)
Reflect.isExtensible(empty) // === false
Reflect.preventExtensions(1)
// TypeError: 1 is not an object

Object.preventExtensions(1)
// 1

Reflect.set()

允许在对象上设置属性,

给属性赋值,并像 property accessor 语法一样,但以函数的方式。

Reflect.set(target, propertyKey, value[, receiver])
参数 含义 必选
target 获取原型的目标对象 Y
propertyKey 设置的属性的名称 Y
value 设置的值 Y
receiver 如果遇到 setter,this 将提供给目标调用 N
// Object
var obj = {}
Reflect.set(obj, "prop", "value") // true
obj.prop // "value"

// Array
var arr = ["duck", "duck", "duck"]
Reflect.set(arr, 2, "goose") // true
arr[2] // "goose"

// It can truncate an array.
Reflect.set(arr, "length", 1) // true
arr // ["duck"]

// With just one argument, propertyKey and value are "undefined".
var obj = {}
Reflect.set(obj) // true
Reflect.getOwnPropertyDescriptor(obj, "undefined")
// { value: undefined, writable: true, enumerable: true, configurable: true }

Reflect.setPrototypeOf()

改变指定对象的原型。

Reflect.setPrototypeOf(target, prototype)
参数 含义 必选
target 获取原型的目标对象 Y
prototype 对象的新原型 (一个对象或 null) Y
Reflect.setPrototypeOf({}, Object.prototype) // true

// It can change an object's [[Prototype]] to null.
Reflect.setPrototypeOf({}, null) // true

// Returns false if target is not extensible.
Reflect.setPrototypeOf(Object.freeze({}), null) // false

// Returns false if it cause a prototype chain cycle.
var target = {}
var proto = Object.create(target)
Reflect.setPrototypeOf(target, proto) // false

22. 异步操作

异步操作

console.log(1);  //(1)
setTimeout(() => {  //(2)
    console.log(2)
},0);
console.log(3);  //(3)
//1  3  2

(1)(3)属于主线程任务,为同步操作,(2)为异步任务,先进入 Event Table 中,等待 0 秒后进入 Event Queue 中等待主线程的任务全部完成后,再读取任务队列中结果进入主线程执行。

所以,如果有一个异步任务经过 2 秒后进入到 Event Queue 中,但是主线程的任务需要 5 秒才能执行完毕,此时的异步任务会在 Event Queue 中等待主线程任务完成,即等待 3 秒后进入主线程。

Ajax

function ajax(url,callback) {
    // 1. 创建 XMLHttpRequest 对象
    var xmlhettp
    if(window.XMLHttpRequest) {
        xmlhttp = new XMLHttpRequest()
    } else { //兼容早期浏览器
        xmlhttp = new ActiveXObject('Microsoft.XMLHTTP')
    }
    // 2. 发送请求
    xmlhttp.open('GET',url,true)
    xmlhttp.send()
    // 3. 服务端相应
    xmlhttp.onreadystatechange = function () {
        if(xmlhttp.readState === 4 && xmlhttp.staus === 200) {
            var obj = JSON.parse(xmlhttp.responseText])
            callback(obj)
        }
    }
}
var url = '...';
ajax(url,res => {
    console.log(res)
})

23. Promise

23.1 基本用法

//resolve 成功,rejecth 失败
let p = new Promise((resolve,rejecth) => {
    setTimeout(() => {
        console.log(1)
        // 一般情况下,使用 if else 语句进行判断是否成功
        //if(){  
        //   resolve()
        //   }else{
        //       reject()
        //   }
        },1000)
}).then(() => {  //第一个方法必须要写,第二个方法可以省略
    console.log('成功')
},() => {
    console.log('失败')
})

//可以在 resolve 写入参数,再通过传参来完成
//resolve('success')
//reject('fail')
//.then((res) => {
//    console.log(res)  //success
//}),(err) => {
//    console.log(err)  //fail
//}

23.2 状态

promise 状态

let p1 = new Promise((resolve, reject) => {
    resolve(1)
})
let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(2)
    }, 1000)
})
let p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject(3)
    }, 1000)
})
console.log(p1) // resolved
console.log(p2) // pending ==> 1 秒后变成 resolved
console.log(p3) // pending ==> 1 秒后变成 rejected
setTimeout(() => {
    console.log(p2)
}, 2000)
setTimeout(() => {
    console.log(p3)
}, 2000)

p1.then(res => {
    console.log(res)  //1
})
p2.then(res => {
    console.log(res)  //2
})
p3.catch(err => {  //使用 catch 捕获错误
    console.log(err)  //3
})

promise 状态

let p = new Promise((resolve, reject) => {
    resolve(2)
    reject(1)
}).then(res => {
    console.log(res)
}).catcj(err => {
    console.log(err)
})
//只能输出 2,Promise 状态不能被改变

23.3 使用 Promise 发送 ajax 请求

单纯使用 ajax 需要嵌套非常多层。

使用 Promise 有大量重复代码,抽离出来写成一个函数,使得代码可读性更强,也有利于后期维护。

function getPromise(url) {
    return new Promise((resolve, reject) => {
        ajax(url, res => {
        	resolve(res)
    	})
    })
}
getPromise(...)
    .then(res => {
    console.log(res)
	return getPromise(...)
    }).then(res => {
        console.log(res)
        return getPromise(...)
    }).then(res => {
        console.log(res)
    })

统一捕获 err:

function getPromise(url) {
    return new Promise((resolve, reject) => {
        ajax(url, res => {
        	resolve(res)
    	})
    })
}
getPromise(...)
    .then(res => {
        console.log(res)
	return getPromise(...)
    }).then(res => {
        console.log(res)
        return getPromise(...)
    }).then(res => {
        console.log(res)
    }).catch(err => {
        console.log(err)
    })  //上述任何一个出现错误都会调用

23.4 Promise 的静态方法

Promise.resolve('success')

Promise.reject('fail')
function foo(flag) {
    if(flag) {
        return new Promise(resolve => {
            //异步操作
            resolve('success')
        })
    } else {
        return Promise.reject('fail')  //如果写成 return 'fail',当条件为 false 的时候,会报错
    }
}

foo(false).then(res => {
    console.log(res)  //fail
},err => {
    console.log(err)
})

Promise.all([...]) 所有对象都完成之后才会进入 res,只要有一个是失败的,都会进入 err 中

可应用于上传多张图片中

Promise.all([p1,p2,p3]).then(res => {
    console.log(res)}, err => {
    console.log(err)
})
const imgArr = ['1.jpg', '2.jpg', '3.jpg']
let promiseArr = []
imgArr.forEach(item => {
    promiseArr.push(new Promise((resolve, reject) => {
        // 图片上传的操作
        resolve()
    }))
})
Promise.all(promiseArr).then(res => {
    // 插入数据库的操作
    console.log('图片全部上传完成')
})

Promise.race([...]) 只要有一个成功,整个就会进入 res 中

可应用于请求图片超时

Promise.race([p1,p2,p3]).then(res => {
    console.log(res)}, err => {
    console.log(err)
})
function getImg() {
    return new Promise((resolve, reject) => {
        let img = new Image()
        img.onload = function () {
            resolve(img) //返回图片
        }
        // img.src = 'http://www.xxx.com/xx.jpg'
        img.src = 'https://www.imooc.com/static/img/index/logo.png'
    })
}

function timeout() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('图片请求超时')
        }, 2000)
    })
}

Promise.race([getImg(), timeout()]).then(res => {
    console.log(res)
}).catch(err => {
    console.log(err)
}

24. Generator

function* foo() {
    for (let i = 0; i < 3; i++) { yield i } } let f = foo() console.log(f.next()) console.log(f.next()) console.log(f.next()) console.log(f.next()) //yield 关键字只存在于 Generator,这里的的 yield 关键字是在 forEach 函数里的 // function* gen(args) { // args.forEach(item => {
//         yield item + 1
//     })
// }
function* gen(x) {
    let y = 2 * (yield(x + 1))
    let z = yield(y / 3)
    return x + y + z
}
//在 next 里可以传递参数
let g = gen(5)
console.log(g.next()) // 6
console.log(g.next(12)) // y=24  8(对应的 x+1=12)
console.log(g.next(13)) // z=13 x=5 42(对应的 y/3=13

使用 Generator 进行 ajax 请求:

function request(url) {
    ajax(url, res => {
        getData.next(res)
    })
}

function* gen() {
    let res1 = yield request('static/a.json')
    console.log(res1)
    let res2 = yield request('static/b.json')
    console.log(res2)
    let res3 = yield request('static/c.json')
    console.log(res3)
}
let getData = gen()
getData.next()

25. Module

  • export default 默认,导入不需要知道命名(可以直接使用别名)
  • import * from ‘../../xx.js’

把庞大的代码拆开。

将多个功能的代码按功能进行分开,以达到多个模块组合在一起形成一个功能复杂的功能。

好处
  • 防止命名冲突
  • 代码复用
  • 高维护性
语法

<script type="module"> </script>

也可以使用 <script src="./src/js/app.js" type="module"></script> 将引用部分放到另一个 js 文件里

export 对外接口

导入的时候命名要完全一样,可以起别名,起了别名之后文件中使用只能使用别名,原名已经失效了

export 和 export default 可以一起使用 import add, {str} from '../../xxx.js'
分别暴露:在要暴露的语句前面+export。

统一暴露:在某个位置使用 export{},将要暴露的数据放在花括号里面。

在模块文件里,使用 export default

export default {

​ ...

}

这样就可以直接使用了。

默认暴露:export.default = { },这种方法在调用时需要添加default

导入不需要知道命名(可以直接使用别名)

  • import 输入其他模块提供的功能
    • 通用的导入方式:import * as m1 from “./src/js/m1.js”;

      导入的是全部

    • 解构赋值的形式:
      • import{school,teach} from “./src/js/m1.js”;
      • import{default as m1} from “./src/js/m1.js”;

      重名时需要使用别名,不然会报错

    • 简便形式(针对默认暴露):improt m3 from “./src/js/m3.js”
  • 使用 babel
    • 安装工具 npm i babel-cli babel-preset-env browerify -D
    • 编译: npx babel src/js -d dist/js --presets=babel-preset-env 先 [原文件目录] 后 [存放文件目录]
    • 打包 : npx browserify dist/js/app.js -o dist/bundle.js 将存放文件目录下的文件打包生成 bundle.js 文件

ES7

1. 数组拓展

  • Array.prototype.includes(searchElement[,fromIndex])
  • includes VS indexOf
    • includes 返回布尔值,可以检测 NaN
    • indexOf 返回 index / -1,不可以检测 NaN
  • 幂运算符:**等同于Math.pow()

ES8

1. 异步编程解决方案 Async Await

两者成对出现,代码可读性更强。

function timeout() {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log(1)
            resolve()
        },1000)
    })
}

async function foo() {
    await timeout()   //等待 timeout()运行完毕后再继续往下运行
    console.log(2)
}

foo()

之前的 ajax 请求代码:

async function getData() {
    const res1 = await request('static/a.json')
    console.log(res1)
    const res2 = await request('static/b.json')
    console.log(res2)
    const res3 = await request('static/c.json')
    console.log(res3)
}

2. 对象拓展

  • Object.values() 获得值
  • Object.entries() 获得数组(key 和 value)
const res = Object,keys(obj).map(key => obj[key])
console.log(res)

//上面可以写成
console.log(Object.values(obj))
console.log(Object.entries(['a','b','c']))
//["0","a"],["1","b"],["2","c"]

3. 对象属性描述

  • Object.getOwnPropertyDescriptors()
    • value 当前对象的默认值
    • writable 是否可以修改
    • enumerable 是否可以通过 for..in 方式循环
    • configurable 是否可以删除

4. 字符串拓展

  • String.prototype.padStart() 头部补全
  • String.prototype.padEnd() 尾部补全第一个参数为长度,第二个参数为用于补全的字符串
'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'

'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'

//等于或大于最大长度,则字符串补全不生效,返回原字符串
'xxx'.padStart(2, 'ab') // 'xxx'
'xxx'.padEnd(2, 'ab') // 'xxx'
//应用于日期  yyyy-mm-dd
const now = new Date()
const year = now.getFullYear()
//padStart 是 String 原型下面的方法,所以想要将其转换为 String
//getMonth()返回的是 0-11 的数字,所以要加 1
const month = (now.getMonth() + 1).toString().padStart(2,'0') 
const day = (now.getDate()) + 1.toString().padStart(2,'0')
console.log(`${year}-${month}-${day}`)
//加密手机号
const tel = '13011111111'
const NewTel = tel.slice(-4).padStart(tel.length,'*')
console.log(NewTel)

5. 尾逗号

允许数参数列表使用尾逗号。

ES9

1. 异步迭代 for await of

  • for-await-of
  • Symbol.asyncIterator
//同步迭代
const arr = ['es6','es7','es8','es9']
arr[Symbol.iterator] = function() {
    let nextIndex = 0
    return {
        next() {
            return nextIndex < arr.length ? {
                value: arr[nextIndex++],
                done: false
            } : {
                value: undefined,
                done: true
            }
        }
    }
}
for(let item of arr) {
    console.log(item)
}
//异步迭代
function getPromise(time) {
    return new Promise((resolve,reject) => {
        setTimeout(() => {
            resolve({  //写成对象的形式
                value: time,
                done:false
            })
        },time)
    })
}
const arr = [getPromise(1000),getPromise(2000),getPromise(3000)]
arr[Symbol.asyncIterator] = function() {
    let nextIndex = 0
    return {
        next() {
            return nextIndex < arr.length ? arr[nextIndex++] : 
            Promise.resolve({
                value: undefined,
                done: true
            })
        }
    }
}
async function test() {
    for await (let item of arr) {
        console.log(item)
    }
}
test()

2. 正则表达式拓展

  • dotAlldot 不能匹配\n \r(包括两者的 Unicode)
    const reg = /./s  //匹配任意单个字符
    console.log(reg.test('5'))  //true
    console.log(reg.test('x'))  //true
    console.log(reg.test('\n'))  //true
    console.log(reg.test('\r'))  //true
    console.log(reg.test('\u{2028}'))  //true
    console.log(reg.test('\u{2029}'))  //true
    
  • 具名组匹配
    const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/;  //用圆括号分组
    
    const matchObj = RE_DATE.exec('1999-12-31');
    const year = matchObj[1]; // 1999
    const month = matchObj[2]; // 12
    const day = matchObj[3]; // 31
    
    const reg = /(?\d{4}-(?<month)\d{2}-(?\d{2}))
    const groups = reg.exec('2020-02-01').groups
    //使用解构赋值
    const {year, month,day} = groups
    console.log(year, month, day)
    
  • 后行断言 match
    • 先行断言:x只有在y前面才匹配,必须写成/x(?=y)/
    • 先行否定断言:x只有不在y前面才匹配,必须写成/x(?!y)/
    • 后行断言x只有在y后面才匹配,必须写成/(?<=y)x/
    • 后行否定断言:x只有不在y后面才匹配,必须写成/(?<!y)x/

3. 对象拓展 Rest&Spread

//克隆对象 为深拷贝
const obj3 = {..obj1}

//合并对象  为浅拷贝
const obj4 = {...obj1, ...obj2}  //obj1 和 obj2 相同键名的会被后者覆盖

//...rest 获取剩余的属性
const {name, age, ...rest} = obj1  //...rest 必须放在最后,不然会报错

4. Promise 拓展 finally()

  • Promise.prototype.finally()无论失败还是成功都会执行 finally 里面的语句【例如:成功失败相同的代码逻辑、关闭操作】

5. 字符串扩展

放松模板字符串文字限制,对一些错误不报错,返回 undefined。

ES10

1. 对象扩展

Object.fromEntries() 返回对象结构 【和Object.Entries()相反(返回键对结构)】

// map  =>  对象
const map = new Map()
map.set('name', 'n1')
map.set('name', 'n2')
console.log(map)
const fromEntries = Object.fromEntries(map)
console.log(map)  //对象格式

2. 字符串扩展

  • String.prototype.trimStart()【trimLeft()】 消除头部的空格,尾部会被保留
  • String.prototype.trimEnd() 【trimRight()】消除尾部的空格,头部会被保留
  • String.prototype.trim() 消除空格

3. 数组扩展

  • Array.prototype.flat(num) 对多维数组进行扁平化操作
    const arr = [1,2,3,[4,5,6,[7,8,9,10,11],12]]  //三维数组
    console.log(arr.flat().flat().flat())
    console.log(arr.flat(3))
    console.log(arr.flat(Infinity))
    
  • Array.prototype.flatMap()
    const arr = [1,2,3,4,5]
    //const res = arr.map(x => [x + 1]).flat() 等价于↓
    const res = arr.flatMap(x => [x + 1])
    

4. 修订 toString()

返回源代码中的实际文本片段【原样输出返回一模一样的原始代码,包括注释空格等等】。

5. 可选的 Catch Binding

省略 catch 绑定的参数和括号。

try {
  // ...
} catch {
  // ...
}

6. JSON 扩展

  • JSON superset
  • JSON.stringify() 增强能力
// JSON 超集 【少用】\u2029 \u2028
eval('var str = "youlan";\u2029 function foo(){return str;}')
console.log(foo())
//0xD800~0xDfff
console.log(JSON.stringify('\uD830\uDE0E'))  //emoji
console.log(JSON.stringify('\uD830'))  //\ud830 原样输出

7. Symbol 扩展

Symbol.prototype.description 只读属性,不可写【修改 description 也不会报错,但是不能起作用】.

const s = Symbol('yl')
console.log(s)  //Symbol(yl)
console.log(s.description)  //yl 如果没有值则返回 undefined

ES11

1. 全局模式捕获 matchAll()

String.prototype.matchAll() 和正则一起使用

const str = `
    <html>
        <body>
            <div>第一个 div</div>
            <p>这是 p</p>
            <div>第二个 div</div>
            <span>这是 span</span>
            <div>第三个 div</div>
        </body>
    </html>
`
//exec g
function selectDiv1(regExp, str) {
    let matches = []
    while(true) {
        const match = regExp.exec(str)
        if(match == null) {
            break
        }
        matches.push(match[1]) //完整匹配
    }
    return matches
}
const regExp = /<div>(.*)</div>/g
const res1 = selectDiv1(regExp, str)
console.log(res1)  //["第一个 div","第二个 div","第三个 div"]

//match
//console.log(str.match(regExp))  //["<div>第一个 div</div>","<div>第二个 div</div>","<div>第三个 div</div>"]

//replace
function selectDiv2(regExp, str) {
	let matches = []
    str.replace(regExp, (all, first) => {
        matches.push(first) //完整匹配
    }) 
    return matches
}
const res2 = selectDiv2(regExp, str)
console.log(res2) //["第一个 div","第二个 div","第三个 div"]

//matchAll 
function selectDiv3(regExp, st){
    let matches = []
    for(let match of str.matchAll(regExp)){
        matches.push(match[1]) //完整匹配
    }
    return matches
}
const res3 = selectDiv3(regExp, str)
console.log(res3) //["第一个 div","第二个 div","第三个 div"]

matchAll方法的正则表达式需要有 g(全局匹配)

2. 动态导入 Dynamic import()

按需引入,使得页面渲染更快

懒加载

eg. 点击按钮才导入某个模块、才开始渲染这一部分的东西

3. 新的原始数据类型 BigInt

console.log(1n == 1) //true
console.log(1n === 1) //false

//创建
const bigInt = BigInt(900719925474740993n)
bigInt.toSring()

4. Promise 扩展 allSettled()

  • Promise.allSettled()
  • allSettled() Vs all()
Promise.allSettled([
    Promise.resolve({
        code: 200,
        data: [1, 2, 3]
    }),
    Promise.reject({
        code: 500,
        data: []
    }),
    Promise.resolve({
        code: 200,
        data: [7, 8, 9]
    }),
]).then(res => {
    //console.log(res,"成功")
    const data = res.filter(item => item.status === "fulfilled")
    console.log(data)
}).catch(err => {
    console.log(err,"失败")
})

如果使用all(),则其中有一个 reject 都会导致整个进程进入“失败”;而allSettled(),成功的会返回status: "fulfilled" value:{...},失败的返回reson: {...},使用 filter 进行过滤获得请求成功的数据

5. 全局对象 globalThis

提供一个标准的方式去获取不同环境下的全局对象

// node: global
// web: window self
const getGlobal = () => {
    if(typeof selt !== 'undefined'){
        return self
    }
    if(typeof window !== 'undefined'){
        return window
    }
    if(typeof global !== 'undefined'){
        return global
    }
    throw new Error("无法找到全局变量")
}
const global = getGlobal()
console.log(global)

//在 es11 中
//console.log(globalThis)

6. 可选链 Optional chaining

先判断这个方法属性是否存在,如果存在再往下取

const street = user && user.address && user.address.street
console.log(street)
const num = user && user.address && user.address.getNum && user.address.getNum()
console.log(num)

//es11 中,代码更加简洁
const street = user?.address?.street
console.log(street)
const num = user?.address?.getNum?.()
console.log(num)

?. 中间不能有空格

7. 空值合并运算符 Nullish coalescing Operator

const b = null
const a = b ?? 6  //当 b 为 undefined 或 null 时,取默认值
console.log(a)

?? 中间不能有空格

ES12

String.prototype.replaceAll()

在此之前只能使用正则替换,现在可以直接使用一个快捷方式;replaceAll

//前
'jxvxscript'.replace(/x/g, 'a');

//后
// jxvxscript becomes javascript
'jxvxscript'.replaceAll('x', 'a');

Promise.any

Promise.any() 接收一个Promise可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise 。 如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promise。

const promise1 = new Promise((resolve, reject) => reject('我是失败的 Promise_1'));
const promise2 = new Promise((resolve, reject) => reject('我是失败的 Promise_2'));
const promiseList = [promise1, promise2];
Promise.any(promiseList).then(values=>{  
  console.log(values);
})
.catch(e=>{  
  console.log(e);
});

WeakRefs

使用 WeakRefs 的 Class 类创建对对象的弱引用(对对象的弱引用是指当该对象应该被 GC 回收时不会阻止 GC 的回收行为)

Logical Assignment Operators

包括这些运算符:&&=||=??=

a = 1;
b = 2;
a&&=b // a=2

/*
以上代码相当于
a && a = b

??=
作用相当于
a ?? a = b
*/

Numeric Separators —— 数字分隔符

数字增加分隔符,可以使用 _ 分割数字,方便阅读较大的数字 对于跟数字打交道比较多的同学来说,可能会更加舒服

// previous syntax before ES12
const number = 92145723;

// new syntax coming with ES12
const number = 92_145_723;
console.log(number) // 92145723

//对国人来说可以这样,万,亿为单位
const number = 1_0000;
console.log(number) // 10000

结语

呼哈,终于整理完了,新鲜出炉的最全 ES6-ES12 新特性总结,希望对大家有用,整理不易,希望对大家有帮助。

「点点赞赏,手留余香」

1

给作者打赏,鼓励TA抓紧创作!

微信微信 支付宝支付宝

还没有人赞赏,快来当第一个赞赏的人吧!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。
码云笔记 » 最全ES6-ES12新特性总结,再也不怕面试官问了

发表回复