ES6小记

172 阅读11分钟

ES6语法小记

简略记录工作中常用的es6语法及其相关知识

var let const

  1. 变量提升
  • var声明的变量会变量提示,在定义前就可以使用,但是值为undefined
  • let与const声明的变量只有声明之后才能使用,否则会报错。
//var声明之前使用
console.log(name);//undefined
var name = "姓名"
//let声明之前使用
console.log(age);//Uncaught ReferenceError: age is not defined
let age = 18
//const同let一样
  1. 块级作用域。
  • 在es6之前,作用域只有全局作用域与函数作用域,es6新增块级作用域,块级作用域用{}包裹
//var声明的变量不会生成块级作用域
if(true){
    var name = "姓名"
}
console.log(name);//姓名
//let与const声明的变量会形成块级作用域,age只能在if里面使用
if(true){
    let age = 18
}
console.log(age);//ReferenceError: age is not defined
  • let const的块级作用域的应用场景。
//双重for循环计数器使用相同的变量名,会出问题
for(var i = 0;i<3;i++){
    for(var i = 0;i<3;i++){
        console.log(i)
    }
}
//只打印一次打印 0 1 2,这是因为var声明的变量在全局作用域下,当第一次内部循环结束之后i=3,不满足外部循环的i<3条件,既不会继续进行循环。
for(let i = 0;i<3;i++){
    for(let i = 0;i<3;i++){
        console.log(i)
    }
}
//打印3次 0 1 2 因为let声明的变量具有块级作用域,外部与内部循环定义的变量i不再统一作用域下,不会相互影响。虽然let可以解决上面的同一变量名的问题,但是还是不建议使用同名变量,不容易调试。
//另外for循环具有两层作用域,()里面是一个单独的作用域。{}里面是一个单独的作用域
  1. const与let的区别。
  • const声明的变量是一个常量,声明之后不能改变,这里的不能改变是值对应的内存地址不能再改变,也就是不能重新赋值,如果是一个对象的话,可以操作对象属性,但是不能重新赋值为一个新的对象。
  • 优先使用const,确定要改变的变量使用let,尽量不用var声明变量。

数组解构

平常获取数组元素需要通过下表获取再赋值给某一变量,使用数组解构赋值可以更加方便

//之前获取数组元素
const arr = ["张三",18,true];
const name =arr[0];
const age = arr[1];
const sex = arr[2];
//使用解构赋值
const arr = ["张三",18,true];
const [name,age,sex] = arr;
//数组的解构赋值需要注意顺序要一一对应,如果只想取特定位置的元素,则前面要留空,用,分割,比如获取最后一个元素
const [,,sex] = arr;
//数组解构也可以使用默认值,即数组中该位置没有数据,则会使用默认值。
const[,,,height = 180] = arr;
console.log(height);//180

对象解构

通过解构赋值快速得到对象的值。

const obj = {
    name:"张三",
    age:18
}
//变量名和对象属性名要一致
const {name,age} = obj;
console.log(name,age);//张三 18

//如果出现属性名和之前已有的变量名重复的情况,可以在解构的时候取一个别名。
const name = "李四";
const obj = {
    name:"张三",
    age:18
}
//:前面的是对象里面的属性名,:后面的是要赋值给的变量名。
const {name:objName,age} = obj;
console.log(objName,age);//张三 18

//对象解构赋值同样可以使用默认值
const obj = {
    name:"张三",
    age:18
}
//:前面的是对象里面的属性名,:后面的是要赋值给的变量名。
const {name:objName,age,sex='男'} = obj;
console.log(objName,age,sex);//张三 18 男

模板字符串

定义字符串一半使用单引号'或者是双引号"现在可以使用反单引号`。反单引号包括的字符串为模板字符串。模板字符串可以使用${}的方式获取变量值。

const name = "张三"
const str = `hi,${name}`
console.log(str);//hi,张三

字符串扩展方法。

  • includes判断字符串中是否包含某个字串,返回true或者false
  • startsWith判断字符串是否以某个字串开头,返回true或者false
  • endsWith判断字符串是否以某个字串结尾,返回true或者false
const str = "hello world"
console.log(str.includes("llo"));//true
console.log(str.startsWith("hello"));//true
console.log(str.endsWith("world"));//true

参数默认值

可以给不一定会传入的参数定义一个默认值,在没有传入的时候使用。

