[全面总结]「ES6」到「ES12」方法和新特性(建议收藏)

388 阅读16分钟

[总结]ES6ES12各类方法和新特性(建议收藏)

~ 此文纯粹手动敲打,如要转载请征求我的同意。禁止私自转载,谢谢🙏

ES6(ES2015)

Es6是简称,全称是ECMAScript 6.0。由于es6是2015年6月份发布的标准。又可以称之为ECMAScript 2015,或es2015。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。

关于letconst

在现代JavaScript(ES6以及之后),变量声明都是采用的let和const;

1. 首先来看let

例如:

let i;
let m;

也可以是:

let i,m;
let i=0,j=0,k=0;
let x=2,y=x*x;//初始化的语句可以使用前面的声明变量;

以上用法和var好像没区别, 咱们来看一下实际的案例:

var my_name="Mike";
function fn1(){
    console.log(my_name);
    //此时my_name的结果是什么?
    let my_name="JavaScript";
};
fn1();

这时控制台会报错 : Uncaught ReferenceError: can't access lexical declaration 'my_name' before initialization;

为什么my_name不是function外部的"Mike"呢? 因为let声明会在当前的{ }内部形成一个块级作用域,let并不能完全的说不存在变量提升,let存在一个隐式变量提升,在let声明之后形成的当前块级作用域会自动检测当前块级作用域环境,如果检测有let声明的变量,会进行隐式提升,这种提升会被浏览器的V8引擎给限制掉,所以就出现了报错,这就是暂时性死区;

2. 再来看一下const

constlet同样存在暂时性死区,和let不同的是,const专门用于声明一个常量;

来看看以下代码:

const obj={
    name:"Mike",
    age:19,
    hobby:"FootBall"
}
obj.name="John";
console.log(obj);//此时会报错吗?

不会报错; const表示声明的是一个常量,顾名思义,常量的值是不能改变的,当用const声明一个原始值类型(基本数据类型)的时候,该值如果发生变化,控制台会报错; 但如果const声明的是一个(对象类型)引用数据类型,只要这个对象的空间地址不发生改变,里面的键值对发生变化,是可以使用const声明的; 总结:

  • letconst存在隐式变量提升,都可能会形成暂时性死区;
  • 在相同作用域下letconst不允许重复声明,在嵌套作用域或不同作用域中是允许同名变量声明的;
  • letconst声明所在的{ }都会形成一个块级作用域;
  • let主要用于声明变量,const主要用于声明常量;

3. 来看看解构赋值

先来看个对象的解构例子:

let obj1={
            name:"Mike",
            age:19,
            hobby:"FootBall"
        };
        let {
            age,
            hobby
        }=obj1; console.log(age,hobby);//19,"FootBall"

此时{age,hobby}分别是两个变量名,并不是对象属性名,这点尤其注意; 这时候agehobby会去匹配obj1对象中的属性名,找到与agehobby相同变量名的属性名,则会进行匹配;

再来看个数组解构的例子:

let arr1=[0,"1",NaN,undefined,"hello"];
let [a,b,c]=arr1;
console.log(a,b,c);//0,"1",NaN 

这时候[a,b,c]里的a b c分别对应的是arr1中的前三个数值,不是索引

4. 关于扩展运算符...

我看网上有将其分为展开运算符和剩余运算符的,我的理解就是一种,先来看个例子:

let arr1=[1,2,3,4,5,6];
let arr2 = [...arr1];// [1,2,3,4,5,6]

我们可以把...arr1看作是1,2,3,4,5,6,这不是字符串,不可以单独打印出来,外层必须要套一层[]{},要么就作为形参,放在形参的最右侧使用; 所以...arr中,arr必须是一个数组对象; 并且...arr只能放在最右边,无论是作为形参还是数组,都只能将其放在最右边;

我们再来看一个例子:

let obj1={
    aa:1,
    bb:2,
    cc:3,
    dd:4,
    ee:5
};
let obj2={
    name:"Mike",
    age:19,
    ...obj1
}

