一、ES6介绍
Javscript分为三大部分:ECMAScript + DOM + BOM(ES就是指 ECMAScript)
- ECMAScript就是一种语法标准,规定了这个语言的语法要如何书写,何种语法有何种作用。之前学习的很多东西都是在ES5的标准里面的。5只是ECMAScript的其中一个版本,而6也是其中一个大版本。只是ES6和ES5相差的比较大,并且也相对好用,所以企业的使用面积非常大。
- ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等,而 ES2015 则是正式名称,特指该年发布的正式版本的语言标准。本书中提到 ES6 的地方,一般是指 ES2015 标准,但有时也是泛指“下一代 JavaScript 语言”。
- 也就是说从2015年以后推出的新标准,现在统称为ES6
二、ES6变量声明
2.1、var关键字(变量可修改)
1、有预解析,可以先使用,后声明
2、可以重复定义同一个变量,逻辑错误,第二次应该是修改变量,而不是定义
3、没有块级作用域,会挂载到window全局变量污染
2.2、let关键字(变量可修改)
es6中为了统一并提高代码的安全性,引入了let关键字来代替var声明变量
1、没有预解析,声明后才能使用
2、不能重复声明
3、拥有块级作用域{}不会造成全局变量污染
2.3、const关键字(常量不可修改)
ES6中,为了让程序可以执行起来更加高效,推出了const关键字来声明一个只读的常量
1、没有预解析,声明后才能使用
2、一旦声明必须赋值
3、拥有块级作用域不会造成全局变量污染
4、声明引用类型的地址不能修改但值却可以修改
const Arr = [10, 20, 30];Arr[1] = 40;console.log(Arr); //[ 10, 40, 30 ]
2.4、块级作用域案例
var btns = document.querySelectorAll('button')for(var i = 0; i < btns.length; i++){btns[i].onclick = function(){console.log(i)}}
此时我们点击按钮,输出的i不会是每个按钮对应的按钮,而是按钮的个数。就是因为for里面的变量是var声明的,没有形成块级作用域,每次访问i都是访问的全局的i。如果我要在事件里面得到按钮的索引,就需要别的方法来实现。如果我们换成let
let btns = document.querySelectorAll('button')for(let i = 0; i < btns.length; i++){btns[i].onclick = function(){console.log(i)}}
此时我们点击按钮,就能输出按钮对应的索引了。这是因为我们使用了let关键字,let会在{}之间形成一个块级作用域,for循环执行多少次,我们就会形成多少个块级作用域,每个i对应1个,当我们在事件里面获取i的时候,i会到事件函数的上级作用域找,刚好就是每个i对应的块级作用域。我们就简单地得到了每个按钮对应的索引。
三、模板字符串
`固定字符${变量或者表达式}`
- 在模板字符串中,可以解析
${}之间的变量或者表达式 - 在整个字符串中允许换行
四、解构
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(简化代码)。
4.1、对象解构
let obj = {name: '张三',age:18};// let name = obj.name;// let age = obj.age;// 将对象里的每个key对应的value赋值给每个同名的变量(等效于上面的写法)// let {name, age} = obj;//部分解构之后重命名(不重命名log打印不到let里面的name)let {name:newName} = obj;//解构内置对象里面的方法let {random} = Math; //{random:fn}console.log(random());
4.2、对象解构反向操作
将多个变量的打包成一个对象
let name = "张三",age = 18;function ff() {console.log('函数');}// ES5 写法let obj = {name: name,age: age,ff: ff}
如果一个对象的属性名和外面的一个变量名同名,可以直接将变量名作为属性名,并会自动地把变量的值作为属性的值
// es6 写法let obj = {name,age,ff};
4.3、数组解构
let arr = [10, 20, 30];// 部分解构let [b] = arr;console.log(b); // 10(默认按顺序解构)let [,b] = arr;console.log(b); //20(用逗号占位)// 复合解构let arr2 = [10,20,[30,40]];let [a,b,[c,d]] = arr2;// 利用数组解构交换两个变量的值let a=10,b=20;[newa,newb] = [b,c];(将两变量放进数组)console.log(a,b) // a=20,b=10
4.4、字符串解构
let str = 'xyz';let [a,b,c] = str;console.log(a,b,c); //x y zstr[1] = 'u';console.log(str); // 还是xyz 无法修改console.log(str[1]); // y
4.5、函数参数默认值和参数解构
4.5.1、函数形参的默认值
function add(a,b,c){a = a || 0;b = b || 0;c = c || 0;return a + b + c;}//上面的例子就可以写成:function add(a=0,b=0,c=0){ //参数 为 undefined 时 参数 赋值为 默认值return a + b + c;}
4.5.2、函数参数的解构
// 数组参数解构------顺序function hs([x, y, z]) {console.log(x, y, z);}hs([1, 2, 3]);// 对象参数解构------无序function fn({x, y, z}) { // {x, y, z} = obj 解构console.log(x, y, z);}fn({z: 4, x: 5, y: 6});
4.5.3、指定函数参数的默认值解构
function fn({name, age} = {}){ //防止不传实参时候的报错console.log(name, age);}fn(); //undefined undefined// fn(); //相当于传了一个null {name, age}=null 就会报错// fn({}); //不会报错,输出:undefined undefined
function fn2({name="张三", age=18} = {}){ //指定默认值console.log(name, age);}fn2(); //张三 18
五、rest 参数和拓展运算符
5.1、rest 参数
arguments 对象:
function fn(){console.log(arguments);// 伪数组存所有传进来实参}fn(10, 20, 30, 50, 60);
ES6提供了新的方法:使用rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
1、rest得到的是真数组,可以用forEach
2、…rest必须是最后一个形参(最后一个形参获取剩余所有参数)
function hs( a, b ,...rest){ // 把剩余的参数都交给restconsole.log(rest);}hs(10, 20, 30, 50, 60);function hs2(...rest){ // rest 接收所有参数作为一个数组rest.forEach(function (item) {console.log(item);});}hs2(60, 70, 80, 90);
5.2、… 拓展运算符
ES6中的…扩展运算符,它的作用就是可以将数组或者对象展开,拆开成为一个一个单独的数据。
let arr = [10, 20, 30];console.log(arr[0],arr[1],arr[2]);// 快速将一个数组拆开成一个一个的元素console.log(...arr); // 10, 20, 30 上述代码的简写// 快速将一个对象里面的数据复制一份到一个新的对象里面let obj = {name: '张三', age: 18}console.log({id:1 , ...obj});// 将一个字符串拆开成为多个单独的字符let str = 'abc'console.log(...str);// 函数传参使用拓展运算符console.log(Math.max(10,20,30)); //30 取最大值console.log(Math.max(...arr));
使用场景:
// 1、合并数组(不影响原数组)let arr1 = [1, 2, 3];let arr2 = [10, 20, 30];let newArr = [...arr1, ...arr2]; //[1, 2, 3, 10, 20, 30]// 2、合并对象(不影响原对象)let obj1 = {name: "张三1",age: 18}let obj2 = {name: "张三2",email: "zhangsan@163.com"}let newObj1 = {...obj1,...obj2};//注意点: 对象的key不能重复的,张三2的会覆盖前面张三1console.log(newObj1); // {name: '张三2', age: 20, email: 'zhangsan@163.com'}//Object.assign();对象的合并, 将第二个以及后面的对象参数合并到第一个对象参数中let newObj2 = Object.assign({}, obj1, obj2); //第一个参数传{}目的: 不改变原对象console.log(newObj2); // {name: '张三2', age: 20, email: 'zhangsan@163.com'}
5.3、… 在解构赋值中的使用
数组解构赋值
按顺序把数组里面的元素解构到等号左边的变量里面,当左边的变量不够,会把剩下的数据放到d这个数组里面,此时d是就一个新数组。
let arr = [1, 2, 3, 4, 5, 6, 7];let [a, b, c, ...d] = arr;console.log(a, b, c, d); // 1 2 3 [4, 5, 6, 7]
字符串解构赋值
let str = '一个字符串';let [a, b, c, ...d] = str;console.log(a, b, c, d); // 一 个 字 ['符', '串']
六、箭头函数
6.1、基本语法
ES6 允许使用 “箭头”(=>)简化函数的定义,让我们在使用回调函数的时候更简单。
箭头函数不可以使用 arguments 获取参数列表,可以使用 rest 参数代替。
箭头函数语法: (参数) => { 函数体 }let fn = ()=>{console.log("我是一个箭头=>");}fn();
简写方式:
// 无参数无返回let fn1 = () => console.log('fn1');fn1();// 无参数有返回:如果函数体里面只有一个语句,可以省略大括号不写, 并且他会默认返回 => 符号后面的数据。let fn2 = () => 'fn2';console.log(fn2());// 有一个参数无返回:形参个数如果为1个,可以省略小括号不写。let fn3 = x => console.log('fn3', x);fn3(2);// 有多个参数有返回:如果函数体有多个语句,则不能省略大括号。let fn4 = (x, y) => {let sum = x + y;return sum + 'fn4';};console.log(fn4(1, 2));// 如果return的是单一个对象,则需要加上大括号和return,例如:// let fn5 = (x, y) => {a:,x b:y}; //报错let fn6 = (x, y) => {return { a: x, b: y };};console.log(fn6(1, 2));
使用场景(回调函数):
// 定时器中的回调函数setInterval(() => {console.log("我用了箭头函数");}, 1000);// forEach中的回调函数var arr = [22, 32, 11, 3, 5, 7, 88];arr.forEach(item => console.log(item));
6.2、this指向
this指向总结:1、this一般写在函数中 2、this永远指向一个引用地址(内存空间)
- 全局使用(函数全局调用)指向window
// 全局使用---------window(全局对象)console.log(this);function fn(){console.log(this);}fn();
// 定时器中使用---------window(全局对象)window.setTimeout(function () {console.log(this);}, 1000)
- 对象调用指向该对象(事件中的事件源)
// 事件中使用----------事件源btn.onclick=function(){console.log(this);}
// 对象方法调用,函数作用域内的this--------指向对象var name = "全局变量";let obj = {name: "局部变量",myThis: this,ff:function(){console.log(this.name); // 局部变量},ff2(){ // 简写console.log(this.name); // 局部变量}// 网上甚至有文档说,ff2(){}是箭头函数简写?????----错误}obj.ff(); // this指向objobj.ff2(); // this指向objconsole.log(obj.myThis); // this指向window(obj属性myThis: this)
- 箭头函数没有自己的作用域,即箭头函数 this 指向其外层作用域
var name = "全局变量";let obj = {name:"局部变量",// 对象方法不能使用箭头函数ff:()=>{console.log(this.name); // 没有自己的作用域,this 指向其外层作用域}}obj.ff(); // this指向外层作用域(所以创建字面量对象,不适合书写箭头函数)
6.3、箭头函数 this 指向案例
需求: 点击盒子,1s之后,改变这个盒子的宽度
<div id="box" style="width: 50px; height: 50px"></div>
// ES5解决: 存储thisbox.onclick = function () {// console.log(this);// 事件源----触发对象var _this = this;setTimeout(function () {// console.log(this);// 指向window// this.style.width = "300px"; // 用this修改不了_this.style.width = "300px";}, 1000)}
// ES6解决: 箭头函数(没有this,就没有灵魂)-----最强大的功能:改变this执行,指向外层作用域box.onclick=function(){// console.log(this);// 事件源setTimeout(()=>{// this----->理论上当前作用域(window)---改变this指向--->指向了事件源(外层作用域)console.log(this);this.style.width = "300px"; // 用this修改成功},1000)}
七、Promise对象
7.1、Promise产生背景
我们如果在进行ajax请求的时候,一个效果需要有多个请求按照一定的顺序完成,如果不使用Promise实现,做起来就容易形成回调地狱
如:
$.ajax({url:'url1',success(res){if(res.code == 200){// 再次发起请求$.ajax({url:'url2',success(res){if(res.code === 200){// 再次发起请求$.ajax({url:'url3',success(res){if(res.code === 200){// ..................}}})}}})}}})
这样的代码是非常恶心的,将来想要维护的时候,难度非常高,所以我们不推荐这样的写法。
小结:Promise的出现是用来解决异步回调的多层嵌套问题的(地狱回调)
7.2、Promise三个状态两个特点
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
功能:避免了回调地狱,把异步代码改成调用起来像同步代码。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
**Promise** 对象有三种状态:
- pending: 初始状态,既不是成功,也不是失败状态。
- fulfilled: 意味着操作成功完成。
- rejected: 意味着操作失败。
**Promise**对象有两个特点:
- 对象的状态不受外界影响。
Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。 - 一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
/*语法:Promise------承诺var p = new Promise(回调函数);回调函数也有两个参数 :resolve --------- 坚定信念reject --------- 拒绝爱情这两个参数也是函数*/var p = new Promise((resolve, reject) => {// ***默认状态----pending 悬而未决的// ***成功状态----fulfilled 满足了resolve(); // pending----->fulfilled// ***失败状态----rejected 拒绝,不开心了reject(); // pending----->rejected});console.log(p);
小结:
- Promise是一个构造函数,new出来实例对象可以帮我们解决异步操作的一些问题
- 当我们遇到异步操作的时候,都可以使用Promise来加工,便于后期的代码书写
7.3、Promise的基本使用
Promise的基本语法:
// 假设: flag是ajax的返回状态let flag = true;let p = new Promise((resolve, reject) => {if (flag) {resolve("假装有返回数据");} else {reject("假装抛出异常");}console.log("Promise"); // 执行顺序1 **** Promise 本身是同步的文档自上而下执行所以先执行})// 执行顺序3 **** .then() .catch() 是异步的最后执行// .then()方法处理成功的回调------也就是resolve打包的数据// .catch()方法处理失败的回调-----也就是reject暴露的异常p.then(data => {console.log(data); // data就是成功时候抛出/打包/暴露的数据}).catch(error => {console.log(error); // error就是失败时候暴露的异常信息})// 上述代码只是基本语法console.log("全局"); // 执行顺序2 **** 全局也是同步的
7.4、使用Promise解决回调地狱
Promise的then链式调用的特点:
1、第一个then执行完会执行第二个then
2、then里面的函数的返回值,会被下一个then的形参接收
3、如果返回的是一个promise对象,下一个then的形参接收到的不是这个promise对象,而是这个promise对象内部调用resolve时候的实际参数(拿到的数据)
7.4.1、解决多重请求(回调地狱)
let p1 = new Promise((resolve, reject) => {$.ajax({url: "http://baidu.com/aaa",type: "GET",success(res) {resolve(1);},error(err){reject(err);}})})let p2 = new Promise((resolve, reject) => {$.ajax({url: "http://baidu.com/aaa",type: "GET",success(res) {resolve(2);},error(err){reject(err);}})})let p3 = new Promise((resolve, reject) => {$.ajax({url: "http://baidu.com/aaa",type: "GET",success(res) {resolve(3);},error(err){reject(err);}})})// 如何解决回调地狱----将异步代码改成看起来像同步代码(方便维护)p1.then(data1=>{console.log(data1);return p2; // 改变下一个.then作用的对象}).then(data2=>{console.log(data2);return p3;}).then(data3=>{console.log(data3);}).catch(err=>{console.log(err.responseText);})
7.4.2、解决多重请求的简化写法
function getPromiseObj(url,test) {return new Promise((resolve, reject) => {$.ajax({url, // 对象简写type: "GET",success(res) {resolve(test); // test用于测试返回的数据便于观看才这样写},error(err) {reject(err);}})})}let p1 = getPromiseObj("http://baidu.com/aaa",1);let p2 = getPromiseObj("http://baidu.com/aaa",2);let p3 = getPromiseObj("http://baidu.com/aaa",3);// 如何解决回调地狱----将异步代码改成看起来像同步代码(方便维护)p1.then(data1 => {console.log(data1);return p2; // 改变下一个.then作用的对象}).then(data2 => {console.log(data2);return p3;}).then(data3 => {console.log(data3);}).catch(err=>{console.log(err.responseText);})
小结: 我们可以通过多个promise调用then方法来把地狱回调转化为链式编程,便于后期的维护
7.5、Promise的all方法和race方法
// 这里用定时器setTimeout,时间可控let p1 = new Promise((resolve, reject) => {setTimeout(() => {// resolve("成功1"); // 这里可以切换状态测试reject("失败1");}, 3000)})let p2 = new Promise((resolve, reject) => {setTimeout(() => {// resolve("成功2");reject("失败2");}, 2000)})let p3 = new Promise((resolve, reject) => {setTimeout(() => {resolve("成功3");// reject("失败3");}, 1000)})
Promise.all方法: (类似&&的关系 )
1、如果多个异步程序都是成功状态, p的状态就是成功, 多个异步程序的成功结果会打包成一个数组统一返回
2、但凡发现一个失败,最快直接返回第一个失败的结果
// 场景:页面一进来,就要加载三个ajax,只有三个全部成功,才可以渲染页面// all里面的数组接收三个异步程序的结果,用来统一处理多个异步程序let p = Promise.all([p1,p2,p3]); // 试一下切换状态!!!p.then(res=>{console.log(res);}).catch(err=>{console.log(err);})
Promise.race方法:(类似||的关系)
1、有一个实例率先改变状态,p的状态就跟着改变,谁快返回谁 。
let p = Promise.race([p1,p2,p3]); // 试一下切换状态!!!p.then(res=>{console.log(res);}).catch(err=>{console.log(err);})
7.6、异步代码同步化
async函数和await关键字一般成对出现,当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
// 使用定时器模拟ajax , 时间就是响应成功的时间let p1 = new Promise((resolve, reject) => {setTimeout(() => {resolve("成功1");}, 3000)})let p2 = new Promise((resolve, reject) => {setTimeout(() => {resolve("成功2");}, 2000)})let p3 = new Promise((resolve, reject) => {setTimeout(() => {resolve("成功3");}, 1000)})// 需求: p1 p2 p3 三个ajax异步程序 顺序执行(同步代码执行顺序)// async await 一组关键字// 重点(项目中要用)*****// async 用来修饰函数,表示这是一个异步函数// await 在异步函数中使用,表示同步代码(异步程序变成同步代码)// ------await后面的异步执行完毕才会执行后续的同步代码async function getVal() {// p1 p2 p3 三个ajax异步程序 顺序执行await p1.then(res => console.log(res));await p2.then(res => console.log(res));await p3.then(res => console.log(res));console.log("同步");}getVal(); // 成功1 成功2 成功3 同步
