
JS-ES6特性
ES6 简介
ES 全称 ECMAScript,是一套脚本语言的标准化规范。严格来说,ES 不仅是 JS 的语言标准,还包括其他脚本语言的语言标准。
2015年6月,ES6 正式发布,也可以叫做 ES2015,是新的 JS 语法标准,ES6 泛指 ES2015 以及后续版本。jQuery 偏向于 ES3,现在主流的 Vue.js 和 React.js 的默认语法都是 ES6。
ES6 的改进:
- 通过 
let, const关键字优化了变量提升的特性。 - 增加了新功能,比如:常量,作用域,异步处理,类,继承等。
 - 之前的语法过于松散,相同的功能可能会写出不同的代码。
 
ES6 的目标:让 JS 语言可以编写复杂的大型应用程序。
兼容性配置
写 ES6 语法的 JS 代码,通过 Babel 将 ES6 转换为 ES5。
参考:http://jspang.com/2017/06/03/es6/
ES5 巩固
严格模式
ES5 中新增的运行模式:strict mode,目的是为了消除 JS 语法中的不合理性和不安全之处,也是为了未来新版的 JS 做铺垫。
如果要使用,就将 use strict 放在脚本文件第一行即可。
模式特点
- 在正常模式种,对没有声明的变量赋值,默认就是全局变量,严格模式中,必须使用 
var显示声明变量。 - 在自定义函数中,禁止用 
this指向window。 - 创建 
eval作用域,eval()使用局部作用域,evalg()使用全局作用域。 - 禁止使用 
with语句。 - 构造函数必须通过 
new实例化对象。 - 无法删除变量。
 - 函数必须声明在顶层。
 
JSON
ES5 中新增的两个关于 JSON 的方法。
JSON.stringfy(obj/arr):JS 对象(数组) => JSON 对象(数组)JSON.parse(json):JSON 对象(数组) => JS 对象(数组)
Object 扩展
Object.create(prototype, [descriptors]):以指定对象为原型,创建新的对象,第二个参数可以添加新的属性。
var obj1 = {name: 'ethan', age: 21};
var obj2 = {sex: 'male'};
obj2 = Object.create(obj1);
obj3 = Object.create(obj1, {
    country: {
        value: 'Chine',
        writable: false, // 不可写
        configurable: true, // 可删除
        enumerable: true // 可枚举
    }
});
Object.defineProperties(object, descriptors):为指定对象定义扩展属性。
var obj = {firstName: 'ethan', lastName: 'lu'};
Object.defineProperties(obj, {
	 fullName: {
         get: function () {
             return this.firstName + '-' + this.lastName
         },
         set: function (data) {
             var names = data.split('-');
             this.firstName = names[0];
             this.lastName = names[1];
         }
     }
});
get 属性名(){}:返回当前属性值的回调函数set 属性名(){}:监视当前属性值变化的回调函数
var obj = {
    firstName: 'ethan', 
    lastName: 'lu'
    fullName: {
    get: function () {
        return this.firstName + '-' + this.lastName
    },
    set: function (data) {
        var names = data.split('-');
        this.firstName = names[0];
        this.lastName = names[1];
    }
});
console.log(obj.fullName);
obj.fullName = 'Echo Dee';
console.log(obj.fullName);
数组扩展
Array.prototype.indexOf(value):获取value在数组中第一次出现的下标。Array.prototype.lastIndexOf(value):获取value在数组中出现的最后一次下标。Array.prototype.forEach(function(item, index){}):遍历数组Array.prototype.map(function(item, index){}):遍历数组并返回一个新的加工后的数组。Array.prototype.filter(function(item, index){}):遍历并过滤出一个子数组。
改变 this 的指向
Function.prototype.bind(obj):将函数内的 this 绑定为 obj,并返回函数。
call(), apply(), bind()都可以改变this的指向,但是前两者为立即调用函数,最后一个在绑定完之后,不会立即调用,而是将函数返回,需要再加括号才能调用。
ES5 加入 bind() 的原因是该方法不会立即调用函数。
ES6 的变量声明
ES5 及之前,使用 var 定义全局变量。
ES6 中,使用 let 和 const 来定义变量:
let:定义局部变量const:定义常量(不可修改)
var 的问题
{
    var a = 1;
}
console.log(a); // 1
由于 var 声明的是全局变量,所以上述代码可以正常输出结果,由此也说明 var 声明的变量不具备块级作用域的特性。
var a = 1;
{
    var a = 2;
}
console.log(a); // 2
在外层和内层的作用域对同一个变量进行了定义和赋值,可能会污染 JS 的作用域,因此应该尽量避免 var 定义变量。
let 定义局部变量
{
    let a = 1;
}
console.log(a); // a is not defined
var a = 2;
{
    let a = 3;
}
console.log(a); // 2
let 定义的变量,只在块级作用域内起作用。
同一个块级作用域内,如果使用 let 关键字重复定义同一个变量,会报错。
const 定义常量
const blog = 'https://blog.ethanlooo.top';
用 const 声明的常量,只在块级作用域内起作用,而且在声明时必须赋值,否则报错。
let 和 const 的特点
- 不存在变量提升
 - 禁止重复声明
 - 支持块级作用域
 - 暂时性死区(在声明变量之前,该变量都是不可用的)
 