此时的...obj1就相当于是把外层的衣服剥开,只露出里层的内容,所以obj2={name:"Mike", age:19,aa:1, bb:2, cc:3, dd:4, ee:5};

总结:

  • 扩展运算符...只能放在数组或对象的最右(后)侧;

  • ...的右边紧跟的是数组或对象,例如...[1,2,3]...{aa:11,bb:22};

5. 模板字符串${}

模板字符串解决了传统的""''不允许换行的问题,我们再使用${}的时候能随意换行,并且${}让解构更清晰,来看个例子:

let str`你好${变量1},再见${变量2},aaaaa`;

模板字符串中允许使用表达式,这是非常好用的地方;

6. 数组的迭代方法for of

说是数组的迭代方法,其实严谨来说for of是用来迭代iterable对象的,而数组是典型的iterable对象;

  • for offor in之间的区别

for of可迭代iterable对象中的所有私有属性或方法,不可迭代出该对象的原型上的属性或方法;

for in可迭代iterable对象或非iterable对象,迭代的是对象中的私有属性或方法,以及后期添加在该对象原型中的属性或方法;

所以一般情况下,我们要的仅仅只是对象中的私有属性或方法, 如果这个对象是一个iterable对象,for of是最好不过的了; 当然,for of只能迭代属性值,并不能迭代属性名, ES6给我们提供了一个方法可以仅迭代对象的私有属性名,叫Object.keys();

7. 对象的迭代属性方法Object.keys()

先来看个简单的例子:

function Person(name,age)(
    this.name=name;
    this.age=age;
);
Person.prototype.hobby='FootBall';
let p = new Person("Mike",19);

let aa = Object.keys(p);
console.log(aa);

此时打印的结果是["name", "age"],Object.keys()会把迭代到的属性名组成一个数组给返回出来; 我们可以在了解一下Object.keys()Object.getOwnPropertyNames的区别.

Object.getOwnPropertyNamesObject.keys()十分类似, 唯一的区别是: Object.keys()在迭代对象的属性名的时候,只会迭代出可枚举的属性名, 而Object.getOwnPropertyNames不仅仅会把所有的私有可枚举属性名迭代出来,还会迭代出私有的不可枚举属性名,例如数组的length属性.

8. Array.prototype.keys( )

和对象方法Object.keys()不一样,这里要尤其注意!

对象中的keys方法是直接作为Object的静态属性,而数组中的keys是添加到数组的原型上,意思就是说供普通数组来调用使用,来看代码:

let arr =['一','二','三'];

let bbb = arr.keys();
console.log(bbb);
for(i of bbb){
    console.log(i);//👉 0 1 2
}

这时候的控制台打印出来的bbb显示的是Array Iterator,并且展开之后什么也没有,有一个next(),所以我猜测这个api是基于generator生成器实现的方法(如有错误请纠正);

这时候要想获取到bbb中的key值,还需要对其进行迭代,这时候利用for of迭代出来值也就是我们期望的key值了;

注意: Array.prototype.keys( )括号中没有参数.

9. Array.prototype.values( )

Array.prototype.values( )类似,不过一个是keys,一个是values,两者是搭配的,使用方法一样,同样的括号中没有参数存在;

10. 来看看箭头函数=>

我们来看看以前写函数的方式:

function fn(){
    console.log('你好');
};

箭头函数的方式:

let fn=(name,age)=>{
    console.log(`你好`,name,age);
};
//如果`{}`中只有一行代码,可以更加省略,甚至只有一个参数的时候 `()`也可以省略掉
let fn=name=>console.log(`你好`,name);
//如果函数没有参数的话,`()`是不能省略的:
let fn=()=>console.log(`你好`);

