ES6 常用特性总结


一、ES6 基本认识

1、什么是 ES6?

  ES6 指的是 ECMAScript 6.0,是JavaScript 语言的一个标准。其目标是使JavaScript 可以用来编写复杂的大型的应用程序,成为企业级开发的语言。

2、ES6 与 JavaScript 的区别?

  ES6 是 JavaScript 的一个标准,JavaScript 是 ES6 的具体实现。

3、Babel 转码器?

  Babel 是一个被广泛使用的 ES6 转码器,其可以将 ES6 代码转为 ES5 代码,从而在现有环境下执行,即使用 ES6 编写代码而无需担心不能运行。
  简单的讲就是 一些 浏览器 不支持 ES6 部分语法,可以使用 Babel 转码器将 ES6 语法 转为 ES5 语法,从而被 浏览器 识别。
  比如:

    ES6 可以使用 箭头函数来 替代 普通函数,通过 Babel 转码器,可以将 箭头函数 转为 普通函数。这样就不需要去担心浏览器是否支持这种语法。

【ES6】
    input.map(item => item + 1);

【ES5】
    input.map(function (item) {
        return item + 1;
    });

二、常用特性

1、let 命令

(1)基本内容
  let 命令通常用来声明局部变量。
  特性:局部有效、不存在变量提升、暂时性死区、不允许重复声明。

(2)特性一:局部有效。
  let 命令类似于 var,用来声明变量,但是 var 是全局有效,let 只在其所在的代码块内生效,出了代码块就获取不到该值。

如下例:(看的可能有点绕,多瞅两遍)
  var 定义的是全局变量,对于 for 循环来说,整个循环都只对一个 变量进行操作。看下面例子的第一个循环,在循环体内操作 i 会对循环有影响。由于进行了两次 i++,所以数组有部分值为 空,且只进行了部分循环。

  let 定义的是局部变量,对于 for 循环来说,每次循环都是不同的作用域,且 let 只对当前作用域有效。更有趣的是,循环语句内部是一个子作用域(即 在内部定义一个同名的 let 变量,不会影响外部的 let 变量)。看下面例子的第二个循环,每次循环操作,j 都是不同的值,且 循环内部 定义了 同名的 let 变量 j ,由于作用域的问题,其并不会影响循环语句中的 j,所以执行了全部循环。

【举例:】

var a = [];             // 用来记录每次循环需要打印的初始值
var b = [];             // 用来记录每次循环的初始值
var count = 0;          // 用来记录循环的次数

for(var i = 0; i < 10; i++, count++) {
    a[i] = function() {
        console.log("当前循环的值 i 为:  " + i);
    };
    b[i] = i++; 
}
console.log("当前循环执行次数为:  " + count);           // 由于 i 为全局变量,每次循环都会进行两次 i++,所以真实循环次数小于 10,所以输出为 5
a[6]();                                                // 由于操作的都是同一变量,所以函数调用的是 最后一次修改的 i 值,所以输出为 10
console.log("每次循环的初始值 i 为:  " + b);            // 用于只进行了部分循环,所有数组有些并没有赋值,即为空值。所以输出为 0,,2,,4,,6,,8
console.log("循环执行后的 i 值:  " + i);                // i = 10 时退出循环, i 为全局变量,所以输出 为 10


var c = [];            // 用来记录每次循环需要打印的初始值
var d = [];            // 用来记录每次循环的初始值
var count = 0;         // 用来记录循环的次数
for(let j = 0; j < 10; j++, count++) {
    let j = 5;
    c[j] = function() {
        console.log("当前循环的值 j 为:  " + j);
    };
    d[j] = j++; 
}
console.log("当前循环执行次数为:  " + count);           // 由于 j 为 局部变量,循环内部定义的 let 同名变量 j (子作用域)不会影响 循环语句的 j,真实循环执行 10 次,所以输出为 10 
c[5]();                                                // 每次操作都是不同的变量,且执行了 j++ 操作,所以输出为 6
console.log("每次循环的初始值 j 为:  " + d);            // 由于内部每次都给 d[5] 赋值,其余元素均为空值,所以输出为  ,,,,,5
console.log("循环执行后的 j 值:  " + j);                // 由于 j 为局部变量,只能存在于 for 循环代码块中, 所以此处会报错,输出 ReferenceError: j is not defined

