ES6基础

93 阅读8分钟

let与const详解

let

变量声明:let、var、直接使用(在window对象上创建了一个属性,会污染全局环境)
let和var的主要区别:let声明的变量只在当前(块级)作用域内有效。let声明的变量不能被重复声明。不存在变量提升(不会被预解析)。
ES6之前的作用域:全局作用域、函数作用域、eval作用域。
块级作用域:一对花括号中的区域{},块级作用域可以嵌套,声明对象的花括号不是块级作用域。

{
    let a = 1;
    {
        console.log(a);//1
        let b = 2;
    }
    console.log(b);//报错
}
for(let i = 0; i < 3; i++){
    console.log(i);//0 1 2
}
console.log(i);//报错

暂存死区
let name = 'gg';
{
    console.log(name);//报错
    let name = 'mm';
}
console.log(name);

报错原因:由于当前块级作用域中存在该变量,所以不能向上层查询,而且let没有预解析,查询不到当前作用域的变量,所以会报错,这就是暂存死区。

const

使用const:和声明变量一样,只是关键字的区别。常量必须在声明的时候赋值,且不能修改。
与let类似的特性:不能重复声明、不存在提升、只在当前(块级)作用域内有效。

常量为引用类型的时候可以修改该引用类型

const mm = {
    age:20,
    name:xm
}
console.log(mm);//{age:20,name:xm}
mm.age = 30;
console.log(mm);//{age:30,name:xm}
mm = {};//报错

const只能保证声明的引用类型(数组、对象、函数)的地址不变,不能保证里面的值不变。

那么怎么防止常量为引用类型的时候能被修改的问题出现?Object.freeze()

const mm = {
    age:20,
    name:xm
}
Object.freeze(mm);
console.log(mm);//{age:20,name:xm}
mm.age = 30;
console.log(mm);//{age:20,name:xm}


ES6之前怎么声明常量?
Object.defineProperty()可以在为对象添加属性的基础上加入限制条件(可枚举、可修改)

var CST = {};
Object.defineProperty(CST,'name',{
    value:'mm',//属性的值
    writable:false//只读
})

Object.seal()表示不可以添加引用类型中的属性,但是可以修改已有的属性,需要配合Object.defineProperty()来实现ES6中的Object.freeze()的效果。

var CST = {a:1};
Object.defineProperty(CST,'a',{
    writable:false//只读
})
Object.seal(CST);

用上面两个函数实现Object.freeze()的功能

Object.defineProperty(Object,'freezePolyfill',{
    value: function(obj){
        var i;
        for(i in obj){
            if(obj.hasOwnProperty(i)){//判断属性是不是对象自身的属性
                if(obj[i] instanceof Object){
                    Object.freezePolyfill(obj[i]);//如果属性值中有对象则使用该函数递归
                }else{
                    Object.defineProperty(obj,i,{
                        writable:false
                    });
                }
            }
        }
        Object.seal(obj);
    }
})

解构赋值

数组的解构赋值

const arr = [1,2,3,4];
let [a,b,c,d] = arr;

更复杂的匹配规则

const arr = ['a','b',['c','d',['e','f','g']]];
const [ , , [ , , [ , , g ]]] = arr;

扩展运算符

const arr1 = [1,2,3];
const arr2 = ['a','b'];
const arr3 = [1,'x'];
const arr4 = [...arr1, ...arr2, ...arr3];//[1,2,3,'a','b',1,'x']

const arr = [1,2,3,4,5,6];
const [a,b,...c] = arr;//c = [3,4,5,6]

默认值(只有是undefined的时候才会选择默认值)

const arr = [1,null,undefined];
const [a,b=2,c,d='x'] = arr;//[1,null,undefined,'x']

交换变量

let a = 1;
let b = 2;
[a,b] = [b,a];//a = 2,b = 1

接收多个返回值

function getUserInfo(id){
    //..ajax
    return[
        true,
        {
            name:'mm',
            sex:'male',
            id:id
        },
        'success'
    ];
};
const [status,data,msg] = getUserInfo(123);

对象的解构赋值

const person = {
    age:20,
    name:'mm'
};
const { age, name } = obj;//对象的属性值必须相同,否则接收不到,返回undefined

对象的解构赋值和数组不同的是对象属性是无序的,不用给不需要的属性空出位置。

更复杂的解构规则

const person = {
    age:20,
    name:'mm',
    friend:[{
        name:'qq',
        age:19
    },{
        name:'pp',
        age:18
    }]
}
const { age } = person;
const { friend: [ friend1, { name }, { name: nam } ] } = person;

外面用对象的解构规则拿到friend,第一项用数组的解构规则拿到,第二项中的name属性使用对象的解构规则拿到,第三项中的name属性不能再使用相同的名字接收,所以{ name: nam }是把属性name中的值赋给nam