function sayHi(word,name){
    word = word||"hello"
    console.log(word,name)
};
//一般我们使用上面的||逻辑或在处理可能没有传入的情况,给他一个默认值。
//但是他是不严格的。没有传入的话,word应该是undefined,如果调用sayHi(false,"张三"),实际传入了false,
//他认为没有传入。正确的应该判断word是否严格等于undefined
function sayHi(word,name){
    word = word === 'undefined' ? "hello" : word
    console.log(word,name)
};
//形参默认值的写法,要把有默认值的写着最后,不然不会生效。
function sayHi(name,word=undefined){
    console.log(word,name)
}

剩余参数

在函数种可以通过arguments获取参入函数的所有参数,他是一个类数组对象,如果要对他进行数组操作,则要线转换为数组,在进行操作。使用剩余参数的写法,可以直接把所有参数转换为数组格式,也可以指定接受剩下的所有参数。 注意:剩余参数只能放在参数末尾。

//写一个求所有传入的数字的和的函数,arguments对象获取参数,
function add(){
    //转换arguments为数组。
    arguments = Array.from(arguments);
    let result = arguments.reduce((prev,current)=>prev+current,0)
    return result
}
console.log(add(1,3,4));//8
//直接使用剩余参数写法
function add(...args){
    //这里的args就是传入的所有参数的数组
    let result = args.reduce((prev,current)=>prev+current,0)
    return result
}
console.log(add(1,3,4));//8

//如果要获取除了第一个参数之外的所有参数的和,使用arguments的话
//要先将其转换为数组,然后再截取数组。之后在对截取之后的数组进行求和操作
//但是使用剩余参数,则只需要先定义一个形参接受第一个参数,把剩余参数以...收集起来进行求和即可
function add(a,...rest){
    //这里的rest就是传入的除了第一个参数外的所有参数的数组
    let result = rest.reduce((prev,current)=>prev+current,0)
    return result
}
console.log(add(1,3,4));//7

展开数组

...运算符也可以用于展开数组,比如call的传参,可以把数组的每一项当做独立的参数传入

    function sum(a,b){
        console.log("姓名:"+this.name,"薪资:"+(a+b))
    }
    const salary = [1000,2000];
    const person = {
        name:"张三"
    }
    sum.call(person,...salary);//姓名:张三 薪资:3000

箭头函数

函数的简写方式,已经一下其他特性。

//普通的方式定义一个函数,
const add = function(a,b){
    return a+b
}
//使用箭头函数的形式,箭头左边的是函数形参,右边是函数体
const add = (a,b)=>{
    return a+b
}
//如果函数体只有一句代码,且返回值就是这一句代码的结果,那么可以省略{}与return
const add = (a,b)=> a+b
//如果函数的形参只有一个,那么可以省略()
const add = a => a*a;
//实际应用,筛选出数组种的偶数项
const arr = [1,2,3,4,5,6];
arr.filter(item=>item % 2)

箭头函数没有自己的this,他里面的this是定义时所在的上层函数中的this。如

const person = {
    age:18,
    sayAge:function(){
        console.log(this.age)
    }
}
person.sayAge();//18
//箭头函数改写
const person = {
    age:18,
    sayAge:()=>{
        console.log(this,this.age)
    }
}
//因为箭头函数中的this是他定义时确定的,值为上层函数的this,这里他的上层是全局。所以this为window对象。
person.sayAge();//window对象,undefined

可以用箭头函数来处理一些需要this的地方。如经常在代码中看到_this或that这种方式来存储this的情况

    const person = {
    age:18,
    sayAge:function(){
        setTimeout(function(){
            console.log(this.age)
        },1000)
    }
    }
    person.sayAge();//undefined
    //定时器里面的函数会被放到异步任务队列,执行时所在的环境为全局。此时的age为undefined
    
    //可以在sayAge调用时,接受一些this,然后再定时器里面的函数使用(闭包)
    const person = {
    age:18,
    sayAge:function(){
        let that = this
        setTimeout(function(){
            console.log(that.age)
        },1000)
    }
    }
    person.sayAge();//18
    //也可以使用箭头函数,让定时器中的函数的this直接指向外层的sayAge的this
    const person = {
    age:18,
    sayAge:function(){
        setTimeout(()=>{
            console.log(this.age)
        },1000)
    }
    }
    person.sayAge();//18

对象字面量的增强

对象的简写方式

//再es5中,定义一个对象
let age = 18;
const person = {
    name:"张三",
    age:age,
    sayName:function(){
        console.log(this.name)
    }
}
//如果需要给对象一个动态属性,则只有定义对象之后再根据下标来给对象添加
person[Math.random()] = "随机属性名";

