ES6新特性速览

虽然ES6现在基本都是前端必会内容了,网上也一堆文章了,但有些初学者可能看完还是记不住,所以便写一篇文章快速过一下ES6的一些新特性,加深印象。如果你还没有学ES6,还是推荐你阅读一下阮一峰的ES6入门指南
☀️如果觉得文章不错,欢迎点赞❤️、关注🌟、收藏📄!

🚀ES6的新特性,大致可以归为4大类:

  • 解决原有语法上的一些问题或者不足
  • 对原有语法进行增强
  • 全新的对象、全新的方法、全新的功能
  • 全新的数据类型和数据结构(symbol、set、map、etc.)

块级作用域

作用域——某个成员能够起作用的范围,在ES2015之前,只有两种作用域:全局作用域函数作用域。在ES2015中新增:块级作用域.

//var 会变量提升,所以值会是undefined
console.log(a);//undefined
var a=1;

//let 不会进行变量提升
console.log(b)//Cannot access 'b' before initialization
let b=2;

//块级作用域
{
  let c=20;
}
console.log(c) //c is not defined

//const 申明时候必须赋值,且声明之后不可修改值,但可以修改对象中的某个值,
//const 和let一样,也存在块级作用域
const a={name:'lonjin'};
a.name='tom';
console.log(a)//{ name: 'tom' }
  • 经典面试题:
for(var i=1;i<6;i++){
    setTimeout(() => {
        console.log(i)
    }, 0);
}
// 6 6 6 6 6

for(let i=1;i<6;i++){
   setTimeout(() => {
    console.log(i)
   }, 0);
}
// 1 2 3 4 5

数组的解构

  • 基本使用
const arr=[1,2,3,4,5];
const [a, b, c] = arr;
console.log(a,b,c) //1 2 3
  • 只获取数组中第三个成员,方法,前面两个用逗号隔开,只声明第三个
const arr=[1,2,3];
const [,,c]=arr;
console.log(c) //3
  • 提取数组当前位置开始的所有成员(注意:三个点的用法只能在解构成员的最后使用)
const arr=[1,2,3,4,5];
const [a,...arr2]=arr;
console.log(arr2) //[ 2, 3, 4, 5 ]
  • 可以给提取到的成员设置默认值,如果没有提取到值,就会赋为默认值
const arr=[1,2,3,4,5];
const [a,b,c,d,e,f=6]=arr;
console.log(f) //6

对象的解构

  • 基本使用(和数组解构基本相同)
const data={name:"lonjin",age:18};
const {name}=data;
console.log(name) //lonjin
  • 如果解构过程中遇到命名重复,可使用重命名的方式解决;设置默认值方法如下:
const data={name:"lonjin",age:18};
const name='tom';
const {name:newName}=data;
console.log(newName) //lonjin
  • 同样也可以设置默认值
const data={name:"lonjin",age:18};
const name='tom';
const {name:newName,say='hello'}=data;
console.log(newName+','+say) //lonjin,hello
  • 解构常用方法,如console.log
const {log}=console;
log(1)//1

