前端学习日记 # ES6

397 阅读9分钟

简介

JS 组成部分

由三大部分组成

  1. ES (JS语法规范)
  2. DOM (操作网页元素API)
  3. BOM (操作浏览器API)

ES6 是什么?

在2015年之前,ES版本都是以‘ES+数字’方式进行命名,最高发布到ES5.1;但2015年之后,ES版本命名发生改变,变为‘ES+年份’进行命名,并且逐年更新,不仅是命名和更新频率发生改变,而且ES2015推出了很多新特性和对JS的缺陷提出很多补救的语法,让JS更健壮,功能更强大,所以2015年对于JS来说是一个里程碑的年份,行业里对2015年及其之后推出的所有ES版本统称为ES6,因此ES6是个泛指,其包含ES2015、ES2017等

新语法

let const声明

var 缺陷
1、缺少块级作用域
2、可在同一作用域下重复声明同一变量
3、无常量概念
4、在for循环里存在污染变量问题
5、变量提升问题(先使用后声明)
6、在全局下,定义的变量其实是window对象的属性(污染window对象)

let/const 的出现就是为了解决 var 的缺陷

var btns = document.getElementsByTagName("button");  // 4个btn

for(var i=0; i<btns.length; i++){
	btns[i].onclick = function(){
		console.log(i);    
	}
}
// 永远输出 4 

for(let i=0; i<btns.length; i++){
	btns[i].onclick = function(){
		console.log(i);    
	}
}
// 输出对应索引

上面例子原因:var声明的变量是全局而let声明的变量是局部的

const 定义常量 必须注意以下:
1、一旦声明就必须赋值
2、一旦赋值就不能修改
3、具有块作用域
4、声明引用类型常量,引用类型里的值可以修改

在全局中,用var声明的变量其实是window对象的属性(函数声明也一样),而用let/const声明的变量不是window对象的属性

模板字符串

一种定义字符串的新方式,让字符串拼接更加简单,并且支持换行

// 以前的方式
var oldOneStr = "xxx";
var oldTwoStr = 'bbb';

// 新方式
let newStr1 = `bsss${name}hhh`;
let newStr2 = `测试
你是谁`;

解构赋值

数组对象中提取值,并对变量赋值,这一系列操作称之为解构,主要用于简化代码

没有解构前

var obj = {
    name: "wt",
    age: 18,
    desc:“谢谢彩信相册”
}

var name = obj.name,
    age = obj.age,
    desc = obj.desc;

出现解构后

var obj = {
    name: "wt",
    age: 18,
    desc:“谢谢彩信相册”
}
// 解构时,左边的变量名必须与右边对象中属性名完全一样
var {name, desc, age} = obj;

解构失败的变量,值将为undefined

完全解构

// 对象
var obj = {
    name: "wt",
    age: 18,
    desc:“谢谢彩信相册”
};
var {name, desc, age} = obj;

// 数组
var arr = [20, 30, 50];
var [a, b, c] = arr;

部分解构

// 对象
var obj = {
    name: "wt",
    age: 18,
    desc:“谢谢彩信相册”
};
var {desc} = obj;

// 数组
var arr = [20, 30, 50];
var [,, c] = arr;

对象解构并重命名

var {name: titleName} = obj;  // 针对对象

多层嵌套如何解构

// 对象
var obj = {
    name: 'wt',
    age: 28,
    ex: {
        isMerry: true
    }
}
var {name, age, ex: { isMerry }} = obj;

// 数组
var arr = [1, 2, [100, 200, 300]];
var [x,,[,y]] = arr

其他应用 (交换变量值)

var str1 = '234',
    str2 = '567';
    
[str1, str2] = [str2, str1];

对象的简化写法

以前

var title = "a", desc = "xxx";

function go(){}

var obj = {
    title:title,
    desc: desc,
    go: go,
    init: function(){}
};

简化后

var title = "a", desc = "xxx";

function go(){}

var obj = {
    title,
    desc,
    go,
    init(){}
};

当对象的属性名和变量名相同时,可以使用简写

函数参数

默认值

以前做法

function test(a, b, c){
    a = a || 0;
    b = b || 0;
    c = c || 0;
    return a+b+c;
}

现在做法

function test(a=0, b=0, c=0){
    return a+b+c;
}

解构

函数参数:数组