关于箭头函数与普通函数的区别:

  • 箭头函数没有自己的this,函数中的this相当于是上级作用域中的this,上级作用域中的this指向谁,那么这个this就指向谁;

  • 因为没有自己的this,所以箭头函数也不能new;

  • 箭头函数没有arguments;

    • 箭头函数要想获取实参信息,可以通过在最后一个形参中传...arg的方式获取实参
  • 箭头函数没有原型对象;

11. Symbol原始类型

Symbol是ES6中新增的一种原始类型(基本数据类型),Symbol类型没有字面量语法; 要获取一个Symbol值,需要调用Symbol()方法,这个函数永远不会返回相同的值,即使每次传入的参数都一模一样,例如:

let aa=Symbol(1);
let bb=Symbol(1);
console.log(aa==bb);//false

这意味着可以将调用Symbol()取得的符号安全地用于为对象添加新属性,无需担心可能重写已有的同名属性; 但是,Symbol作为对象的属性名时,是迭代不到的,例如:

let aa = Symbol(1);
        let obj = {
            name: "mike",
            age: 19,
            aa:123
        };
        for(let i in obj){
            console.log(i);//name,age,aa
        };
        console.log(Object.keys(obj));//["name", "age", "aa"]

但是Symbol作为属性值的时候,却是可以被迭代的:

let arr=[aa,123,321];
        for(let i of arr){
            console.log(i);//Symbol(1), 123, 321
        }

12. 默认参数

例如:

function fn1(name="Mike",age=19){};

function fn2(name,age){
    name='Mike'||name;
    age=19||age;
}

fn1fn2是等价的,只不过fn1的方式是ES6新提供的一种简写方式;

13. Map对象和Set对象

Es6内置的Set(集合)类和Map(映射)类;

1) 我们先来看看Map

        let map=new Map();
        console.log(map);
        map.set("jian1","值1");
        map.set("jian2","值2");
        map.set("jian3","值3");
        map.set("jian4","值4");
        map.set("jian5","值5");
        map.set("1","字符串1");
        map.set(1,"数字1");
        console.log(map);
        console.log(map.get("jian3"));//获取到键叫"jian3"对应的值
        console.log(map.get(1));//获取到键叫1对应的值
        console.log(map.get("1"));//获取到键叫"1"对应的值
        console.log(map.size);//获取map对象的长度;

从中我们可以看出,Map类创造出的实例对象中的属性名是能够区分基本数据类型的,例如把属性名设置为1"1"是不一样的,而这个在普通对象中却是不能实现的,因为普通对象中的键名只能是字符串或Symbol类型,当给普通对象的键名设置为number类型的时候,会默认的被调用toString()方法,将其转化成字符串的number;

那么,Map的键名可以传递原始值类型,那么引用数据类型可以吗? 我们来看看:

let obj={ name:'Mike' };
let map2=new Map();
map2.set(obj,'值1');
console.log(map2);
       /*  
        map2的打印结果:
        0: {Object => "值1"}
        1: {1 => 1}
        2: {2 => 2}
        3: {3 => 3} 
        */
console.log(map2.get(obj)); //值1

由此我们可以看出,Map类的实例对象的键名是可以是各种类型的;包括本文中未提及的null undefined bigint object Array 等等;

Map还有一个特点: 可以链式给对象设置属性,比如:

//Map,set每次调用的时候都会返回自身,所以可以进行链式调用
map2.set(1,1).set(2,2).set(3,3);

除此之外,Map 有内置的 forEach 方法,与 Array 类似;

2) 我们再来看看Set

Set 是一个特殊的类型集合 —— “值的集合”(没有键),它的每一个值只能出现一次

它的主要方法如下:

  • new Set(iterable) —— 创建一个 set,如果提供了一个 iterable 对象(通常是数组),将会从数组里面复制值到 set 中。
  • set.add(value) —— 添加一个值,返回 set 本身
  • set.delete(value) —— 删除值,如果 value 在这个方法调用的时候存在则返回 true ,否则返回 false
  • set.has(value) —— 如果 value 在 set 中,返回 true,否则返回 false
  • set.clear() —— 清空 set。
  • set.size —— 返回元素个数。