模版字符串

  • 反引号定义字符串,若字符串中需要有反引号,可以用\反斜杠转义

  • 支持字符串中直接换行

  • 支持插值表达式写法 `hello, ${name}

const name='lonjin';
const text=`hello ${name} \nuser age ${17+1}`;
console.log(text) 
/*
hello lonjin 
user age 18
*/

带标签的模板字符串

  • 标签模板字符串的作用:是对我们的模板字符串进行加工处理,然后返回新内容
const name='lonjin',
    sex=true,
    age=18;

function fn(strings,...args){
    //string为text中普通string数组
    console.log(strings)
    //[ 'my name is ', ',my age', ',my sex', '' ]
    //args为插值表达式中的值
    console.log(args)
    // [ 'lonjin', 18, true ]

    //可以处理完数据return出去
    let sex=args[2]?'man':woman;
    return strings[0]+args[0]+strings[1]+args[1]+strings[2]+sex
}

const text=fn`my name is ${name}my age${age}my sex ${sex}`;

console.log(text)
//my name is lonjinmy age18my sex man

字符串的扩展方法

  • includes():判断一个字符串是否包含在另一个字符串中,返回true或者false
const str='erro type end.';
let f1=str.includes('erro');
console.log(f1)//true
  • startsWith():判断当前字符串是否以另外一个给定的子字符串开头,返回true或者false
const str='erro type end.';
let f2=str.startsWith('erro');
console.log(f2) //true
  • endsWith:判断当前字符串是否以另外一个给定的子字符串结尾,返回true或者false
const str='erro type end.';
let f3=str.endsWith('.');
console.log(f3) //true

参数默认值

  • 函数参数默认值
function fn(status=false){
    console.log(status)
}
fn(true)
fn()
/*
true
false
*/

剩余参数

  • 只能出现在最后,且只能使用一次
function fn(...args){
    console.log(args)
};
fn(1,2,3,4,5)
//[ 1, 2, 3, 4, 5 ]

展开数组

  • 想依次打印数组的值
let arr=['a','b','c'];
console.log(...arr);
//a b c

箭头函数

  • 基本使用
//普通函数
function fn(num){
    return num+1
};
console.log(fn(2)) //3

//箭头函数
const fn2=n=>n+1;
console.log(fn2(2))//3

箭头函数的this

  • 箭头函数不会改变this指向,也就是说在箭头函数的外面拿到的this是什么,在箭头函数内部拿到的this也就是什么。
const name='tom'

const people={
    name:'lonjin',
    say(){
        console.log(`this name is ${this.name}`)
    },
    say2:()=>{
        console.log(`this name is ${this.name}`)
    }
}

people.say();//this name is lonjin
people.say2()//this name is undefined

对象的扩展

对象字面量的增强

  • 对象中属性名和属性值相同就可以缩写

  • 函数表达式也可以缩写

  • 动态属性名,可以直接使用计算属性名,使用方括号的形式使用

const username='lonjin';

const people={
    age:18,
    username,
    [Math.random()]:123,
}

console.log(people)
//{ age: 18, username: 'lonjin', '0.3692996732470484': 123 }

Object.assign

  • 将多个源对象中的属性复制到一个目标对象中;第一个参数为目标对象,如果有相同的,后面的会覆盖之前的值。
let obj={
    a:123,
    b:456,
}
let obj2={
    a:789,
    c:234
}
let obj3={
    d:123,
    c:678
}
// const n=Object.assign(obj,obj2)
//console.log(n)//{ a: 789, b: 456, c: 234 }

const m=Object.assign(obj,obj2,obj3);
console.log(m)
//{ a: 789, b: 456, c: 678, d: 123 }

Object.is

  • 判断两个对象是否相等,和严格相等运算符(===)类似,但有两个值他们判断起来不太一样:+0-0NaN
console.log(
    //0==false //true
    //+0 === -0 // true
    // NaN === NaN  //false
    //Object.is(NaN,NaN) //true
    Object.is(+0,-0) //false
)

Proxy

  • 代理对象:为对象设置访问代理器
const people={
    name:"lonjin",
    age:18,
}

let peopleProxy=new Proxy(people,{
    //监听读取
    get(target,property){
        // target为对象
        // property为要读取的键
        console.log(target,property)
        //{ name: 'lonjin', age: 18 } name
        //返回读取的内容
        return property in target?target[property]:'none'
    },
    //监听属性设置
    set(target,property,value){
        /*target---目标对象
          property---要设置的键名
          value---要设置的新值
        */
       if(property=='age'){
           if(!Number.isInteger(value)){
               throw new TypeError('Value must be a number')
           }
       }
       target[property]=value
    }
})

console.log(peopleProxy.name)//lonjin
console.log(peopleProxy.say)//none
// peopleProxy.age='28';//throw new TypeError('Value must be a number')
peopleProxy.age=20;

defineProperty和Proxy的区别

  • defineProperty只能监视属性的读写,Proxy能够见识到更多对象操作。比如属性的删除:
const people={
    name:"lonjin",
    age:18,
};
let peopleProxy=new Proxy(people,{
   deleteProperty(target,property){
       //target 为代理对象
       //property 为要删除的键名
       delete target[property]
   }
});
delete peopleProxy.age;
console.log(peopleProxy)
//{ name: 'lonjin' }
  • Proxy更好的支持数组对象的监视,vue3中就采用了Proxy来实现数据监听
//监听数组push操作
let arr=[];

let newArray=new Proxy(arr,{
    set(target,property,value){
        target[property]=value;
        return true
    }
});
newArray.push(1);
newArray.push(2);
console.log(newArray)
//[1,2]

Reflect

  • Reflect成员方法就是Proxy处理对象的默认实现,Reflect并非一个构造函数,所以不能通过new()来调用。
let people={
    name:'lonjin',
    say:'hello',
}

let peopleProxy=new Proxy(people,{
    //如果我们没有定义get方法,那么Proxy会默认一个get方法如下,返回Reflect
    get(target,property){
        return Reflect.get(target,property)
    }
})
  • Reflect最大作用是提供了一套完整的对象调用方法
let people={
    name:'lonjin',
    say:'hello',
}

console.log('name' in people)
console.log(Reflect.has(people,'name'))

console.log(delete people['say']);
console.log(Reflect.deleteProperty(people,'say'));

console.log(Object.keys(people));
console.log(Reflect.ownKeys(people));
  • Reflect一共有13个静态方法,更多查看MDN-Reflect

Class

  • 基本使用
class People{
    constructor(name){
        this.name=name
    }
    say(){
        console.log(`this name ${this.name}`)
    }
};
let user=new People('lonjin');
user.say()
//this name lonjin

静态方法

  • 静态方法可以直接通过类型本身去调用,而实例方法需要通过这个类型构造的实例对象去调用,只需要在方法前面加static
    constructor(name){
        this.name=name
    }
    say(){
        console.log(`this name ${this.name}`)
    }

    static created(name){
        return new People(name)
    }
};
let user=People.created('lonjin');
user.say()
//this name lonjin

class的继承

  • class继承只需要用关键词extends实现,super为父类构造函数
class People{
    constructor(name){
        this.name=name
    }
    say(){
        console.log(`this name ${this.name}`)
    }
};

class Child extends People{
    constructor(name,age){
        super(name);
        this.age=age;
    }
    message(){
        console.log(`my name is${this.name},my age is ${this.age}`)
    }
};

let tom=new Child('tom',18);
tom.message();
//static

Set数据结构

  • set的内部成员是不允许重复的,也就是每一个值在set中都是唯一的。我们平时可以利用Set来实现数组去重
//基本使用
let setArray=new Set();
setArray.add(1);
setArray.add(2);
setArray.add(3);
setArray.add(4);
setArray.add(4);
console.log(setArray)
//Set(4) { 1, 2, 3, 4 }

//遍历
setArray.forEach(i=>{console.log(i)});
//1 2 3 4

//获取数量
console.log(setArray.size) //4

//删除(返回true或者false)
console.log(setArray.delete(1))//true

//判断是否有某个值(返回true或者false)
console.log(setArray.has(3))//true

//清空
setArray.clear();

数组去重

  • Array.from+set
//方法1
let arr=[1,2,3,4,5,5,6];
let newArr=Array.from(new Set(arr));
console.log(newArr)
//[ 1, 2, 3, 4, 5, 6 ]
  • 展开运算符
let arr=[1,2,3,4,5,5,6];
console.log([...new Set(arr)]);
//[ 1, 2, 3, 4, 5, 6 ]

Map

  • js的对象本质上是键值对的集合,但是传统上只能用字符串当作键。所以ES6提供了Map数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。与对象最大的区别就是可以使用任意类型作为键,而对象只能使用字符串作为键
let m=new Map();

let n={name:'tom'};

// 设置值
m.set(n,18)

//读取值
console.log(m.get(n))//18

//判断是否有某个值
// m.has();

//删除
// m.delete()

//清空
// m.clear()

//遍历
m.forEach((val,key)=>{
    console.log(val,key)
})

Symbol

  • 一种全新的原始数据类型, 表示一个独一无二的值。主要解决对象中属性名冲突的问题
//创建一个独一无二的属性名,且外部无法访问到(模拟私有成员)
let name=Symbol();
let people={
    [name]:'lonjin',
    say(){
        console.log(this[name])
    }
};
people.say()//lonjin
  • 如果我们希望重新使用同一个Symbol值,Symbol.for()方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局
//全局注册表
let n1=Symbol.for('name');
let n2=Symbol.for('name');
console.log(n1===n2)//true
  • 获取对象中的键时候,如果使用for in,就获取不到Symbol类型的键;必须使用Object.getOwnPropertySymbols()进行获取。
let people={
    [Symbol()]:'tom',
    age:18
}

console.log(Object.keys(people))
console.log(JSON.stringify(people))
console.log(Object.getOwnPropertySymbols(people))
/*
[ 'age' ]
{"age":18}
[ Symbol() ]
*/

for of方法

  • ES6借鉴其他语言,引入了for...of循环,作为遍历所有数据结构的统一的方法。

遍历数组

let arr=[1,2,3,4,5];

for(const item of arr){
    console.log(item)
}
//1 2 3 4 5

可以使用break进行退出

//可以使用break进行退出
let arr=[1,2,3,4,5];
for(const item of arr){
    console.log(item)
    if(item>3){
        break
    }
}
//1 2 3 4 

遍历Set

let n=new Set([1,2,3,4]);
for(const item of n){
    console.log(item)
}
//1 2 3 4

遍历Map可以配合数组结构语法,直接获取键值

let n=new Map();
n.set('name','tom');
n.set('age',18);

for(const [key,value] of n){
    console.log(key+'----'+value)
}
/*
name----tom
age----18
*/

可迭代接口

  • ES6提供了Iterable接口,实现了Iterable接口就是for...of的前提
let arr=new Set([1,2,3,4,5]);

const iterator=arr[Symbol.iterator]();

console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())

/*
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: 4, done: false }
{ value: 5, done: false }
{ value: undefined, done: true }

*/

实现可迭代接口

  • 由于for...of方法不能便利普通对象,我们可以手动实现一下
const obj = {
    store: ['foo', 'bar', 'baz'],
  
    [Symbol.iterator]: function () {
      let index = 0
      const self = this
  
      return {
        next: function () {
          const result = {
            value: self.store[index],
            done: index >= self.store.length
          }
          index++
          return result
        }
      }
    }
  }
  
for(const item of obj){
    console.log(item)
}
//foo bar baz

迭代器模式

  • 开发一个任务清单应用
// 迭代器设计模式
const todos = {
  life: ['吃饭', '睡觉', '打豆豆'],
  learn: ['语文', '数学', '外语'],
  work: ['喝茶'],

  // 提供统一遍历访问接口
  each: function (callback) {
    const all = [].concat(this.life, this.learn, this.work)
    for (const item of all) {
      callback(item)
    }
  },

  // 提供迭代器(ES2015 统一遍历访问接口)
  [Symbol.iterator]: function () {
    const all = [...this.life, ...this.learn, ...this.work]
    let index = 0
    return {
      next: function () {
        return {
          value: all[index],
          done: index++ >= all.length
        }
      }
    }
  }
}

Generator

  • Generator函数是ES6提供的一种异步编程解决方案,来避免异步编程中回调嵌套过深。

基本使用

function * fn(){
    console.log('1')
    yield 100
    console.log('2')
    yield 200
    console.log('3')
    yield 300
}

const f=fn();

console.log(f.next())
console.log(f.next())
console.log(f.next())
console.log(f.next())

/*
1
{ value: 100, done: false }
2
{ value: 200, done: false }
3
{ value: 300, done: false }
{ value: undefined, done: true }
*/
  • 使用 Generator 函数实现 iterator 方法
const todos = {
  life: ['吃饭', '睡觉', '打豆豆'],
  learn: ['语文', '数学', '外语'],
  work: ['喝茶'],
  [Symbol.iterator]: function * () {
    const all = [...this.life, ...this.learn, ...this.work]
    for (const item of all) {
      yield item
    }
  }
}

for (const item of todos) {
  console.log(item)
}