// 数组解构必须遵循顺序
function test([a, b, c]){
    return a+b+c;
}

test([10, 20, 30]);

函数参数:对象 (推荐)

// 比较推荐此方式作为函数参数进行解构,因为无需按顺序
function test({c, b, d}){
    console.log(b);
}

test({
    d: 12,
    c: '232',
    b: true
})

解构默认值

如果函数参数为对象并解构赋值,但函数调用时,并没有传实参,此时将会语法报错

function test({c, b, d}){
    console.log(b);
}

test()  // 相当于传了一个null过去进行解构,如:{c, b, d}=null  语法报错
test({}) // 预期要传一个对象过去,至少需要传空对象{} 语法正确 

从上面代码可以知道解构对象参数时,至少需要一个空对象{}传过去,但为了不传实参也不会出现语法错误,我们需要使用解构对象默认值,如下

function test({a,b}={}){
    console.log(a,b)
}

test()  // undefined,undefined  语法正确

上面做法虽然语法正确了,但比非我最终想得到的结果,我希望在不穿任何值的情况下,解构出来的a和b有默认值,所以还要设置解构对象属性默认值,如下

function test({a=0,b=0}={}){
    console.log(a,b)
}

test()  // 0,0 

上面写法已经是最终版的写法,但综合前面的知识,还想解构重命名(这个是伪需求),如下

function test({a:one=1,b:two=2}={}){
    console.log(one,two);
}

test();

未知剩余参数 Rest

ES6之前

function test(){
    console.log(arguments);  // 函数内部默认变量,类数组
}
test();

ES6之后 (Rest参数取代了arguments)

function test(a, b, ...c){
    console.log(c);   // [3,4,5]
}
test(1,2,3,4,5)

注意事项:
1、rest参数只能用于函数定义的形参中,以 ...变量名 形式出现
2、rest参数只能出现在函数形参最后一位
3、rest参数实际上用数组把未知剩余参数装起来,数据类型就是数组

与解构相结合

let [a, ...rest] = [];  // a=undefined, rest=[]
let [a, ...rest] = [1,2,3];  // a=1, rest=[2,3]

// 数组
function test([c, d, ...z]){
    console.log(z);  // [3,4,5]
}
test([1,2,3,4,5])

// 对象
function testObj({name,age,...d}){
    console.log(d);  // {sex:33,go:false}
}
testObj({
    name: 'xx',
    sex: 33,
    go: false,
    age: 18
})

扩展运算符

和上面提到的 rest参数 写法是一模一样,但功能完全不一样。一个收(rest参数),一个放(扩展运算符)

扩展数组

function test(){
    var arr = [1, 2, 3];
    console.log(...arr);  // 其实就是把数组的中括号[]去掉
}

