一、为何要学习ES6
① ES6 的版本变动内容最多,具有里程碑意义
② ES6 加入许多新的语法特性,编程实现更简单、高效
③ ES6 是前端发展趋势,就业必备技能
二、ES6 兼容性
可通过网址查看:ECMAScript 6 compatibility table
三、let 声明变量以及声明特性
let 可以像 var 一样用来声明变量,但两者有区别:
1 let 的变量不能重复声明,但可以修改变量,比如:
let a = 1;
let a = 2;
打开 F12 发现会报错
let a = 2;
a = 4;
console.log(a); //成功打印 4
2 let 是块级作用域
也就意味着 let 只能在代码块内部使用,比如:
{
let a = 2;
console.log(a); //成功打印2
}
console.log(a); //报错
3 不存在变量提升
4 不影响作用域链,比如:
{
let a = 2;
function f() {
console.log(a); //成功打印 2
}
f();
}
5 let 之间都是独立存在的互不影响
四、const 声明常量以及特点
常量:值不能修改的变量
1 必须赋初始值
const A;
打开 F12 发现会报错
2 一般常量使用大写(人为要求)
3 常量的值不能赋值修改
const A = 2;
A = 4;
console.log(A); //报错
4 常量不能重复声明
5 const 是块级作用域
{
const A = 2;
}
console.log(A); //报错
6 对于数组和对象的元素修改,不算做对常量的修改,不会报错
const arr = ['a','b']
arr.push('c')
console.log(arr); //成功打印 ["a", "b", "c"]
因为 arr 存储的是复杂数据类型,那么 arr 在内存中是以地址的形式存储的,只要地址不变就不会报错
五、解构赋值
ES6 允许按照一定模式从数组和对象中提取值,对变量进行赋值,这被称为解构赋值。
1 数组的解构赋值
const arr = ['桐谷和人','张楚岚']
let [dj,yrzx] = arr; //dj 与 yrzx 都是变量
console.log(dj,yrzx); //成功打印 桐谷和人 张楚岚
2 对象的解构赋值
const sishen = {
name: '黑崎一护',
age: 18,
fighting: function() {
console.log('我会卍解'); //成功打印 我会卍解
}
}
let {name,fighting} = sishen; //变量名要与对象里的属性名、方法名一致,不然会报错
fighting()
console.log(name); //成功打印 黑崎一护
六、模板字符串
ES6 引入了新的声明字符串的方式,叫反引号(``)
1 声明
let str = `易斩千击`;
console.log(str); //成功打印 易斩千击
2 内容中可以直接出现换行符
let str = `<ul>
<li>abc</li>
</ul>`
把 反引号(``) 换成 单引号('') 或 双引号("") 都会报错
3 变量拼接
let str = `易大师`;
let out = `${str}低分段乱杀`
console.log(out); //成功打印 易大师低分段乱杀
七、对象的简化写法
1 ES6允许在大括号里面直接写入变量名和函数名,作为对象的属性和方法。
let name = '陈子豪';
function late(){
console.log(`${name}天天迟到`);
}
const anchor = {
name,
late
}
console.log(anchor); //成功把 anchor 对象打印出来
anchor.late() //成功打印 陈子豪天天迟到
2 在对象里创建方法可以简写
const o = {
name: 'abc',
run() {
console.log(666);
}
}
o.run() //成功打印 666
八、箭头函数以及声明特点
ES6允许使用 箭头(=>) 定义函数。
1 声明函数以及调用函数
let 函数名 = (形参) => {
}
函数名(实参);
2 箭头函数的 this 无法改变
this 是静态的,this 始终指向函数声明时所在作用域下的 this 值,相当于往上一层找
3 箭头函数不能作为构造函数实例化对象
4 箭头函数不能使用 arguments
5 箭头函数的简写
① 当形参有且只有一个的时候,可以省略小括号,比如:
let fn = a => {
return a + a
}
console.log(fn(1)); //成功打印 2
② 当代码体只有一条语句的时候,可以省略花括号,比如:
let fn = (a) => a + a
console.log(fn(1)); //成功打印 2
此时 return 必须省略,因为语句的执行结果就是函数的返回值
6 箭头函数适用场景
① 箭头函数适合与 this 无关的回调,比如:定时器,数组的方法回调
② 箭头函数不适合与 this有关的回调,比如:事件回调,对象的方法
九、函数参数的默认值设置
1 ES6允许给函数形参赋值初始值,例如:
function fn(a,b,c=1) {
return a + b + c
}
let result = fn(1,2)
console.log(result); // 成功打印 4
代码解释:
fn 函数的形参有3个,但是实参只有2个,那这样得到的结果就不对。可是,此时c=1,给
函数形参赋了初始值。如果实参没有给 c 赋值,那么 c 就按照形参赋的值计算;如果实参给
c 赋了值,那么 c 就按照实参赋的值计算。本案例实参没有给 c 赋值,所以 c 按照形参赋值算,
也就是 1 + 2 + 1 = 4。
注意:
形参赋初始值,该形参最好是末尾的形参
2 使用场景
一般与解构赋值相结合,例如:
function connect({host='127.0.0.1',username,password,port}) {
console.log(host); //成功打印 127.0.0.1
}
const mysql = {
username: 'root',
password: '123456',
port: 3306,
}
connect(mysql)
十、rest 参数的使用
ES6引入 rest 参数,用于获取函数的实参,用来代替 arguments
1 语法:
function fn(...args) {
console.log(args); // 成功打印[1, 2, 3]
}
fn(1,2,3)
参数说明:
① 使用 rest 参数必须在形参前面加三个点(...)
② rest 参数中存储了传递过来的所有实参,并且是以数组的形式存储(arguments 是以伪数组)
③ rest 参数必须要放到所有形参的最后面
十一、扩展运算符
1 扩展运算符能将数组转换为用逗号分隔的参数序列
const arr = [1,2,3]
function fn(...args) {
console.log(args); //打印结果为 [1, 2, 3]
}
fn(...arr)
代码说明:
扩展运算符其实就是三个点(...),但是与 rest 参数不一样,扩展运算符是放在实参里的。
本案例打印的结果为一个数组里有三个元素,而不是一个数组里只有一个元素([1, 2, 3]作为一个元素),
不懂的可以把实参(...arr) 里的三个点(...)去掉试试看。实际上就是,扩展运算符把数组转换为用逗号
分隔的参数序列,比如,[1,2,3] 转换为 1,2,3,那这样就由原来的一个数组变成了3个实参。
2 数组的合并
const RNG = ['Letme','Mlxg','Uzi','Ming']
const WE = ['Xiye']
const EDG = ['Meiko']
const jiaolian = ['阿布']
const yayunhui_2018 = [...RNG,...WE,...EDG,...jiaolian]
console.log(yayunhui_2018); //成功打印 ["Letme", "Mlxg", "Uzi", "Ming", "Xiye", "Meiko", "阿布"]
3 数组的克隆
const RNG = ['Uzi']
const BLG = [...RNG]
console.log(BLG); //成功打印 ["Uzi"]
注意:
该克隆为浅拷贝
4 将伪数组转换为真数组
const div = document.querySelectorAll('div'); //querySelectorAll 是以伪数组的形式存储元素的
const divs = [...div]
console.log(divs);
十二、Symbol 数据类型
1 说明
ES6引入了一种新的原始数据类型 Symbol,表示独一无二的值。
它是JavaScript语言的第七种数据类型,是一种类似于字符串的数据类型。
2 特点
① Symbol 的值是唯一的,用来解决命名冲突的问题
② Symbol值不能运算(不能加减乘除,不能比较大小,不能拼接字符串等)
③ Symbol定义的对象属性不能使用 for...in 循环遍历,但是可以使用 Reflect.ownKeys 来
获取对象的所有键名
3 创建 Symbol
① 通过 Symbol() 函数创建:
let s = Symbol();
console.log(s,typeof s); //打印结果为 Symbol() "symbol"
可以往该方法里写一个字符串,比如:
let s1 = Symbol('张三');
let s2 = Symbol('张三');
console.log(s1 === s2); //打印结果为 false
代码解释:
打印结果为 false,说明通过Symbol() 函数创建的Symbol值是不一样的,Symbol 括号
里面的字符串张三只是起到描述作用,相当于注释,告诉程序员这个值的作用
② 通过 Symbol 对象创建:
let s4 = Symbol.for(666);
console.log(s4,typeof s4); //打印结果为 Symbol(666) "symbol"
for 括号里面也可以写描述字符串,例如:
let s4 = Symbol.for(666);
let s5 = Symbol.for(666);
console.log(s4 === s5); //打印结果为 true
代码解释:
打印结果为 true,说明通过Symbol 对象创建的Symbol值,如果 for 括号里的描述字符串
一样,那么Symbol的值就相同。
4 使用场景
一般是用在对象里面,因为 Symbol 无法遍历出来,所以可以起到保护个别隐私属性的作用,比如说
密码不想展现出来,那么密码这个属性名就可以使用 Symbol。
① 语法一,在外部给对象添加 Symbol:
let password = Symbol('密码') //也可以用 Symbol 对象创建
const a = {}
a[password] = 123456
console.log(a); //打印结果为 {Symbol(密码): 123456}
读取 password 的值要加中括号:
console.log(a[password]) //打印结果为 123456
console.log(Object.keys(a)) //打印结果为 [ ],说明无法遍历 Symbol
② 语法二,在内部给对象添加 Symbol:
let password = Symbol('密码'); //也可以用 Symbol 对象创建
const yonghu = {
[password]: 123456
}
console.log(yonghu) //打印结果为 {Symbol(密码): 123456}
读取 password 的值要加中括号:
console.log(yonghu[password]) //打印结果为 123456
console.log(Object.keys(yonghu)) //打印结果为 [ ],说明无法遍历 Symbol
5 问题
有一个问题我一直百思不得其解,下面有两段代码:
const o = {
[Symbol('密码')]: 123456
}
console.log(o[Symbol('密码')]); //undefined
---------------------------------------------------------------
const a = {
[Symbol.for('密码')]: 123456
}
console.log(a[Symbol.for('密码')]); //123456
为啥蓝色部分的代码打印结果为 undefined,而紫色部分的代码打印结果为 123456,
我想在蓝色部分打印 Symbol 的值应该怎么做?
十三、迭代器
1 说明
① 迭代器(lterator) 是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只
要部署 lterator 接口,就可以完成遍历操作。
② ES6创造了一种新的遍历命令 for...of 循环,lterator 接口主要供 for...of 使用
③ __proto__具备 iterator 接口的数据可用 for...of 遍历,具有 iterator 接口的数据有:
Array、Arguments、Set、Map、String、TypedArray、NodeList
2 例子
let arr = ['唐三藏','孙悟空','猪悟能','沙悟净']
for(let k of arr) {
console.log(k);
}
打印结果:
唐三藏
孙悟空
猪悟能
沙悟净
3 for...of 遍历原理
① 创建一个指针对象,指向当前数据结构的起始位置
② 第一次调用对象的 next() 方法,指针自动指向数据结构的第一个成员
③ 接下来不断调用 next() 方法,指针一直往后移动,直到指向最后一个成员
④ 每调用 next() 方法返回一个包含 value 和 done 属性的对象
例子:
let arr = ['唐三藏','孙悟空','猪悟能','沙悟净']
// 创建一个指针对象
let iterator = arr[Symbol.iterator]()
console.log(iterator); //把打印结果展开发现里面有 next()
// 第一次调用对象的 next() 方法
console.log(iterator.next()); //打印结果为 {value: "唐三藏", done: false}
// 不断调用 next() 方法
console.log(iterator.next()); //打印结果为 {value: "孙悟空", done: false}
console.log(iterator.next()); //打印结果为 {value: "猪悟能", done: false}
console.log(iterator.next()); //打印结果为 {value: "沙悟净", done: false}
console.log(iterator.next()); //打印结果为 {value: undefined, done: true}
done 为 true 说明循环结束
注:
需要自定义遍历数据的时候,要想到迭代器。
4 迭代器应用
自定义遍历数据,比如说遍历对象,但对象是没有 iterator 接口的,所以无法使用 for...of 遍历,
此时如果想使用 for...of 遍历对象,就需要手动在对象里添加 iterator 接口。例子如下:
const banji = {
name: '终极一班',
students: [
'breathe',
'wei',
'xioahu',
'gala',
'ming'
],
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index < this.students.length) {
let result = {value: this.students[index],done: false}
index++;
return result
} else {
return {value: undefined,done: true};
}
}
}
}
}
for(let v of banji) {
console.log(v);
}
打印结果为:
breathe
wei
xioahu
gala
ming
这段代码可以根据 for...of 遍历原理 和 浏览器报错信息一步一步写
十四、生成器函数声明与调用
1 说明
生成器其实就是一个特殊的函数,用来进行异步编程
2 函数的声明以及调用
其实就是在 function 和 函数名中间加个 * 号,比如:
function * fn() {
console.log(6);
}
console.log(fn());
把打印结果展开发现里面有 next() 方法,说明生成器函数也是个迭代器对象,使用
普通的调用方法无法调用函数,只能通过 next() 方法去调用函数,如下所示:
fn() //控制台啥也没有
fn().next() //成功打印 6
3 生成器函数内部可以出现 yield 语句
function * fn() {
yield '666'
}
console.log(fn().next()); //打印结果为 {value: "666", done: false}
因为生成器函数也是个迭代器对象,所以可以使用 for...of 遍历,比如:
function * fn() {
yield '666'
}
for(let v of fn()) {
console.log(v); //打印结果为 666
}
4 生成器函数的参数传递
next() 方法也可以传递参数,只不过 next() 方法是把参数返回给 yield,例子:
function * fn(agrs) {
console.log(agrs); //打印 1
let one = yield 1;
console.log(one); //打印第2次调用next
let two = yield 2;
console.log(two); //打印第3次调用next
let three = yield 3;
console.log(three); //打印第4次调用next
}
let iterator = fn(1);
iterator.next()
iterator.next('第2次调用next')
iterator.next('第3次调用next')
iterator.next('第4次调用next')
代码解释:
第一次调用生成器函数相当于开启这个函数,函数的形参与实参之间是正常传递的,
如果函数内部有 yield,在第二次调用生成器函数时,如果 next() 方法里面有参数,
会把参数返回给第一个 yield;在第三次调用生成器函数时,会把参数返回给第二个 yield,
以此类推
十五、Promise
注意:
重点,面试经常问!!!!
1 说明
Promise是ES6引入的异步编程的新解决方案。语法上Promise是一个构造函数,用来封装异步
操作并可以获取其成功或失败的结果。
2 实例化 Promise 对象
因为Promise是一个构造函数,所以可以new它
const p = new Promise(function(resolve,reject) {
let success = '成功'
resolve(success)
// let error = '失败'
// reject(error)
})
p.then(function(value) {
console.log(value);
},function(reason) {
console.log(reason);
})
代码解释:
标红色的部分就是语法,Promise 的参数是一个方法,方法里面有 resolve,reject 两个参数,
这两个参数在 Promise 内部属于函数。其实在 Promise 方法里应该写异步代码的,这里为了
简单演示就没写。当调用了resolve() 方法之后,Promise 对象(p)的状态就会变成成功,之后就
调用 promise 对象的 then 方法,这个 then() 方法里有两个函数作为参数,并且这两个函数分别
有一个形参,第一个叫 value,第二个叫reason。当 Promise 对象(p)的状态变成成功后就会调用
then() 方法里的第一个函数;当 Promise 对象(p)的状态变成失败后就会调用then() 方法里的第二个函数
十六、Set
1 说明
ES6提供了新的数据结构Set(集合),它类似于数组,但成员的值都是唯一的,集合实现了 iterator 接口,
所以可以使用 扩展运算符 和 for...of 进行遍历。
2 创建集合
Set 是一个对象,所以要通过 new 声明。
let s = new Set();
可以往 Set() 里传一个可迭代数据,一般传一个数组,比如:
let s = new Set([1,2,3,3])
console.log(s); //打印结果为 Set(3) {1, 2, 3}
注意:
Set(集合) 内重复的元素只算一个
3 Set集合的属性和方法
① size 属性,返回集合的元素个数
let s = new Set([1,2,3,3])
console.log(s.size); //打印结果为 3
② add() 方法,增加一个新元素,返回当前集合
let s = new Set([1,2,3,3])
s.add(4)
console.log(s); //打印结果为 Set(4) {1, 2, 3, 4}
注意:
新元素是从末尾添加
③ delete() 方法,删除元素,返回 boolean 值
let s = new Set([1,2,3,3])
s.delete(3)
console.log(s); //打印结果为 Set(2) {1, 2}
④ has() 方法,检测集合中是否包含某个元素,返回 boolean 值
let s = new Set([1,2,3,3])
console.log(s.has(1)); //打印结果为 true
如果元素存在就返回 true,如果元素不存在就返回 false
⑤ clear() 方法,清空集合
let s = new Set([1,2,3,3])
s.clear()
console.log(s); //打印结果为 Set(0) {}
十七、Map
1 说明
ES6提供了Map数据结构。它类似于对象,也是键值对的集合。但是 “键” 的范围不限于字符串,各种
类型的值(包括对象) 都可以当作键。Map也实现了 iterator 接口,所以可以使用 扩展运算符 和 for...of 进行遍历。
2 声明 Map
需要使用 new 来声明
let m = new Map()
3 Map集合的属性和方法
① set() 方法,增加一个新元素,返回当前Map
语法:
set(key, value)
参数说明:
(1) 该方法里有两个参数
(2) key:键
(3) value:值
例子:
let m = new Map();
m.set('name','文章')
let person = {
sex: '男'
}
m.set(person,['周卫国'])
console.log(m);
把打印结果展开发现 person 这个键居然是个对象,也就是说对象可以作为键名,进一步说明
Map的 “键” 的范围不限于字符串,各种类型的值(包括对象) 都可以当作键,换做在以前的对象
里,键名只能是字符串。所以Map 是对象的升级版
② size 属性,返回Map的元素个数
let m = new Map();
m.set('name','文章')
let person = {
sex: '男'
}
m.set(person,['周卫国'])
console.log(m.size); //打印结果为 2
③ get(key) 方法,返回键名对应的键值
let m = new Map();
m.set('name','文章')
let person = {
sex: '男'
}
m.set(person,['周卫国'])
console.log(m.get(person)); //打印结果为 ["周卫国"]
④ has(key) 方法,检测 Map 中是否包含某个键,返回 boolean 值
let m = new Map();
m.set('name','文章')
let person = {
sex: '男'
}
m.set(person,['周卫国'])
console.log(m.has('name')); //打印结果为 true
如果键名存在就返回 true,如果键名不存在就返回 false
⑤ clear() 方法,清空集合,返回 undefined
let m = new Map();
m.set('name','文章')
let person = {
sex: '男'
}
m.set(person,['周卫国'])
m.clear()
console.log(m); //打印结果为 Map(0) {}
十八、class 类
1 类的本质
① class 本质还是 function
② 类的所有方法都定义在类的 prototype 属性上
③ 类创建的实例,里面也有 __proto__ 指向类的 prototype 原型对象
④ 类的绝大部分功能,ES5 都可以做到,类的写法只是简化代码,让 JS 像面向对象的编程语言而已。
⑤ 所以类其实就是语法糖,它的底层就是 ES5
⑥ 语法糖就是一种便捷写法,也就是说,有两种方法可以实现同样的功能,但是有一种写法更加清晰、方便,
那么这个方法就是语法糖
2 类和对象
说明:
① 类是抽象了对象的公共部分,它泛指某一大类(class)
② 对象是特指某一个,通过类实例化一个具体的对象
面向对象的思维特点:
① 抽取(抽象)对象公共的属性和行为组织(封装)成一个类(模板)
② 对类进行实例化,获取类的对象
创建类:
① 语法:
class 类名 {
constructor() {
}
}
② 类的实例化:
let 变量名 = new 类名()
③ 例子:
class Phone {
constructor(brand,price) {
this.brand = brand;
this.price = price
}
call(nice) {
console.log(this.brand,this.price,nice);
}
}
let huaWei = new Phone('华为','2999')
huaWei.call(666)
打印结果为 华为 2999 666
注意:
① 通过 class 关键字创建类,类名是习惯性首字母大写。
② 类里面有个 constructor 函数,可以接收传递过来的参数,同时返回实例对象,
属性一般写在 constructor 函数里面。
③ constructor 函数只要 new 生成实例时,就会自动调用这个函数,如果我们不写这个函数,
类也会自动生成这个函数。
④ 类必须使用 new 实例化对象。
⑤ 创建类的类名后面不要加小括号,生成实例的类名后面加小括号,类里面的函数不需要
加 function。
⑥ 函数之间不需要逗号隔开。
⑦ 在ES6中类没有变量提升,所以必须先定义类,才能通过类实例化对象 。
⑧ 类里面的共有属性和方法一定要加 this 使用。
3 静态成员
class Phone {
static name = '苹果'
}
let apple = new Phone()
console.log(apple.name); // 打印结果为 undefined
console.log(Phone.name); //打印结果为 苹果
代码解释:
static 关键字可以把属性和方法定义为静态成员,静态成员只能通过类本身去访问,无法通过
实例化对象去访问。
4 实例成员
class Phone {
constructor(name) {
this.name = name;
}
}
let apple = new Phone('hauWei')
console.log(apple.name); //打印结果为 hauWei
console.log(Phone.name); //打印结果为 Phone
代码解释:
实例成员只能通过实例化对象去访问,无法通过类本身去访问。
5 类的继承
说明:子类可以继承父类的一些属性和方法。
语法:
通过 extends 关键字来实现,例如:
class Father { //父类
}
class Son extends Father { //子类继承父类
}
super 关键字:
super 关键字用于访问和调用父类上的函数。可以调用父类的构造函数,也可以调用父类的普通函数
注意:子类在构造函数中使用super,必须放到 this 前面 (必须先调用父类的构造方法,再使用子类构造方法)
继承中的属性或者方法查找原则:
如果实例化子类调用一个方法,先看子类有没有这个方法,如果有就先执行子类的,如果子类里面没有,就去
查找父类有没有这个方法,如果有,就执行父类的这个方法
例子:
class Father {
constructor(name,age) {
this.name = name;
this.age = age;
}
run() {
console.log(666);
}
}
class Son extends Father {
constructor(name,age) {
super(name,age)
}
}
let son = new Son('胡歌',18)
console.log(son); //打印结果为 Son {name: "胡歌", age: 18}
son.run() //打印结果为 666