(3)特性二:不存在变量提升
  变量提升指的是 变量可以在声明前使用。
  let 不存在变量提升,即声明变量后,才可以使用该变量,不能在声明前使用,否则会报错。

【举例:】

console.log(a);     // 不报错,输出 undefined
console.log(b);     // 报错,输出 ReferenceError: b is not defined
var a = 10;
let b = 20;

(4)特性三:暂时性死区
  暂时性死区指的是 刚开始进入当前作用域,所要使用的变量就已经存在了,但是不可获取,当变量被声明后,才可以获取该变量。

【举例:】

var tmp = 123;
console.log(tmp); //不报错,输出 123
if (true) {
    console.log(tmp); // 报错,ReferenceError: Cannot access 'tmp' before initialization
    let tmp;
}

第一次定义 tmp 为 var 型,所以可以正常输出 123,
进入 if 语句后,由于存在 let 定义的 tmp,系统判定 tmp 为局部变量而非全局变量。
导致 console.log(tmp) 中 tmp 出现在 变量声明前(变量提升失败), 从而报错,此处即为暂时性死区。

(5)特性四:不重复声明
  在同一块 let 作用域中,若使用 let 声明一个变量,则不能再重复声明同一个变量。

【举例:】
// 报错,Identifier 'a' has already been declared
{
    let a = 1;
    var a = 2;
}

// 不报错,undefined
{
    var a = 1;
    var a = 2;
}

// 报错,Identifier 'a' has already been declared
{
    let a = 1;
    let a = 2;
}

2、const 命令

(1)基本内容
  const 通常用来声明一个只读的常量,一旦声明,常量的值不能被修改,且声明时必须初始化。
  用法类似于 let,局部有效、不存在变量提升、不重复声明。

【举例:(常量值不可被修改)】

const PI = 3.1415926
console.log(PI);   // 输出 3.1415926
PI = 3.14          // 报错,输出 Assignment to constant variable.

【举例:(常量声明时需要初始化)】

const a           // 报错,输出 Missing initializer in const declaration

【举例:局部有效】
{
    const PI = 3.1415926;
}
console.log(PI);   // 报错,输出 PI is not defined

【举例:不存在变量提升】
{
    console.log(PI);    // 报错,输出 Cannot access 'PI' before initialization
    const PI = 3.1415926;
}

【举例:不重复声明】
{
    var PI = 3.14
    const PI = 3.1415926;  // 报错,输出 SyntaxError: Identifier 'PI' has already been declared
}

(2)若 const 声明的是对象,那么 其不变的是 指向对象的地址,对象的值仍可以改变。可以通过object.freeze() 方法冻结对象(即对象不可修改)。

【举例:var,对象可被修改】
{
    var f = {name : 'tom', age : '12'};
    console.log(f.name + ", " + f.age);   // tom, 12
    f.name = 'jarry';
    f.age = 44;
    console.log(f.name + ", " + f.age);  // jarry, 44
    f = {name : 'rick', age : '22'};
    console.log(f.name + ", " + f.age);  // rick, 22
}

【举例:const,对象内容可被修改,但是对象不可被修改】
{
    const f = {name : 'tom', age : '12'};
    console.log(f.name + ", " + f.age);   // tom, 12
    f.name = 'jarry';
    f.age = 44;
    console.log(f.name + ", " + f.age);  // jarry, 44
    f = {name : 'rick', age : '22'};  // TypeError: Assignment to constant variable.
}