//es6中的简写方法,属性名和变量名相同可简写,对象方法可省略:function,可以直接定义动态属性
let age = 18
const person = {
    name:"张三",
    age,
    sayName(){
        console.log(this.name)
    },
    [Math.random()]:"随机属性名"
}

Object.assign()可以讲源对象的属性全部复制给目标对象,最后返回目标对象。如果源对象存在与目标对象同名属性,则源对象属性会覆盖目标对象的同名属性。

const source1 = {
    a:1,
    b:1
}
const source2 = {
    a:2,
    c:2
}
const target = {
    a:0,
    b:0
}
const newObj = Object.assign(target,source1,source2)
console.log(target);//{a: 2, b: 1, c: 2}
console.log(newObj === target);//true

可以利用这个特性来对对象进行浅拷贝操作。前提是目标对象要是和拷贝对象无关的一个对象

const oldObj = {
    name:"张三"
}
const newObj = Object.assign({},oldObj);
console.log(oldObj,newObj,oldObj === newObj);//{name: '张三'} {name: '张三'} false
//虽然看起来两者一致,但是他们不是同一个对象,不会相互影响。

Object.is用于判断数据是否相等。 平时判断数据是否相等用==或者===等号。==等号比较时会自动进行类型转换之后进行比较。===不会类型转换只有当数据和类型一致才会返回true,但是+0与-0则被认为是相等的。NaN不等于NaN。Object.is()可以来处理这两个特殊情况。

console.log('1' == 1);//true
console.log('1' === 1);//false
console.log(+0 === -0);//true
console.log(NaN === NaN);//false
console.log(Object.is(+0,-0));//false
console.log(Object.is(NaN,NaN));//true

Proxy

proxy可以对对象属性的读写设置代理,vue3的数据双向绑定就用到了proxy,vue2中用的是Object.defineProperty()。

//Proxy接受两个参数,第一个参数是要代理的目标对象,第二个参数是代理处理对象。
const person = {
    name:"张三",
    age:18
}
const proxyPerson = new Proxy(person,{
    //get方法用于获取对象属性时的处理逻辑,接受两个参数,代理目标对象与访问的属性
    //除了返回属性值之外,可以进行一些其他的逻辑处理。
    get(target,property){
        //看目标对象是否存在该属性,存在则返回该目标属性,不存在则返回一个默认值。
        return property in target ? target[property]:'defaultValue'
    },
    //set方法为对象属性设置一个属性值,接受三个参数,分别是目标对象,对象属性,已经接受到的属性值。
    set(target,property,value){
       //可以做一些设置值额外的操作,比如限制类型必须为数字。
       if(!Number.isInteger(value)){
           throw Error(`${value} in not a Number`)
       }
       else{
           target[property] = value
       }

    }
})
console.log(proxyPerson.name);//张三
console.log(proxyPerson.other);//defaultValue
//proxyPerson.height = "字符串";//Uncaught Error: 字符串 in not a Number
proxyPerson.height = 180;
console.log(proxyPerson.height);//180

proxy比defineProperty更加强大,defineProperty只能监听对象的读写操作,proxy可以监听更多对象属性操作,如delete in。proxy也可以直接代理数组对象。不需要重写数组方法。

const arr = [];
const proxyArr = new Proxy(arr,{
    set(target,property,value){
        console.log("监听到了数组变化")
        target[property] = value;
        //表示编辑完成
        return true
    }
})
proxyArr.push(0)

Reflect

Reflect是一个静态类,封装了一系列底层的对对象的操作Reflect内部的方法就是proxy内部方法的默认实现

//没有定义get方法相当于下面的代码
const proxyObj = new Proxy(obj,{
    get(target,property){
        return Reflect.get(target,property)
    }
})
//也就是说,我们在使用代理对象时,应该在上面写自己添加的逻辑,最后调用相应的Reflect方法。

作用:提供了一套统一的操作对象的Api

const obj = {
    name:"张三",
    age:18
}
//对对象的一些操作
console.log(name in obj)
delete obj.name;
Object.keys(obj);

//现在可以统一使用Reflect的内置方法
Reflect.has(obj,'name');
Reflect.deleteProperty(obj,'name')
Reflect.ownKeys(obj)

Promise

异步编程的一种方案。见Promise从入门开始手写

class

es6之前,使用构造函数加原型的方式实现继承关系,es6新增class,更加方便的实现继承关系。

//ES6之前,定义一个Person构造函数。
function Person(name){
    this.name = name;
}
//给实例对象添加方法。可以通过给Person函数的原型对象添加公用方法
Person.prototype.say = function(){
    console.log(`My name is ${this.name}`)
}
const person = new Person("张三");
person.say()