扩展字符串 (不推荐

function test(){
    var str = 'abcd';
    console.log(...str);  // 其实就是把字符串分割成一个一个字符
}

扩展对象

function test(){
    var obj = {
        name: 'wt',
        age: 18
    };
    
    // 其实就是把对象的大括号{}去掉
    var newObj = {
        sex: 'men',
        ...obj
    };  
}

注意
使用扩展运算符来创建对象,是一种浅复制,最外层属性是非复杂类型,将完全复制,如果是复杂类型(对象、数组、函数),将和原本对象共用一套数据(说白就是你改,我也变,非深度复杂),代码如下:

    const obj = {
        name: 'xxx',
        info: {
            schoolName: 'bsss'
        }
    }

    const obj1 = {
        ...obj
    }

    obj.name = '2222'
    obj.info.schoolName = 'woshixiaoxue'

      
    console.log(obj1.name);  // xxx
    console.log(obj1.info.schoolName); // woshixiaoxue

使用场景

1、数组中的值作为函数参数(对象中的值不可作为函数参数)

function test(a, b, c){
    console.log(a,b,c);
}

let arr = [10,20,30];
test(...arr);

2、数组合并

let arr1 = [1,2,3],arr2 = [2,3,4];
// 合并,但不会去重
let arr = [...arr1, ...arr2]; // [1,2,3,2,3,4]

3、字符串转数组

let arr = [...'abcd'];

4、对象合并

let obj1 = {
    name: 'ww',
    age: 18
};

let obj2 = {
    name: 'tt',
    sex: '男'
};

// 合并并去重,如果有相同KEY,后面替换前面(书写顺序)
let newObj = {...obj2,...obj1};

// es6还提供了另一种合并对象的API
let newObj = Object.assign({}, obj2, obj1);

注意事项:
被合并对象(obj1/obj2)的属性值,如果是引用类型,那么修改被合并对象(obj1/obj2)后,合并对象(newObj)对应的属性值也会跟随变化,如果非引用类型,那就不会跟随变化

箭头函数

使用“箭头”(=>)来简化函数定义

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

1、普通函数有3种定义方式(函数声明、函数表达式、匿名函数),而箭头函数只有2种(函数表达式、匿名函数
2、普通函数中的this是动态的,而箭头函数中的this是静态
3、普通函数的this指向调用者,而箭头函数的this指向当前所在环境的this

声明

// 表达式声明
let fn = () => {};

// 匿名函数用法
() => {}

注意点

1、函数参数如果只有一个,可以省略小括号不写
2、如果函数体内只有一个语句,可以省略大括号不写,如果这个语句是return,那么可以连return都省略
3、如果函数体内有多个语句,则不能省略大括号
4、如果函数体只有一个语句,且返回一个对象,不能省略大括号和return
5、箭头函数不能使用arguments,可以使用rest参数替代

// 无参数且只有一个语句
const test1 = () => console.log(123);

// 一个参数且多个语句
const test2 = p => {
    let a = 1,b = 2;
    return a+b;
};

// 多个参数且只有一个返回语句
const test3 = (a,b) => a+b; 

应用场景

回调函数

// 箭头函数很适合做回调函数
setTimeout(()=>{
    console.log(123);
}, 3000);

// 数组遍历
let arr = [1,2,3,4];
arr.forEach(item => console.log(item));

this

箭头函数中的this指向与function函数中的this指向不同,箭头函数的this指向外层的this(可以理解为箭头函数不存在自己的this)
字面量对象中也不存在this,对象属性使用this指向对象所在作用域的this

var obj = {
    name:‘bb’,
    desc: this.name + "我来了" // 这里的this指代的是window,并非obj对象
    go: function(){
        console.log(this.name); // obj.go()这里this指向obj对象 (推荐)
    },
    come: () => {
        console.log(this.name); // obj.come()这里this指向外层window
    }
}

let btn = document.getElementById("#send");
btn.onclick = function(){
    setTimeout(function(){
        console.log(this);  // this指向window
    }, 2000);

    setTimeout(()=>{
        console.log(this);  // this指向btn (推荐)
    }, 3000);
}


Promise

相比起传统方案(回调函数),是更合理更强大的异步编程解决方案!
Promise是一个对象,更像一个容器,保存异步操作

作用

解决异步编程中的回调地狱问题,异步代码同步化

三种状态

1、pending 初始状态,既不是成功,也不是失败
2、fulfilled 操作成功
3、rejected 操作失败

特点

1Promise对象不受外界影响,Promise对象代表一个异步操作。只有异步操作的结果可以决定Promise状态
2、一旦状态改变就不会再变,Promise对象的状态改变只有2种情况:
   第一、pending变为fulfilled 
   第二、pending变为rejected  
   
   状态一旦凝固,就不会再改变了

状态发生改变后,你再对Promise对象添加回调函数,回调函数也会立刻得到这个结果。 和事件完全不同

语法

const p = new Promise(function(resolve, reject){
    if("操作成功"){
        resolve();  // pending --> fulfiled 成功
    }else{
        reject();   // pending --> rejected 失败
    }
});

p.then(d => {}).catch(err => {});

数组

reduce 遍历求和

语法
let arr = [1,2,3];

let res = arr.reduce(function(pre, current, index, arr){
    // 遍历arr数组,每遍历一个元素,就执行一遍这个函数
    // pre指的是上一次执行这个函数的返回值
    // 如果遍历第一次执行的话,有initValue的话,pre就是initValue(从0开始遍历)
    // 否则pre就是下标为0的值(从1开始遍历)
    // current当前遍历的元素
    return 10;
}, initValue);

console.log(res) // 最后一次遍历所return的值
应用情景

求和、求乘积

const arr = [1,4,5,6,7];
// 求和
let addRes = arr.reduce(function(pre, current, index, arr){
    return pre+current;
}, 0);

// 求乘积
let mulRes =  arr.reduce(function(pre, current, index, arr){
    return pre*current;
}, 1);