【举例:freeze,对象不可被修改,对象内容不可被修改】
{
    const f = Object.freeze({name : 'tom', age : '12'});
    console.log(f.name + ", " + f.age);  // tom, 12
    f.name = 'jarry';
    f.age = 44;
    console.log(f.name + ", " + f.age);  // tom, 12
    f = {name : 'rick', age : '22'};  // TypeError: Assignment to constant variable.
}

3、解构表达式

(1)什么是解构?
  解构指的是 ES6 支持按照一定的模式,从数组或者对象中提取值,并将提取的值 对变量进行赋值。

(2)数组的解构赋值
  一般情况下,只要 = 左右两侧 的模式相同,左边的变量 就会赋值 上 右边对应的值。

【未使用解构表达式给赋值:】
let a = 10;
let b = 20;
let c = 30;
console.log(a, b, c);

【使用 解构表达式赋值:】
let [a, b, c] = [100, 200, 300];
console.log(a, b, c);

  解构不成功时,对应的数据为 undefined。
  允许解构赋值指定默认值,默认值可以为一个函数(惰性,用到时才调用)。

【嵌套数组赋值:】

let [a, [b, [c, d]]] =  [1, [2, [3, 4]]];
console.log(a, b, c, d);         // 输出 1 2 3 4

let [head, ...tail] = [1, 2, 3, 4];
console.log(head);    // 输出 1
console.log(tail);    // 输出 (3) [2, 3, 4]

let [x, y, ...z] = [1];
console.log(x);        // 输出 1
console.log(y);        // 输出 undefined
console.log(z);        // 输出 []

【部分解构:(给匹配上的变量赋值)】

let [x, y, z] = [1, 2];
console.log(x);       // 输出 1
console.log(y);       // 输出 2
console.log(z);       // 输出 undefined

let [a, [b], c] = [1, [2, 3], 4];
console.log(a);       // 输出 1
console.log(b);       // 输出 2
console.log(c);       // 输出 4

【解构时使用默认值:(即若赋值失败,可以使用默认值)】

function hello() {
    return "hello";
}

let [x=hello(), y=hello(), z=100] = [1, 2, 3];
console.log(x);               // 输出 1
console.log(y);               // 输出 2
console.log(z);               // 输出 3

let [x2=hello(), y2=hello(), z2=100] = [, 2, ];
console.log(x2);              // 输出 hello
console.log(y2);              // 输出 2
console.log(z2);              // 输出 100

(3)对象的解构赋值
  对象同样可以进行解构。与数组解构不同的是,对象解构时根据属性名进行匹配,不需要注意顺序。
  属性名不匹配时,值为 undefined。
  可以自定义属性名,使用 : 去指定。
如下例:
  let {name, age} 等价于 let {name: name, age: age}

【根据属性名匹配:】

let {name, age} = {name: "tom", age: 22};
console.log(name);               // 输出 tom
console.log(age);                // 输出 22

【属性名匹配不成功,返回 undefined:】
let {name2, age2} = {name: "tom", age: 22};
console.log(name2);               // 输出 undefined
console.log(age2);                // 输出 undefined

【自定义属性名匹配:】
let {name: name3, age: age3} = {name: "tom", age: 22};
console.log(name3);               // 输出 tom
console.log(age3);                // 输出 22

4、字符串拓展

  即加强了字符串处理功能。
(1)字符的 unicode 表示
  JavaScript 允许使用使用 \uxxxx 的形式表示一个字符,其中 xxxx 表示 unicode 值。但是这种写法只支持 \u0000 ~ \uffff,超出这个限制需要使用 双字节 进行表示。
比如:

  \u1F680 会解析成 \u1F68 和 0。若想正常显示,需使用双字节 \uD83D\uDE80 表示。

【举例:】

console.log("\u0061");       // 输出 a
console.log("\u00614");      // 输出 a4

  ES6 可以使用 大括号将 xxxxx 括起来,从而正确解读。

【举例:】

console.log("\u{0061}");
console.log("\u{1F680}");
console.log("\uD83D\uDE80");