//使用class定义一个构造函数

class Person{
    //在构造器里面初始化实例对象
    constructor(name){
        this.name = name
    }
    //直接添加实例方法
    say(){
        console.log(`My name is ${this.name}`)
    }
}
//同样使用new关键字生成一个实例对象
const person = new Person("张三")
person.say();

class有静态方法,可以直接通过类名访问,使用static关键字标识。

class Person{
    constructor(name){
        this.name = name
    }
    say(){
        console.log(`My name is ${this.name}`)
    }
    //定义一个静态方法
    static create(name){
        return new Person(name)
    }
}
//直接通过类名访问静态方法
const person = Person.create("张三")
person.say();

extends关键字实现继承

class Person{
    constructor(name){
        this.name = name
    }
    say(){
        console.log(`My name is ${this.name}`)
    }
    static create(name){
        return new Person(name)
    }
}
//定义一个student类继承person类
class Student extends Person{
    constructor(name,number){
        //必须在子类中利用super调用父类的构造方法
        super(name);
        //子类自己的初始化逻辑
        this.number = number;
    }
    sayNumber(){
        //可以利用super来调用父类的方法
        super.say();
        console.log(`My number is ${this.number}`)
    }
}
const student = new Student("张三",1233);
student.sayNumber()
//父类的静态方法依然可以通过子类的类名访问
console.log(Student.create("xxxx"))

set

set是一种没有重复元素的数据的集合

//通过new关键字创建set集合
const set = new Set()
//通过add方法为集合添加元素,返回的是集合本身,所以可以链式调用,多次添加
set.add(1).add(2).add(2).add(3);
console.log(set);//Set(3) {1, 2, 3}
//通过has方法判断集合中是否包含某元素,返回true或false
console.log(set.has(100));//false
//通过size属性获取集合元素个数,类似数组的length
console.log(set.size);//3
//通过delete方法删除集合中某一元素
set.delete(2);
console.log(set);//Set(2) {1, 3}
//通过clear方法情况集合
set.clear()
console.log(set);//Set(0) {size: 0}
//可以使用forEach for of来遍历set集合
set.forEach(i=>console.log(i))
for(let i of set){
    console.log(i)
}
//通常可以同set结构来为数组去重
let arr = [1,2,3,3];
arr = [...new Set(arr)];
console.log(arr);//(3) [1, 2, 3]

map

map是与对象类似的键值对的集合,不同的是对象的键只能是字符串,但是map的键可以使任意类型的

let person = {}
//用数字作为键
person[123] = 123
//用boolean值作为键
person[true] = true
//用对象作为键
person[{name:"zs"}] = "张三"
//获取person对象的所有健名,得到的都是字符串
console.log(Object.keys(person));//(3) ['123', 'true', '[object Object]']

//用new关键字创建一个map
let map = new Map()
//通过set方法为map设置键值对
map.set(true,true);
let person = {name:"zc"}
map.set(person,"zc")
console.log(map);//Map(2) {true => true, {…} => 'zc'}
//通过has方法确定是否含有某个键
console.log(map.has(true));//true
//通过delete方法删除某个键
map.delete(true)
console.log(map);//Map(1) {{…} => 'zc'}
//通过clear方法清空map
map.clear()
console.log(map);//Map(0) {size: 0}

Symbol

Symbol是一种基本数据类型,用于创建一个独一无二的值,主要用于放在对象属性重名

//加入有两个文件给同一对象取了相同的属性名,那么后面的会覆盖前面的
let obj = {}
//a.js___________________
obj.name = "张三"
//b.js__________________
obj.name = "李四"
console.log(obj);//{name: '李四'}
//可以通过约定取名方式规避这种问题,如
//a.js___________________
obj.a_name = "张三"
//b.js__________________
obj.b_name = "李四"
console.log(obj);//{name: '李四', a_name: '张三', b_name: '李四'}
//symbol的用发
//使用Symbol函数创建一个symbol
let symbol1 = Symbol()
console.log(symbol1);//Symbol()
//每一个symbol都是不一样的
console.log(Symbol()===Symbol());//
//可以给Symbol传一个标识参数,方便调试
console.log(Symbol('foo'),Symbol('bar'));//Symbol(foo) Symbol(bar)
//用Symbol作为对象的属性名
let name = Symbol()
let person = {
    [name]:"张三",
    say(){
        console.log(this[name])
    }
}
//通过这种方式来实现访问symbol属性,实现私有属性的访问。
person.say();//张三
//要创建两个相同的Symbol,可以使用Symbol.for
console.log(Symbol.for('foo') === Symbol.for('foo'));//true