面试中我们经常被问到数组去重的方法,这时候Set便是一个最简洁的方法,比如:

let arr = [1,2,0,9,0,9,0,[1,2,0,0,0],0,6,4,5,67,43,4,'你好','hello',"你好",0,0];
let set = new Set(arr);//括号里放的是一个iterable对象(通常是数组);
console.log(set); //[1,2,0,9...];

这时候就实现了数组去重;

14. class

class声明会创建一个新类并为其赋予一个名字,类意味着一组对象从同一个原型对象继承属性; 新增的class关键字并未改变JavaScript原型的本质;

class abc{
    consturctor(name,age){
        this.name=name;
        thia.age=age;
    }
    hobby='FootBall';
    my_public(){
        console.log("这是my_public");
    }
    static my_static(){
        console.log('这是我的static');
    }
}

从示例可以看出:

  • class关键字可以声明一个类,后面紧跟着类名和花括号中的类体;
  • 关键字constructor用于定义类的构造函数,但实际上定义的函数并不叫'constructor',class语句会定义一个新变量Range,并将这个特殊的构造函数的值赋给该变量;
  • 如果类不需要任何初始化,可以省略consturctor关键字及其方法体,解释器将隐式的为其创建一个空的构造函数;

1) 静态方法

class体中,把static放在方法声明前用于声明一个静态方法,该静态方法是把类当作是一个对象,以键值对的方式存储在该对象当中,只要该类(该对象)能访问,类的实例对象无法访问到.

2) 获取方法、设置方法以及其他形式的方法

class类中的,可以像在对象字面量中一样定义获取属性方法设置方法;唯一的区别是类的获取方法和设置方法后面不加括号;

一般来说,对象字面量支持所有简写的方法定义语法都可以在类体中使用,这包括生成器方法(带*)和名字为方括号表达式的方法;

15. try catch

当编译运行程序出错的时候,编译器就会抛出异常。同时程序下面的代码不能正常执行,这对开发很不友好,但是有一种更合理的es6语法 try..catch,它会在捕捉到异常,同时不会打断代码执行,把出现的异常通过catch捕捉到,避免出现红色报错;

例如:

try{
    console.log(num);
    let num=`学前端的格斯`;
}catch(err){
    console.log(err);
}

这时候try当中就出现了暂时性死区,按理说会出现红色报错,但由于使用了try...catch,这时候的报错会被catch捕获到,由catch接收,所以会在catch内打印出具体的错误信息;

16. Object.is(value1,value2)

  • value1 : 被比较的第一个值。

  • value2 : 被比较的第二个值。

ES5 比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。JavaScript 缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。

ES6 提出“Same-value equality”(同值相等)算法,用来解决这个问题。Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。

Object.is('one','one');//true;
Object.is({},{});//false;

不同之处只有两个:一是+0不等于-0,二是NaN等于自身。

-0===+0; 👉true;
NaN===NaN; 👉false;

来Object.is()的表现:
Object.is(NaN,NAN); 👉true;
Object.is(+0,-0); 👉false;

所以,Object.is更符合我们正常人的思维模式,它解决了=====总是会存在一些历史遗留下来的常人难以理解的奇怪问题;

17. Object.assign(target,obj1,obj2,obj3,...)

基本用法

Object.assign()方法用于对象的合并,将源对象obj1``obj2``obj3的所有可枚举属性,复制到目标对象target上。

代码示例:

let target={
    name:`写前端的格斯`,
};
let obj1={
    aged:18
};
let obj2={
    sex:`man`
};
let new_obj = Object.assign(target,obj1,obj2);

这时候我们打印出来的new_obj的结果是:

new_obj={
    name:`写前端的格斯`,
    aged:18,
    sex:`man`
}

Object.assign()方法的第一个参数是目标对象,后面的参数都是源对象。

Object.assign()的返回值就是target;