(2)新增方法 -- includes()、startsWith()、endsWith()
  JavaScript 中通过 indexOf() 可以确定某个字符串中是否包含另外一个字符串。
  ES6 新增三个方法用于判断字符串中是否包含另一个字符串。
    includes() 返回布尔值,true 表示当前字符串中存在另一个字符串,false 表示不存在。
    startsWith() 返回布尔值,true 表示当前字符串的头部存在另一个字符串,false 表示不存在。
    endsWith() 返回布尔值,true 表示当前字符串的尾部存在另一个字符串,false 表示不存在。

【举例:】

let test = "hello world";
console.log(test.includes("wo"));          // 输出 true
console.log(test.startsWith("he"));        // 输出 true
console.log(test.endsWith("ld"));          // 输出 true
console.log(test.includes("helloworld"));  // 输出 false 

(3)模板字符串(``)
  模板字符串是增强版的字符串,使用 反引号(``) 标识字符串,可以作为普通字符串使用,可以定义多行字符串,内部使用 ${} 可以嵌入变量、函数、表达式等并解析。

【举例:】

let [name, age] = ["tom", 32];
function fun() {
    return "helloworld";
}
let test2 = `${name}, 
            age = ${age - 10}, 
            say ${fun()}`;
console.log(test2);

5、对象的拓展

  拓展对象的用法。
(1)属性简写
  ES6 允许在 对象中 直接写变量,此时 属性为 变量名,属性值为 变量值。即 {a} 等价于 {a: a}

【举例:】

let a = "hello";
let b = {a};
console.log(b);      // 输出 {a: "hello"}

let c = {a: a};
console.log(c);      // 输出 {a: "hello"}

let d = {g: "hello"};
console.log(d);      // 输出 {g: "hello"}

  对象中的方法也可以简写。

【举例:】

let [name, age] = ["tom", 32];

let person = {
    name,
    age,
    hello() {
        console.log(`${name}, ${age}`);
    },
    hello2: function() {
        console.log(`${name}, ${age}`);
    }
};

person.hello();
person.hello2();

(2)新增方法 -- assign()
  Object.assign() 方法用于对象的合并。
  其实现的是浅拷贝,即若 源对象中的某个属性值 仍是一个对象,那么目标对象 中拷贝得到的是这个对象的引用,即对源对象中这个对象进行修改,会影响到目标对象。

【格式:】
    Object.assign(target, source1, ...source2);
注:
    target 为目标对象,source1, ...source2 等都是源对象。
    该方法是将 源对象 的值 复制 到 目标对象 中。
    若出现同名属性,则后者会覆盖前者。
    即 target、source1、source2 中存在同名属性,则最后 target 的那个同名属性为 source2 的属性。
    
【举例:】

let tom = {
    name: "tom",
    age: 32,
    teacher: {
        chinese: "rose",
        english: "jack"
    }
};

let jarry = {
    name: "jarry",
    age: 33,
    email: "jarry@163.com"
};

let people = Object.assign({}, tom, jarry);

console.log(people);

tom.teacher.chinese = "rick";

console.log(people);

如何实现深拷贝嘞:
  有一种解决办法:(具体原因没有仔细深究)
    先将对象转为 json 字符串,再将 字符串转为对象。

将上例
    let people = Object.assign({}, tom, jarry);
改为
    let people = Object.assign({}, JSON.parse(JSON.stringify(tom)), jarry);

(3)新增对象遍历方法 -- keys()、values()、entries()
  Object.keys() 获取对象 key 形成的数组。
  Object.values() 获取对象 value 形成的数组。
  Object.entries() 获取对象 key - value 形成的 二维数组。

【举例:】

let people = {
    name: "tom",
    age: 22
};

console.log(Object.keys(people));      
console.log(Object.values(people));
console.log(Object.entries(people));

(4)扩展运算符(...)
  用于取出对象、数组的参数并拷贝到当前对象中。
  若数据重复,会覆盖,等同于 Object.assign()。