扩展运算符

const person = {
    name: 'mm',
    age: 20,
    sex: 'male',
    hobby: 'football'
};
const { name, ...oth } = person;//oth中包含剩下的所有没有匹配到的对象

//对象的合并
const person = {
    age: 20,
    sex: 'male'
}
const student = {
    name: 'mm',
    ...person,
}

对已经声明的变量进行对象的解构赋值

let age;
cosnt obj = {
    name: 'mm',
    age: 20
}
({ age } = obj);

默认值

let friend = {
    name: 'mm',
    age: 22
}
let { name, age = 24, hobby = {'football'}} = friend;//当匹配不到或者匹配到undefined就会用到默认值

提取对象属性

const { name, hobby: [hobby1], hobby } = {
    name: 'mm',
    hobby: ['study']
}

hobby: [hobby1]提取属性值的内容就是数组中的第一项,即hobby1 = 'study'hobby提取到属性值,即hobby = ['study']
使用对象传入乱序的函数参数

function AJAX({
    url,
    data,
    type = 'get'
}){
    console.log(type);//'get'
};

AJAX({
    url: '/getinfo',
    data: {
        a: 1
    },
});

获取多个函数的返回值

function getUserInfo(){
    //...ajax
    return{
        status: true,
        data: {
            name: 'mm'
        },
        msg: 'success'
    };
};
const { status, data, msg } = getUserInfo(123);

字符串的解构赋值

const str = 'I am Tom';
const [a,b,v,...oth] = str;//a='I',b=' ',oth=['a','m',' ','T','o','m']
//三种获取字符串中字符的方式
const [...spStr1] = str;
const spStr2 = str.split('');
const spStr3 = [...str];

数值与布尔值的解构赋值

const { valueOf } = 1;
const { toString } = false;

数值和布尔值本身没有属性,查找属性时先会返回其包装对象再查找。
函数参数的解构赋值

function swap([x,y]){
    return [y,x];
};
let arr = [1,2];
arr = swap(arr);

扩展

字符串扩展

const xm = {
    name: 'mm',
    age: 15,
    say1: function(){
        console.log('我叫' + this.name + ', 我今年' + this.age + '岁!');
    }
    say2: function(){
        console.log(`我叫${ this.name }, 我今年${ this.age }岁!`);//say2与say1输出相同
    }
}

