1.1 ES6 介绍
ECMAScript 6.0(以下简称ES6) ,是 JavaScript语言 的下一代标准,已经在2015年6月正式发布了。它的目标,是使得javaScript语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
ES6 主要是为了解决 ES5 的先天性不足,比如 JavaScript 里并没有类的概念、变量提升、内置对象的方法不灵活、模块化实现不完善等。
1.2 let和const命令
let
-
let声明变量,没有变量提升
-
是一个块作用域(变量和函数生效的区域)
-
不能重复声明
const
- const声明常量,一旦被声明,无法修改
let和const的作用
-
解决for循环,用var声明变量i出现变量提升的问题(用var i = 0,console出现的结果是10,实际结果应是5)
const arr = []; for(let i = 0; i < 10; i++) { arr[i] = function() { return i; } }; console.log(arr[5]()); -
不会污染全局变量
let RegExp = 10; console.log(RegExp); console.log(window.RegExp);
建议:在默认情况下使用const,当你知道变量值需要被修改的情况使用let
1.3 模板字符串
使用tab间上面的反引号``,插入变量时使用${变量名}
const oBox = document.querySelector(".box");
let id = 1,
name = 'Isaac';
let htmlStr = `
<ul>
<li>
<p id = ${id}>${name}</p>
</li>
</ul>
`;
oBox.innerHTML = htmlStr;
优点:解决了拼接字符串繁琐的操作
用途:后端文本字符串追加到前端页面
1.4 函数之默认值、剩余参数
默认值
-
带默认值的函数
//es5的写法 function add(a, b) { a = a || 10; b = b || 20; return a + b; } console.log(add()); //es6的写法 function add(a = 20, b = 10) { return a + b; } console.log(add()); -
默认的表达式也可以是一个函数
function add(a, b = getVal(5)) { return a + b; } function getVal(val) { return val + 5; } console.log(add(10));
剩余参数
由三个...和一个紧跟着的具名参数指定 ,例如: ...keys
//es5写法
function pick(obj) {
let result = Object.create(null);
for(let i = 1; i < arguments.length; i ++) {
result[arguments[i]] = obj[arguments[i]]
}
return result;
}
let book = {
title: '新华字典',
author: 'Isaac',
year: 2020
}
let bookData = pick(book, 'title', 'year', 'author');
console.log(bookData);
ps:pick函数在遍历book对象并获取数据时,需要用到arguments(--[]实参列表,伪数组)的length来作为限制循环的条件。那么这时候问题来了,arguments作为此函数的实参列表,所存储的是该函数中所有的参数:arguments = [obj, 'title', 'year', 'author'],可以看出,obj其实并不是我们想要的,所以我们在遍历时要从下标为1的title开始,也就是i = 1,作为循环的起始条件。
es6 为我们提供了剩余函数的概念,应用这一概念可以很好的解决arguments的问题,如下:
function pick(obj, ...keys) {
//...keys 解决了arguments的问题
let result = {};
for(let i = 0; i < keys.length; i++) {//循环的起始条件i = 0
result[keys[i]] = obj[keys[i]];
}
return result;
}
let book = {
title: '新华字典',
author: 'Isaac',
year: 2020
}
let bookData = pick(book, 'year', 'author');
console.log(bookData);
1.5 函数之扩展运算符、箭头函数
扩展运算符
- 剩余运算符 vs 扩展运算符
剩余运算符:把多个独立的合并到一个数组中
扩展运算符:将一个数组分割,并将各个项作为分离的参数传给函数
//es5写法,处理数组中的最大值,使用apply
const arr = [10, 20, 50, 30, 90, 100, 40];
console.log(Math.max.apply(null, arr));
//es6写法(应用扩展运算符)
console.log(Math.max(...arr))
箭头函数
es6使用 => 来定义 function(){} 等于 () => {}
优点:使代码更简洁
//es5写法
let add = function(a, b) {
return a + b;
}
//es6写法(使用箭头函数)
//两个形参
let add = (a, b) => {
return a + b;
}
//或写成一行
let add = (a, b) => a + b;
//一个形参
let add = val => {
return val + 5;
}
//无参
let fn = () => 'hello world' + 123;
//以对象为函数参数时
let getObj = id => {
return {
id : id,
name : 'Isaac'
}
}
//或写成一行,注意参数为对象时,要使用{}
let getObj = id => ({id: id, name: 'Isaac'});
//闭包函数
//es5写法
let fn = (function () {
return function () {
console.log('hello es6')
}
})();
//es6写法
let fn = (() => {
return () => {
console.log('hello es6 2');
}
})();
fn();
1.6 箭头函数this指向和注意事项
箭头函数的this指向
箭头函数没有this绑定,es5中this指向:取决于调用该函数的上下文
//es5写法
let PageHandle = {
id: 123,
init: function () {
document.addEventListener('click', function(event) {
//报错:this.doSomeThings is not a function
this.doSomeThings(event.type);
}.bind(this),false)//可以通过.bind(this)来解决this指向问题
},
doSomeThings:function(type) {
console.log(`事件类型:${type},当前id:${this.id}`);
}
}
PageHandle.init();
此时在浏览器中运行代码,点击文档,console中会报错,原因是当前的this指向init这个函数中为它定义的执行期上下文对象(document)并不是我们想要的PageHandle,所以我们可以使用es6中的箭头函数来解决这个问题,如下:
let PageHandle = {
id: 123,
init: function () {
//箭头函数没有this指向,箭头函数内部this只能通过查找作用域链来确定,一旦使用箭头函数,当前不存在作用域链
document.addEventListener('click', (event) => {
this.doSomeThings(event.type);
}, false)
},
doSomeThings: function (type) {
console.log(`事件类型:${type},当前id:${this.id}`);
}
}
PageHandle.init();
注意事项
- 使用箭头函数 函数内部没有arguments
- 箭头函数不能使用new关键字来实例化对象
ps:function函数是一个对象,但是箭头函数不是一个对象,它其实就是一个语法糖
1.7 解构赋值
- 解构赋值是对赋值运算符的一种扩展
- 它针对数组和对象来进行操作
- 优点:代码书写上简洁易读
//es5写法
let node = {
type: 'iden',
name: 'foo'
}
let type = node.type;
let name = node.name;
//es6写法
//完全解构
let {type, name} = node;
//不完全解构
let obj = {
a:{name: "张三"},
b:[],
c:'hello, world'
}
let {a} = obj;
console.log(a);
let {a, ...res} = obj;
console.log(res);
//默认值
let {a, b = 30} = {a: 20};
//对数组解构
let arr = [1, 2, 3];
let [a, b, c] = arr;
console.log(a, b, c);
//可嵌套
let [a, [b]] = [1,[2],3]
1.8 对象的扩展功能
es6直接写入变量和函数,作为对象的属性和方法
const name = 'Isaac',
age = 20;
const person = {
name, //等价于es5写法中的 name: name
age,
sayName() {
console.log(this.name);
}
}
person.sayName();
对象的方法
- is() === 用于比较两个值是否严格相等
- assign() 用于对象的合并,用法:Object.assign(target, obj1, obj2...)
1.9 Symbol类型
原始数据类型Symbol,它表示是独一无二的值
最大的用途:用来定义对象的私有变量
const name = Symbol('name');
const name2 = Symbol('name');
console.log(name === name2);
ps:上述代码可Chrome的控制台中输出结果为false,可以证明Symbol数据类型的变量是独一无二的
let s1 = Symbol('s1');
let obj = {
[s1]:'Isaac'
};
console.log(obj[s1]);
ps:如果用Symbol定义的对象中的变量,取值时一定要用[变量名]
//获取Symbol声明的属性名(作为对象的key)
let s = Object.getOwnPropertyDescriptor(obj);
let m = Reflect.ownKeys(obj);
1.10 Set集合数据类型
集合:无重复值的有序列表
let set = new Set();
//添加元素
set.add(2);
set.add('4');
set.add(['hello', 'world', 3]);
//删除元素
set.delete(2);
//校验某个值是否在set中
console.log(set.has('4'));
//将set转换成数组
let set2 = new Set([1, 2, 3, 3, 3, 4])
//扩展运算符
let arr = [...set2];
console.log(arr);
ps:set中对象的引用无法被释放
let set3 = new Set(),
obj = {};
set3.add(obj);
//释放当前的资源
obj = null;
console.log(set3);
解决这个问题,可以使用WeakSet()来代替Set()集合
let set4 = new WeakSet(),
obj = {};
set4.add(obj);
//释放当前的资源
obj = null;
console.log(set4);
weakSet
- 不能传入非对象类型的参数
- 不可迭代
- 没有forEach()
- 没有size属性
1.11 Map数据类型
Map类型是键值对的有序列表,键和值是任意类型
let map = new Map();
map.set('name', 'Isaac');
map.set('age',20);
console.log(map.get('name'));
console.log(map);
map.has('name'); //true
map.delete('name');
map.clear();
console.log(map);
1.12 数组的扩展功能
- from
将伪数组转换成真正的数组
function add() {
console.log(arguments);
//es5转换
//let arr = [].slice.call(arguments);
//console.log(arr);
//es6写法
let arr = Array.from(arguments);
console.log(arr);
//或者使用扩展运算符 将伪数组转换成真正的数组
console.log([...arr]);
}
//from() 还可以接收第二个参数,用来对每个元素进行处理
let liContents = Array.from(lis, ele => ele.textContent);
console.log(liContents);
- of
将任意的数据类型,转换成数组
console.log(Array.of(3, 11, 20, '30'));
- copyWithin
数组内部将指定位置的元素复制到其它的位置,返回当前数组
//从3位置往后的所有数值,替换从0位置往后的三个数值
[1, 2, 3, 8, 9, 10].copyWithin(0, 3);
- find
找出第一个符合条件的数组成员
let num = [1, 2, -10, -20, 9, 2].find(n => n < 0);
- findIndex
找出第一个符合条件的数组成员的索引
let numIndex = [1, 2, -10, -20, 9, 2].findIndex(n => n < 0);
- entries、keys、values
返回一个遍历器 可以使用for...of 循环进行比遍历
//keys() 对键名遍历
//values() 对值遍历
//entries() 对键值对遍历
for (let index of ['a', 'b'].keys()) {
console.log(index);
}
for (let ele of ['a', 'b'].values()) {
console.log(ele);
}
for (let [index, ele] of ['a', 'b'].entries()) {
console.log(index, ele);
}
- includes
返回一个布尔值,表示某个数组是否包含给定的值
console.log([1, 2, 3].includes(2));
//用来代替之的indexOf
console.log([1, 2, 3].indexOf('2'));
1.13 迭代器Iterator的用法
Iterator是一种新的遍历机制,两个核心
- 迭代器是一个接口,能快捷的访问数据,通过Symbol.iterator来创建迭代器 通过迭代器的next()获取迭代之后的结果
- 迭代器是用于遍历数据结构的指针(数据库的游标)
// 使用迭代
const items = ['one', 'two', 'three'];
// 1. 创建新的迭代器
const ite = items[Symbol.iterator]();
console.log(ite.next()); //{value: "one", done: false} done如果为false表示遍历继续 如果true表示遍历完成
console.log(ite.next());
console.log(ite.next());
1.14 生成器Generator
Generator函数 可以通过yield关键字,将函数挂起;为了改变执行流提供了可能,同时为了做异步编程也提供了方案
与普通函数的区别:
- function 后面函数名之前有个 *
- 只能在函数内部使用yield表达式,让函数挂起
function* func() {
console.log('one');
yield 2;
console.log('two');
yield 3;
console.log('end');
}
// 返回一个遍历器对象 可以调用next()
let fn = func();
console.log(fn.next());
console.log(fn.next());
console.log(fn.next());
//总结:generator函数是分段执行的,yield语句是暂停执行 而next()恢复执行
//Example
function* add() {
console.log('start');
//x 可真不是yield '2'的返回值,它是next()调用 恢复当前yield()执行传入的实参
let x = yield '2';
console.log('one:' + x);
let y = yield '3';
console.log('two:' + y);
return x + y;
}
const fn = add();
console.log(fn.next());//{value: "2", done: false}
console.log(fn.next(20));//{value: "3", done: false}
console.log(fn.next(30));//{value: 50, done: true}
// 使用场景:为不具备Interator接口的对提供了遍历操作
function* objectEntries(obj) {
//获取对象的所有的key保存到数组[name, age]
const propKeys = Object.keys(obj);
for (const propkey of propKeys) {
yield [propkey, obj[propkey]]
}
}
const obj = {
name:'Isaac',
age:18
}
obj[Symbol.iterator] = objectEntries;
console.log(obj);
for(let [key, value] of objectEntries(obj)) {
console.log(`${key},${value}`);
}
应用:部署ajax操作,让异步代码同步化
1.15 Promise的基本使用及方法
相当于一个一个容器,保存着未来才会结束的事件(异步操作)的一个结果
各种异步操作都可以用同样的方法进行处理(eg:axios)
- 特点
- 对象的状态不受外界影响,处理异步操作有三个状态:pending(进行中),resolved(成功),rejected(失败)
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果
//promise被创建时,会有一个回调函数作为他的参数
let pro = new Promise(function (resolved, rejected) {
//回调函数有两个参数:resolved,rejected
//执行异步操作
let res = {
code: 201,
data:{
name:'Isaac'
},
error:"失败了"
}
//设置定时器
setTimeout(() => {
if(res.code === 200) {
resolved(res.data);
}else {
rejected(res.error);
}
}, 1000)
})
console.log(pro);
//应用then()方法来接收Promise构造函数处理异步的结果,then()有两个参数
pro.then((val) => {
//接收成功的结果
console.log(val);
}, (err) => {
//接收失败的结果
console.log(err);
});
自己封装ajax,如下:
const getJSON = function(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onreadystatechange = handler;
xhr.responseType = 'json';
xhr.setRequestHeader('Accept', 'application/json');
//发送
xhr.send();
function handler() {
if(this.readyState === 4) {
if(this.status === 200) {
resolve(this.response.HeWeather6);
}else {
reject(new Error(this.statusText));
}
}
}
})
}
getJSON('https://free-api.heweather.net/s6/weather/now?location=beijing&key=b1eac8ade8b749bfb154b194a06964a4')
.then((data) => {
console.log(data);
},(error) => {
console.log(error);
})
- then
有两个参数,第一个参数是resolve回调函数,第二个参数是可选的,是reject状态回调的函数,返回一个新的Promise实例,可以采用链式编程
- resolve与rejected
能将现有的任何对象转换成Promise对象
let p = new Promise(resolve => resolve('foo'));
p.then((data) => {
console.log(data);
})
- all
应用:一些游戏类的素材比较多,等待图片、flash、静态资源文件都加载完成才进行页面的初始化
let promise1 = new Promise((resolve, reject) => { });
let promise2 = new Promise((resolve, reject) => { });
let promise3 = new Promise((resolve, reject) => { });
let p4 = Promise.all([promise1, promise2, promise3]);
p4.then(() => {
//三个都成功 才成功
}).catch(err => {
//如果有一个失败则失败
})
- race
用于某个异步请求设置超时时间,并且在超时后执行相应的操作
//请求图片资源
function requestImg(imgSrc) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = function() {
resolve(img);
}
img.src = imgSrc;
})
}
function timeOut() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('图片请求超时'));
}, 50);
})
}
Promise.race([requestImg('https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1594914423649&di=c26d75c17b18f01d8cc7e6db0886eb61&imgtype=0&src=http%3A%2F%2Fa3.att.hudong.com%2F14%2F75%2F01300000164186121366756803686.jpg'),timeOut()])
.then(data => {
console.log(data);
document.body.appendChild(data);
}).catch(err => {
console.log(err);
});
1.16 async的用法
作用使得异步操作更加方便
基本操作:async会返回一个Promise对象,then(),catch()
ps:async是Generator的一个语法糖
async function f() {
let s = await 'hello async';
let data = await s.split('');
return data;
}
//如果async函数中有多个await 那么函数会等待所有的await指令运行完的结果 才去执行
f().then(v => {
console.log(v);
}).catch(e => {
console.log(e);
})
1.17 class类
//es5 造类
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayName = function() {
return this.name;
}
let p1 = new Person('Isaac', 19);
console.log(p1);
//es6
class Person {
//实例化的时候会立即被调用
constructor(name, age) {
this.name = name;
this.age = age;
}
}
//通过Object.assign()方法一次性向类中添加多个方法
Object.assign(Person.prototype, {
sayName() {
return this.name;
},
sayAge() {
return this.age;
}
})
let p1 = new Person('Isaac', 28);
console.log(p1);
ps:可以使用关键字extends实现子类对父类的继承
1.18 module模块化实现
-
es6 模块功能主要由两个命令构成:import 和 export
-
export 用于规定模块的对外接口 import用于输入其它模块提供的功能
-
一个模块就是独立的文件