注意:

  • 如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性;
  • Object.assign()方法实行的是浅拷贝,而不是深拷贝。
  • 如果源对象的属性名与target对象中属性名重复,那么源对象中的属性将会覆盖第掉target对象中对应的属性;

18. Object.setPrototypeOf()Object.getPrototypeOf()

我们先来看看MDN上关于__proto__的说明:

已废弃:  该特性已经从 Web 标准中删除,虽然一些浏览器目前仍然支持它,但也许会在未来的某个时间停止支持,请尽量不要使用该特性。

以上是MDN关于__proto__说明解释,已被废弃; 转而替代的设置和获取原型的API是Object.setPrototypeOf()Object.getPrototypeOf();

先来看看Object.setPrototypeOf();

Object.setPrototypeOf方法的作用与__proto__相同,用来设置一个对象的原型对象(prototype),返回参数对象本身。它是 ES6 正式推荐的设置原型对象的方法。

Object.setPrototypeOf(obj,my_proto);
👉将obj对象的__proto__设置成my_proto;

再来看看Object.getPrototypeOf();

该方法与Object.setPrototypeOf方法配套,用于读取一个对象的原型对象。

let my_proto=Object.getPrototypeOf(obj);
👉获取到obj的原型,返回值是obj的原型;

19. String.raw

这是一个字符串的方法,该方法返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,往往用于模板字符串的处理方法。

请看示例代码:

let str1 = String.raw`Hi\n\t,John!`
👉console.log(str1);
    打印结果是: "Hi\n\t,John!"

要注意的是: String.raw后面直接跟的``,不是()

20. 函数的name属性

这个属性早就被浏览器广泛支持,但是直到 ES6,才将其写入了标准;

需要注意的是,ES6 对这个属性的行为做出了一些修改。如果将一个匿名函数赋值给一个变量,ES5 的name属性,会返回空字符串,而 ES6 的name属性会返回实际的函数名。

21. Array.from( )

主要用于将伪数组转换成一个真数组;

来看示例代码:

let lis=document.querySelectorAll("li");
let arr = Array.from(lis);

获取到所有<li>标签的集合;这就是一个伪数组,表面上和数组无疑,但是它的__proto__指向的并不是Array;

这时候通过Array.from()方法,将lis__proto__更改为Array实现真数组的转变;

ES7(ES2016)

1. Array.prototype.includes( )

此方法数组原型上的方法,返回值是Boolean类型;

用于判断某个真数组中是否包含某个,例如:

let arr = [11,22,33,44,66];
arr.includes(55);//返回结果是false;
arr.includes(66);//返回结果是true;

2. String.prototype.includes( )

字符串的原型上同样也有includes方法,使用上和数组一致,返回值依然是Boolean类型;

3. Object.values( )

此方法是用于迭代一个对象中的私有属性值,刚好和Object.keys相呼应,来看代码:

let obj={
    name:'Mike',
    aged:19,
    sexual:'man'
};
Object.values(obj);👉'Mike' 19 'man'

4. Object.entries( )

此方法主要是讲一个普通对象转变成一个二维数组,请看代码:

let obj={
    name:'Mike',
    aged:19,
    sexual:'man'
};
Object.entries(obj);
返回值是👇
[    ['name', 'Mike'],
    ['aged', 19],
    ['sexual', 'man']
]

该方法可以搭配Map内置类一起使用,因为Map括号中要求传递的就是一个二维数组对象;

5. Object.fromEntries( )

首先我先看一个示例:

const entries = new Map([
  ['foo', 'bar'],
  ['baz', 42]
]);

const obj = Object.fromEntries(entries);

console.log(obj);
// expected output: Object { foo: "bar", baz: 42 }

首先Object.fromEntries()接收一个iterable(通常是二维数组),里面的每一个数组有两个值,分别对应的是一个键值对;最后Object.fromEntries()返回的是一个对象,通常是搭配Map内置类一起使用。

未完,待续...

工作忙,此文长期维护更新...