var let const 的共同点
- 全局作用域中定义的变量,可以在函数中使用
 - 函数中声明的变量,只能在函数及其子函数中使用
 
暂时性死区
使用 let 或者 const 声明的变量,会使区块形成封闭的作用域,如果在声明之前使用变量,会报错。暂时性死区 Temporal dead zone 就是这样一种机制,确保变量先声明,再使用。
变量的解构赋值
ES6 允许从数组或者对象中提取值,再将提取出来的值赋给变量。
解构:分解数据结构;赋值:给变量赋值。
数组的解构赋值
ES6 之前的写法
var arr = [1, 2, 3];
var a = arr[0];
var b = arr[1];
var c = arr[2];
ES6 之后的写法
let [a, b, c] = [1, 2, 3];
正常情况应该是一一对应进行赋值,但是如果左边的数量大于右边数组的长度,多余的变量就是 undefined。
左边允许有默认值,如下
{
    let [name = 'ethan'] = [];
    console.log(name); // ethan
}
{
    let [a, b] = [1];
    console.log(a + ', ' + b); // 1, undefined
}
{
    let [a, b = 2] = [1];
    console.log(a + ', ' + b); // 1, 2
}
{
    let [a, b = 2] = [1, undefined];
    console.log(b); // 2
}
{
    let [a, b = 2] = [1, null];
    console.log(b); // null
}
对象的解构赋值
将对象中的值按照属性匹配的方式提取出来。
ES6 之前:
var name = json.name;
var age = json.age;
var sex = json.sex;
ES6 之后:
const person = {name: 'ethan', age: 21};
let {name, age} = person;
同样,左边的变量数如果大于右边的对象的属性数,就会被赋值 undefined。
const person = {name: 'ethan', age: 21};
let {name, age, sex} = person; // {'ethan', 21, undefined}
也可以自定义变量名,不一定和属性名一致。
const person = {name: 'ethan', age: 21};
let {name: myName, age: myAge} = person;
如果变量名在解构前使用过,会报错:
let name = 'ethan';
{name} = {name: 'echo'}; // 报错
外层添加圆括号即可
let name = 'ethan';
({name} = {name: 'echo'});
console.log(name); // echo
字符串结构
const [a, b, c, d] = 'ethan';
console.log(a); // e
console.log(b); // t
箭头函数
语法:
(参数1, 参数2, ...) => { 函数体 }
- 
如果只有 1 个参数,可以省略小括号。
 - 