for of

for of一种新的遍历方式,可以遍历任意可迭代对象。普通对象不是可迭代对象,不可以直接用for of变量,数组,类数组,set,map结构都可以直接使用for of 来进行遍历。

    //遍历数组
    let arr = [1,3,4];
    for(const item of arr){
        console.log(item);//1 3 4
    }
    //遍历set结构
    let set = new Set(arr);
    for(const item of set){
        console.log(item)//1 3 4
    }
    //变量map结构
    let map = new Map();
    map.set("1",1);
    map.set("2",2);
    for(const item of map){
        console.log(item);// ['1', 1]  ['2', 2]
    }
    //map结构使用for of获得的是一个[key,vlaue]数组,可以使用解构来获取key与value
    for(const [key,value] of map){
        console.log(key,value);//1 1 2 2
    }

可迭代接口

任何实现了可迭代接口的数据解构都可以使用for of来进行遍历。for of的本质就是调用了数据结构内部的Symbol.iterator方法。

//以set结构为例
let set = new Set([1,2,3]);
//直接调用set的Symbol.iterator方法。该方法返回一个迭代器,该迭代器中含有一个next方法
const iterator = set[Symbol.iterator]();
//调用迭代器的next方法会返回一个对象,该对象包括当前值以及是否遍历完成的标识。
console.log(iterator.next());//{value: 1, done: false}
console.log(iterator.next());//{value: 2, done: false}
console.log(iterator.next());//{value: 3, done: false}
console.log(iterator.next());//{value: undefined, done: true}
console.log(iterator.next());//{value: undefined, done: true}

是对象变得可迭代,就需要给对象添加一个可迭代接口,也就是给对象添加一个Symbol.iterator方法。

const obj = {
    //为对象添加Symbol.iterator方法
    [Symbol.iterator]:function(){
        //该方法返回一个迭代器对象
        return {
            //迭代器对象中有一个next方法
            next:function(){
                //调用next方法会返回迭代结果对象
                return {
                    //该对象包含当前迭代结果value,以及是否迭代完成的done
                    value:"测试数据",
                    done:true
                }
            }
        }
    }
}
for(const item of obj){
    console.log("迭代")
}
//执行上述代码,发现没有任何效果,但是也不会报错,因为我们上面只是思路分析,把next方法的返回值写死了,第一次的done就是true,for of以为迭代完成,不会进入迭代。改写如下。
const obj = {
    name:"张三",
    age:18,
    [Symbol.iterator]:function(){
        //要迭代的是obj,首先调用Symbol.iterator方法时this的值就是obj
        //假设我们的迭代器要实现的时获取obj的所有键值。
        const values = Object.values(this);
        //每一次迭代对应当前迭代次数的值,定义一下递增的下标
        let index = 0;
        return {
            next:function(){
                //next方法返回的结果
               const result = {
                   //value值为当前迭代次数对应的键值。
                   value:values[index],
                   //当index>=values的长度时,说明迭代完成
                   done:index>=values.length
               }
               //没完成一次迭代index+1
               index++
               //最后返回迭代结果
               return result
            }
        }
    }
}
for(const item of obj){
    console.log(item);//张三 18
}

生成器函数

生成器函数是一种异步任务嵌套过深的优化方案,一半和yield配合使用,可以控制代码的执行,调用生成器函数会生成一个生成器对象,调用生成器对象的next方法,函数体里面的内容才会开始执行,碰到yield之后就会暂停执行,且返回一个结构对象,与迭代器的next方法一样,该对象包含两个属性,value与done,value的值为yield后面的值,done则为true或者false。

//声明一个生成器函数,只需要在函数声明与函数名之间加一个*
function* gen(){
    console.log("生成器函数执行了")
}
const g = gen();
console.log(g);//gen {<suspended>}
//只有调用了next方法,函数体才会开始执行
g.next();//生成器函数执行了  {value: undefined, done: true}
//__________________
function * gen(){
    console.log(100);
    yield 100;
    console.log(200);
    yield 200;
    console.log(300);
    yield 300;
}
const g = gen();
//每调用一次next方法,就会继续向下执行,直到碰到下一个yield命令,暂停执行,并返回yield后面的值。直到最后返回done为true
console.log(g.next());//100 {value: 100, done: false}
console.log(g.next());//200 {value: 200, done: false}
console.log(g.next());//300 {value: 300, done: false}
console.log(g.next());//{value: undefined, done: true}