【举例:】

let people = {
    name: "tom",
    age: 22
};

let people2 = {
    name: "jarry",
    age: 33
};

console.log({people, people2});
console.log({...people, ...people2});


let a = [3, 1, 2];
console.log(a);
console.log(...a);

6、函数的拓展

(1)函数参数默认值
  可以在定义函数的同时,指定函数的默认值,调用时,若未传递参数,则使用默认值。

【举例:】

function test (x, y) {
    y = y || "hello";
    console.log(x + "======" + y);
}

test("tom");                      // 输出 tom======hello
test("tom", "helloworld");        // 输出 tom======helloworld


function test2 (x, y = "hello") {
    console.log(x + "======" + y);
}

test2("tom");                     // 输出 tom======hello
test2("tom", "helloworld");       // 输出 tom======helloworld

(2)rest 参数
  rest 参数 ,形式为 ...变量名, 用于接收多个参数,保存在数组中。
  若有多个参数,则 rest 必须放在最后,否则会报错。

【举例:】

function test (...values) {
    // for of 每次获取的是数组的值
    for (let j of values) {
        console.log(j);
    }
}

test(8, 7, 9);

function test2 (...values) {
    // for in 每次获取的是数组的下标
    for (let j in values) {
        console.log(values[j]);
    }
}
test2(8, 7, 9);

function test3 (...values, y) {            // 报错,SyntaxError: Rest parameter must be last formal parameter
    for (let j in values) {
        console.log(values[j]);
    }
}

(3)箭头函数
  ES6 支持 使用 箭头 => 定义函数。

【使用箭头函数定义 函数:】

var f = v => v;
console.log(f(3));   // 3

// 等价于
var f = function(v){
    return v;
}
console.log(f(3)); // 3

  如果没有参数、或者有多个参数,需使用圆括号 () 代替参数部分。
  如果方法体(代码块)只有一条语句,则 return 可以省略。

【使用 () 代替参数:】

var f = () => 5;
// 等价于
var f = function () {
    return 5 
};


var sum = (num1, num2) => num1 + num2;
// 等价于
var sum = function(num1, num2) {
    return num1 + num2;
};

  如果方法体(代码块)存在多条语句,则需要使用大括号 {} 括起来,并使用 return 返回值。

【举例:】

var fun = () => {
    let num = 7;
    let num2 = num + 3;
    return num + num2;
};
console.log(fun());  // 17

// 等价于
var fun = function() {
    let num = 7;
    let num2 = num + 3;
    return num + num2;
};
console.log(fun());   // 17

  若返回的是一个对象,则必须在对象外面加上圆括号 (),否则 {} 会被当成代码块被解析。

【举例:】

var getPeopleItem = id => ({ id: id, name: "Temp" });
console.log(getPeopleItem(3));   // {id: 3, name: "Temp"}

var getPeopleItem = id => { id: id, name: "Temp" };
console.log(getPeopleItem(3)); //  SyntaxError: Unexpected token :

   

  解构与箭头函数可以一起使用:

【举例:】

let people = {
    name: "tom",
    age: 22
};

let fun = (param) => {
    console.log(param.name + "==========" + param.age);
};
fun(people);

let fun2 = ({name, age}) => {
    console.log(name + "==========" + age);
};
fun2(people);

7、数组常用方法

  参考:https://www.cnblogs.com/l-y-h/p/12150578.html
(1)新增方法 -- reduce()
  Array.reduce(callback[, initialValue]) 用于给数组的每一个元素执行一个回调函数。
其中 :
  initialValue 为第一次执行 callback 时的参数值,可以省略。
  callback 有四个参数,callback(previousValue, currentValue, index, array).
  previousValue 指上一次执行回调的函数值,或者初始值。
  currentValue 指数组当前被处理的元素
  index 指当前数组元素的下标
  array 指当前的数组

【举例:】

let arr = [4, 6, 5];