如果函数体只有 1 条语句,且是
return语句,可以省略大括号。 
传统的定义和调用:
function fc(a, b){
	console.log('hi');
    return a + b;
}
console.log(fc(1, 2)); // 3
ES6 中的写法:
const fc = (a,b) => {
	console.log('hi');   
    return a + b;
};
console.log(fc(1, 2)); // 3
ES6 中的函数简化版:
const fc = a => a + 10;
console.log(fc(1)); // 11
不过一般不会省略小括号,VSC 的自动格式化也会加上小括号。
this 指向
ES6 之前:this 指向的是调用函数的对象。
ES6 的箭头函数:函数本身不绑定 this,this 指向的是箭头函数定义位置的 this。
const obj = {name: 'ethan'};
function fc1() {
    console.log(this); // obj
    return () => {
        console.log(this); // obj
    }
}
const fc3 = (a) => {
    console.log(this); // Window
}
const fc2 = fc1.call(obj);
fc2();
fc3(1);
参数默认值
传统写法:
function fc(name) {
    var p = name || 'ethan';
    console.log(p);
}
ES6 写法:
function fc(name = 'ethan'){
	console.log(name);
}
注意,有默认值的参数后面的参数,也必须要有默认值。
剩余参数
剩余参数允许把不确定数量的多余的参数放到一个数组中。
传统写法:
function fc(a, b, c){
    console.log(a);
    console.log(b);
    console.log(c);
    console.log(d); // 报错
}
ES6 写法:
const fc = (a, b, ...args) => {
    console.log(args[0]); // 3
    console.log(args[1]); // 4
}
fc(1, 2, 3, 4);
剩余参数和解构赋值配合:
const names = ['ethan', 'echo', 'elisa'];
let [p1, ...p2] = names;
console.log(p1); // 'ethan'
console.log(p2); // ['echo', 'elisa']
扩展运算符
扩展运算符将数组或对象拆分成逗号分隔的参数序列。
const nums = [1, 2, 3];
console.log(...nums); // 1 2 3
可以使用扩展运算符实现数组的深复制。
let arr1 = [1, 2, 3];
let arr2 = [...arr1];
还可以快速合并数组。
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
// 方法1
let arr3 = [...arr1, ...arr2];
// 方法2
arr1.push(...arr2);
string 扩展
ES6 新增的字符串方法:
- 
includes(str):判断是否包含指定的字符串 - 
startsWith(str):判断是否以指定字符串开头 - 
endsWith(str):判断是否以指定字符串结尾 - 
repeat(count):重复指定次数 
number 扩展
- 
二进制用
0b开头,八进制用0o开头 - 
Number.isFinite(i):判断是否为有限大的数。比如Infinity这种无穷大的数,返回的就是 false。 - 
Number.isNaN(i):判断是否为NaN。 - 
Number.isInteger(i):判断是否为整数。 - 
Number.parseInt(str):将字符串转换为对应的数值。 - 
Math.trunc(i):去除小数部分。 
array 扩展
- 
Array.from(arr):将伪数组或者可遍历对象转换成真数组 - 
array.findIndex(function(currentValue, index, arr), thisValue):返回符合测试条件的第一个数组元素的下标 - 
array.find(function(currentValue, index, arr),thisValue):返回符合测试条件的一个数组元素 
object 扩展
Object.is(x, y):判断两个变量是否完全相等,底层是通过字符串判断。Object.assign():可以将可枚举属性的值从一个或多个对象分配到目标对象。
Set 数据结构
集合,非重复数组。
let mySet = new Set(); // 新建 Set
mySet.add(1); // 添加元素
mySet.add(2);
mySet.add(2);
console.log(mySet); // {1, 2}
console.log(mySet.has(1)); // true
console.log(mySet.has(3)); // false
console.log(mySet.size); // 2
Symbol 数据结构
ES6 引入的数据结构,表示独一无二的值,解决命名冲突的问题,不能和其他数计算。
构造函数:const symbol = Symbol(),括号里可以加字符串作为符号描述。
作为对象属性
let obj = {
    name: 'ethan',
    age: 21
}
let symbol = Symbol();
obj[symbol] = 1; // 只能通过属性选择器给对象添加 Symbol 属性,不可能写成 obj.symbol = 1
传参标识
let symbol1 = Symbol();
let symbol2 = Symbol();
console.log(symbol1 == symbol2); // false
let symbol3 = Symbol('three');
let symbol4 = Symbol('four');
console.log(symbol3); // Symbol(three)
console.log(symbol4); // Symbol(four)
Promise 入门
JavaScript 的执行环境是单线程的,即任务都是依次执行,无法并行。
异步模式可以一起执行多个任务,常见的异步模式有:定时器,接口调用,事件函数。
JS 中常见的接口调用方式:
- 原生 Ajax
 - 基于 jQuery 的 Ajax
 - Fetch
 - Promise
 - Axios
 
ES5 中,如果异步调用的结果存在以来,需要进行多层嵌套回调,会导致代码层次过多,难维护,而且会导致回调地狱。
ES6 中的 Promise 可以解决这个问题。
概述
ES6 中的 Promise 是异步编程的一种方案,Promise 是一个对象,可以获取异步操作的消息。
通过 Promise 对象,可以用同步的形式来书写异步代码。
基本用法
new一个 Promise 对象,传递一个参数,参数为一个用于处理异步任务的函数。- 并且传入两个参数:
resolve和reject,分别表示异步执行成功的回调函数和异步执行失败后的回调函数。 - 通过 
promise.then()处理返回结果。 
异步任务处理
// 1. 封装 model 层的接口
const promise = new Promise((resolve, reject) => {
  // 用定时器代替 Ajax 请求接口
  setTimeOut(function () {
    var data = { retCode: 0, msg: "hello!" }; // 模拟接口返回的数据
    if (data.retCode == 0) {
      resolve(data);
    } else {
      reject({ retCode: -1, msg: "network error" });
    }
  }, 100);
});
// 2. 业务层的接口调用
promise
  .then((data) => {
    // 从 resolve 获取正常结果
    console.log(data);
  })
  .catch((data) => {
    // 从 reject 获取异常结果
    console.log(data);
  });