使用${}省去了+连接字符串,同时不需要把每段字符串单独用引号包裹,整体使用一个反引号即可(`)。

//padStart padEnd
let str = 'i';
let str1 = str.padStart(6,'mooc');//moocmi
let str2 = str.padEnd(5,'mooc');//imooc

//repeat
'i'.repeat(10);/iiiiiiiiii   不能带负数,小数会取整

//startsWith endsWith
const str = 'A promise is a promise';
str.startsWith('A pro');//true
str.endsWith(promise);//true

//includes
const str = 'A promise is a promise';
if(str.indexOf('promise') !== -1){//indexOf的方法
    console.log('存在');
}
if(str.includes('a promise')){
    console.log('存在';)
}

for-of遍历字符串

let str = 'PROMISE';

//ES6之前遍历字符串的方法
//1.使用for循环
for(var i = 0;i < str.length;i ++){
    console.log(str[i]);
    console.log(str.charAt(i));
}

//2.转成数组后遍历
var oStr = str.split('');
const oStr = [...str];
const [...oStr] = str;

oStr.foreach(function(word){
    console.log(word);
});

//for-of遍历字符串
for(let word of str){
    console.log(word);//P R O M I S E
}

正则扩展
ES6之前的修饰符只有g、i、m,ES6新加入了两个修饰符。

//u:unicode
console.log(/^\ud83d/.test('\ud83d\udc36'));//true
console.log(/^\ud83d/u.test('\ud83d\udc36'));//false 加入u把整个正则看作一个整体unicode编码

//y 黏连修饰符
const r1 = /imooc/g;
const r2 = /imooc/y;
const str = 'imoocimooc-imooc';

console.log(r1.test(str));//imoocimoocimooc
console.log(r2.test(str));//imoocimooc 必须是紧邻的符合规则的符号,否则不匹配

数值扩展

//新的数值表示法
0o 0O 八进制
0b 0B 二进制

//新的方法和安全数
Number.parseInt(2.23);//2 原来是window下的方法,现在是Number下的方法
Number.isNaN(NaN);//true 是不是NaN
Number.isNaN(1);//false

    //实现isNaN()
    function isNaN(value){
        return value !== value;
    }

Number.isFinite(Infinity);//false 是不是有理数
Number.isFinite(1111);//true
Number.MAX_SAFE_INTEGER;//最大安全数 2^53-1
Number.MIN_SAFE_INTEGER;//最小安全数 -2^53+1
Number.isSafeInteger();//判断是否在安全范围

//幂运算
let a = 5**5;//25
let a = 5**5**0;//5 从右往左计算

函数扩展

//函数参数的默认值
function add(a,b = 1+a){//参数的默认值不能出现变量自身或者后面的变量
    console.log(a,b);//10,11
}
add(10);

//扩展运算符(把数组中内容展开)
[1,2...[1,2,3]]//[1,2,1,2,3]
//剩余参数(把参数聚合成一个数组)
function sum(...args){//剩余参数必须在最后一位
    console.log(args);//[1,2,3,4,5]
}
sum(1,2,3,4,5);

//箭头函数
const add1 = (a,b) => a + b;//函数体为单行
const add2 = function(a,b){
    return a + b;
}
console.log(add1(2,2));//4
console.log(add2(2,2));//4

const add1 = (a,b) => {//函数体为多行
    a += 1;
    return a + b;
}

const pop = arr => void arr.pop();//函数体不需要返回值

箭头函数与普通函数的区别

//1.箭头函数中没有代表实参的arguments,可以用剩余参数代替
const num = (...args) => {
    console.log(arguments);//报错
    console.log(args);//[1,2,3]
};
num(1,2,3);

//2.箭头函数没有自己的this
const xiaoming = {
    name:'小明',
    age:null,
    getAge:function(){
        let _this = this;
        //...ajax
        setTimeout(function(){
          _this.age = 14;//this.age中this指向window,无法获取到age
          console.log(_this);
        },1000);
    }
};

const xiaoming = {
    name:'小明',
    age:null,
    getAge:function(){
        //...ajax
        setTimeout(() => {
          this.age = 14;
          console.log(this);
        },1000);
    }
};
xiaoming.getAge();

1.定时器是谁调用的与定时器中使用普通函数和箭头函数无关。即定时器就是window调用的。

2.普通函数中,谁调用了函数,this执行谁。由于window调用了定时器,所以this执行window。 箭头函数中的this是在函数创建时就会绑定,例如本代码中,定时器是在getAge方法中创建的,所以它里面的this绑定的就是getAge方法中的this。

对象扩展

//简洁表示法
const getUserInfo = (id = 1) => {
    const name = 'xiaoming',
    const age = 10;
    
    return {
        name,//name:name
        age,/age:age
        say(){//say: function()
            console.log(this.name + this.age);
        }
    };
};
const xiaoming = getUserInfo();

//属性名表达式
const key = 'age';
const obj = {
    name:'xm',
    [key]:14;//age:14
    ['daf'+'dsfds'+789]:111;
}

//扩展运算符复制对象(浅拷贝)
const obj = {
    a:1,
    b:2,
    c:{
        aa:1,
        bb:2
    }
}

const obj2 = {
    a:3,
    b:4,
    z:10
}

const cObj1 = { ...obj1 };
console.log(cObj1.d.aa);//1
cObj1.d.aa = 999;
console.log(cObj1.d.aa);//999
console.log(Obj1.d.aa);//999

//合并对象
const newObj = {
    ...obj1,
    ...obj2//a:3,b:4,... 合并对象中有相同的属性,谁在后面取谁的值
}

//部分新的方法和属性
Object.is(+0,-0)//false
+0 === -0//true
Object.is(NaN,-NaN)//true(其他是否相等的比较与===的结果相同)
NaN === NaN//false

const obj = Object.assign({a:1},{b:2},{c:3},{d:4},{e:5});//{a:1,b:2,c:3,d:4,e:5}(注意是浅拷贝)

const obj = {
    a:1,
    b:2,
    c:3,
    d:4
}
console.log(Object.keys(obj));//['a','b','c','d'] 返回属性组成的数组
console.log(Object.values(obj));//[1,2,3,4] 返回属性值组成的数组
console.log(Object.entries(obj));//[['a',1],['b',2],['c',3],['d',4]] 返回每一对键值对

//super
const obj = {name:'xm'};
const cObj = {
    say(){//必须使用对象的简写方式才能使用super
        console.log(`My name is ${super.name}`);
    }
}
Object.setPrototypeOf(cObj,obj);//把obj设置为cObj的原型对象
cObj.say();//super可以获取到原型上的属性
console.log(cObj.__proto__ === Object.getPrototypeOf(cObj));//true 获取原型

数组扩展

//扩展运算符的使用
const user = ['小明',14,['eat','play'],'我有女朋友'];
function say(name,age,hobby,desc){
    console.log(`我叫${name},我今年${age}岁,我喜欢${hobby.join('and')}${desc}`);
}
say(user[0],user[1],user[2],user[3]);//一个一个传太麻烦了
say(...user);
say.apply(null,user);//apply传入的数据是数组形式

//扩展运算符合并字符数组
const arr1 = [1,2,3.4];
const arr2 = [4,3,2,1];
const arr3 = [1.2,'123',true];

const cArr1 = [1,2,3,...arr2];//在已有数据中合并数组
const cArr2 = [...arr1];//数组的复制
const [...cArr3] = arr3;//数组的复制
const cArr4 = [...arr1,...arr2,...arr3];//合并几个数组

//将类数组对象转换成数组的方法(总结)
const obj = {
    0: 1,
    1: 'asd',
    2: false,//类数组对象的属性名必须是数值型或字符串型的数字
    length: 2
};
Array.from(obj, item => item * 2);//将类数组转换成数组,并且执行后面的回调
Array.prototype.slice.call();
[].slice.call();
[...]

//新的方法
//Array.of
console.log(Array.of(1,2,'123',true));//[1,2,'123',true];

//Array#fill
let arr = new Array(5).fill(0);//[0,0,0,0,0] 如果数组中有值会被覆盖掉
let arr = [1,2,3,4,5].fill(0,0,3);//[0,0,0,4,5] 用0覆盖0,1,2位置上的值

//Array.includes
let arr = [1,2,3];
console.log(arr.includes(1));//true 判断数组中有没有某个值

//keys values entries
const arr = [10,11,12];
for (let i of arr.keys()){
    console.log(i);//0,1,2
}
for (let i of arr.values()){
    console.log(i);//10,11,12
}
for (let [i,v] of arr.entries()){
    console.log(i,v);//0 10,1 11,2 12
}

//find 根据条件(回调)按顺序遍历数组 当回调返回true时 就返回当前遍历到的值
const res = [1,2,3,4].find((value,index,arr) => value % 2 === 0);
console.log(res);//2

//findIndex 根据条件(回调)按顺序遍历数组 当回调返回true时 就返回当前遍历到的下标
const res = [1,2,3,4].find((value,index,arr) => value % 2 === 0);
console.log(res);//1

promise

promise对象中存放着我们未来要做的事,比如异步操作,事情完成后会返回相应的结果,结果成功或失败都有对应的反应。

promise基本结构

function fn(){
    //函数中返回Promise的实例,传入resolve作为参数代表事件成功后的反应
    return new Promise(resolve => {
        //以定时器为例表示异步操作
        setTimeout(function(){
            resolve();
        },1000);
    })
}
fn.then(console.log(1));//1秒后打印1

promise相比于以回调函数的方式执行异步操作,首先解决了在有多个异步操作的情况下,回调函数存在嵌套的问题;其次执行异步操作的过程中,回调函数可能出现被用户更改指向等问题。promise可以解决以上回调函数中的问题。

错误处理
promise判断resolvereject都是异步执行的,所以无法使用try catch语句

function fn(val){
    return new Promise((resolve,reject) => {
        if(val){
            resolve();
        }else{
            reject();
        }
    });
}
fn(false).then(() => {
    console.log('成功');
},() => {
    console.log('失败');
})

then方法中的第二个回调是失败时做的事,resolvereject中只可以传入一个参数,第一个之后的参数接收不到。

//使用实例的catch方法可以捕获错误
fn(true).then(data => {
    console.log(data);
    return fn(false);
})
.then( () => {
    //上面返回的是失败,这里没有对失败的处理函数,所以会向下执行
    console.log('我永远不会被输出');
})
//前面如果有对失败的处理函数就不会再执行catch
.catch(e => {
    console.log(e);
})

//不论成功还是失败 finally中的内容一定会执行
fn(true).then(data => {
    console.log(data);
    return fn(false);
})
.catch(e => {
    console.log(e);
})
.finally(() =>{
    console.log(100);
});

promise有三种状态,进行中、成功和失败状态,调用resolvereject会将promise变为成功和失败状态,并且状态的改变不可逆。

promise的其他方法

//Promise.all方法可以把多个promise实例包装成一个新的promise实例
//Promise.all([promise1,promise2,promise3]) 返回Promise
//Promise.all中的所有promise都返回成功,Promise.all返回成功,有任何一个失败都会返回失败。
//Promise.all里面是空数组会返回成功
function getData1(){
    return new Promise((resolve,reject) => {
        setTimeout(() => {
            console.log('第一条数据加载成功');
            resolve('data1');
        },1000);
    });
}
function getData2(){
    return new Promise((resolve,reject) => {
        setTimeout(() => {
            console.log('第二条数据加载成功');
            resolve('data2');
        },1000);
    });
}
function getData3(){
    return new Promise((resolve,reject) => {
        setTimeout(() => {
            console.log('第三条数据加载成功');
            resolve('data3');
        },1000);
    });
}
let p = Promise.all([getData1(),getData2(),getData3()]);
p.then(arr => {
    console.log(arr);//[data1,data2,data3] 在所有决议成功后返回该数组
});