let newArr = arr.reduce((previousValue, currentValue, index, array) => {
    console.log("上一次处理的值为: " + previousValue);
    console.log("当前处理的值为: " + currentValue);
    console.log("当前元素下标为: " + index);
    console.log("当前数组元素为: " + array[index]);
    return currentValue * 2;
});

console.log(newArr);

8、Promise 对象

(1)什么是 Promise ?
  Promise 是一个异步编程的一种解决方案。可以理解为一个容器,里面保存着未来才会结束的某个操作(异步操作)的结果,通过 Promise 对象可以获取异步操作的消息。

(2)Promise 特点
特点一:对象的状态不受外界影响。
 Promise 有三种状态,Pending (进行中)、Resolved (解决)、Rejected (失败)。只有异步操作的结果能决定 Promise 处于哪种状态,其余操作无法改变该状态,无法中途取消操作。

特点二:状态改变后,不再变化。
  状态改变的情况,Pending -> Resolved 、 Pending -> Rejected。
  一旦状态改变,其不会再改变。

(3)Promise 缺点
  无法取消Promise,一旦新建便会执行,无法中途取消。
  如果不设置回调函数,Promise内部的错误不会向外抛出。
  处于Pending时,无法判断该操作是刚开始还是即将完成。

(4)如何使用?
  需要使用 new 去实例化一个 Promise 对象,参数为 一个函数。
  函数的参数为 resolve、reject ,参数为两个函数,由 JavaScript 引擎提供。
  resolve 函数是改变状态, Pending -> Resolved ,即异步操作成功后调用,并将异步操作的成功结果作为参数向外传递。
  reject 函数也是改变状态, Pending -> Rejected,即异步操作失败后调用,并将异步操作的失败结果作为参数向外传递。
  使用 then 方法可以处理 resolve、reject 传递出来的结果。其接受两个回调函数作为参数。第一个回调函数用来处理 resolve 传递的结果,第二个回调函数用来处理 reject 传递的结果。第二个回调函数可选。
  一般情况下,使用 catch 方法处理 reject 传递出来的结果。其作用等价于 then 方法中的第二个回调函数。

【格式:】

var promise = new Promise((resolve, reject) => {
    if(异步操作成功) {
        resolve(data);
    } else {
        reject(error);
    }
});

promise.then((data) => {
    // 成功的操作
}).catch((error) => {
    // 失败的操作
});

9、模块化

(1)什么是模块化?

  模块化就是把代码进行拆分,方便重复利用。类似于 Java 中的各种 jar 包。
  模块化两个主要命令:export、import。
    export:用于规定模块的对外接口,即通过 export 可以获取模块的内容。
    import:用于导入模块,即通过 import 可以与其他模块建立起联系。

(2)export 命令
  通常一个模块就是一个文件,该文件内部的变量、数组、对象 等外界都不能获取。需要使用 export 将其导出。
  export 可以导出 基本类型变量、函数、数组、对象等。

【导出方式一:】

export var test = "hello";
    
【导出方式二:】

var a = "hello";
var b = [1, 2, 3];
export {a, b};

导出方式一、导出方式二 对外暴露的接口名 为 变量名、函数名等。
使用 import 要填写正确的接口名,才能正常引用模块,否则会出错。

【导出方式三:(使用 as 自定义接口名)】

var a = "hello";
var b = [1, 2, 3];
export {
    a as AA,
    b as BB
};

【导出方式四:(使用 default 可以忽略接口名,此时 import 可以自定义接口名)】

export default {
    var a = "hello";
    var b = [1, 2, 3];
}

(3)import 命令
  export 定义了模块对外的接口后,可以使用 import 导入相应的模块功能。

【导入方式一:(使用 {} 可以一次接受多个模块接口名)】

import {a, b} from 'xx/xx.js';

【导入方式二:(使用 as 取名)】

import {a as AA, b as BB} from 'xx/xx.js';

【导入方式三:(对于 export default 的模块,可以任意取名)】

import test from 'xx/xx.js';

未完待续...