封装 Ajax 请求
const request = require("request");
const request1 = function () {
  const promise = new Promise((resolve, reject) => {
    request("https://www.ethanloo.cn", function (response) {
      if ((response.retCode = 200)) {
        resolve("request success," + response);
      } else {
        reject("failed");
      }
    });
  });
  return promise;
};
request1().then((res1) => {
  console.log(res1);
  return request2();
});
基于 Promise 处理多次 Ajax 请求
通过 Promise,可以使多层嵌套调用按线性方式书写,把多层嵌套调用改进为链式调用。
例如有 3 个请求,请求 2 依赖于请求 1 的结果,请求 3 依赖于请求 2 的结果,传统写法会陷入回调地狱。
const request = require("request");
const request1 = function () {
  const promise = new Promise((resolve, reject) => {
    request("https://www.baidu.com", function (response) {
      if ((response.retCode = 200)) {
        resolve("request1 success," + response);
      } else {
        reject("failed");
      }
    });
  });
  return promise;
};
const request2 = function () {
  const promise = new Promise((resolve, reject) => {
    request("https://www.google.com", function (response) {
      if (response.retCode == 200) {
        resolve("request2 success," + response);
      } else {
        reject("failed");
      }
    });
  });
  return promise;
};
const request3 = function () {
  const promise = new Promise((resolve, reject) => {
    request("https://www.yahoo.com", function (response) {
      if (response.retCode == 200) {
        resolve("request3 success," + response);
      } else {
        reject("failed");
      }
    });
  });
  return promise;
};
// 逐个进行请求并打印返回结果
request1()
  .then((res1) => {
    console.log(res1);
    return request2();
  })
  .then((res2) => {
    console.log(res2);
    return request3();
  })
  .then((res3) => {
    console.log(res3);
  });
return 的函数返回值
return 有两种可能的返回值:
- 返回 Promise 实例对象,然后该对象调用下一个 
then - 返回普通值,返回的值会传递给下一个 
then,通过then参数中函数的参数接收 
第一种情况类似上面的多 Ajax 请求;
第二种返回普通值的情况:
function queryData(url) {
  return new Promise((resolve, reject) => {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function () {
      if (xhr.readyState != 4) return;
      else if (xhr.readyState == 4 && xhr.status == 200) {
        resolve(xhr.responseText);
      } else {
        reject("failed");
      }
    };
    xhr.responseText = "json";
    xhr.open("get", url);
    xhr.send(null); // 请求接口
  });
}
queryData("https://www.ethanlooo.top")
  .then(
    (data1) => {
      console.log(JSON.stringify(data1));
      return queryData("https://blog.ethanlooo.top");
    },
    (error1) => {
      console.log(error1);
    }
  )
  .then(
    (data2) => {
      console.log(JSON.stringify(data2));
      return "hello, ethan";
    },
    (error2) => {
      console.log(error2);
    }
  )
  .then((data3) => {
    console.log(data3); // hello, ethan
  });
第一个请求结束,返回的是 Promise 的实例对象,因此第二个请求会正常进行。
第二个请求结束,返回的是一个普通的字符串,为了确保继续进行链式操作,在第三个 then 前面会产生一个新的默认的 Promise 的实例对象来调用。同时,上一个返回的普通字符串会作为参数 data3 传入第三个函数。
Promise 常用实例方法
promise.then():获取异步任务的正常结果。promise.catch():获取异步任务的异常结果。promise.finally():异步任务无论成功与否,都会执行。
function queryData(url) {
  return new Promise((resolve, reject) => {
    setTimeout(function () {
      let data = { retCode: 0, msg: "ethanloo" }; // 模拟接口返回的数据
      if (data.retCode == 0) {
        resolve(data);
      } else {
        reject({ retCode: -1, msg: "error" });
      }
    }, 100);
  });
}
queryData()
  .then((data) => {
    console.log("success");
    console.log(data);
  })
  .catch((data) => {
    console.log("failure");
    console.log(data);
  })
  .finally(() => {
    console.log("whatever");
  });
Promise 常用类方法
Promise.all():并发处理多个异步任务,所有任务成功才能得到结果Promise.race():并发处理多个异步任务,只要有一个任务成功就得到结果
let promise1 = queryData(api1);
let promise2 = queryData(api2);
let promise3 = queryData(api3);
Promise.all([promise1, promise2, promise3]).then((result) => {
    console.log(result);
});
Promise.race([promise1, promise2, promise3]).then((result) => {
    console.log(result);
});
ES7 async 异步函数
async意思是异步,加上该关键词的函数的返回值是 Promise 实例对象await只能存在于async函数中,用于等待一个异步任务完成的结果。
async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。
const request1 = function () {
  const promise = new Promise((resolve) => {
    request("https://www.ethanloo.cn", function (response) {
      if (response.retCode == 200) {
        resolve("request1 success, " + response);
      } else {
        reject("failure");
      }
    });
  });
  return promise;
};
async function queryData() {
  const response = await request1();
  return response;
}
queryData().then((data) => {
  console.log(data);
});