ES6
一、let和const 变量的声明和提升
ES5 中都是用 var 声明变量,但是无论声明在何处,都会被视为声明在函数最顶部,
或者是全局作用域最顶部,而 let const 都是有块级作用域的
1、 let/const 和 var 的区别
1.1 预解析
在预解析过程中,var 定义的变量会被预解析
let / const 不会被预解析,只能先定义后使用
console.log(num) // undefined
var num = 100
console.log(num) // 100
console.log(num) // 直接报错 Cannot access 'num' before initialization
let num = 100
console.log(num)
console.log(str) // 直接报错 Cannot access 'str' before initialization
const str = 'hello world'
console.log(str)
1.2 重复声明(重名变量)
使用 var 可以定义两个一样地变量,只是第二次定义没有意义, 赋值有意义
let 和 const 不允许声明重名变量
var n1 = 100
var n1 = 200
console.log(n1) // 200
let n2 = 200
let n2 = 300 // 报错 Identifier 'n2' has already been declared
const n3 = 300
const n3 = 400 // 报错 Identifier 'n3' has already been declared
1.3 块级作用域
var 没有块级作用域
let 和 const 有块级作用域, 每一个可以书写代码段的 {} 都能限制变量的使用范围
if (true) {
var num = 100
console.log(num) // 100
}
console.log(num) // 100
if (true) {
let num = 100
console.log(num) // 100
}
console.log(num) // num is not defined
if (true) {
const num = 100
console.log(num) // 100
}
console.log(num) // num is not defined
2. let 和 const 的区别
2.1 赋值问题
let 可以在声明的时候不赋值
const 在定义的时候必须赋值
let num
console.log(num) // undefined
num = 200
console.log(num) // 200
const n1 // 报错 Missing initializer in const declaration
2.2 修改值
let 定义的变量值可以被修改
const 定义的值, 在定义时赋值, 一旦赋值不允许修改
let n1 = 100
console.log(n1) // 100
n1 = 200
console.log(n1) // 200
const n2 = 1000
console.log(n2)
n2 = 2000 // 报错 Assignment to constant variable.
// 因为 const 声明的常量不允许修改
二、模板字符串
ES6 新增一个定义字符串的方式,使用反引号(``)
//特点1:可以换行
let str = `hello
world`
console.log(str) // hello world
//特点2:可以在字符串内拼接变量, 书写 ${ 变量 }
let age = 18
let str1 = '你好 我今年 ${ age } 岁了'
console.log(str1) // 你好 我今年 ${ age } 岁了
let str2 = `你好 我今年 ${ age } 岁了`
console.log(str2) // 你好 我今年 18 岁了
//特点3: 可以调用函数
function fn(a) {
console.log(a); // ["hello ", " world ", "", raw: Array(3)]
}
let num = 100
fn`hello ${num} world ${num}`
// ${} 会把字符串切割成数组,并当成第一个参数
function fn(a,b,c) {
console.log(a); // ["hello ", " world ", "", raw: Array(3)]
console.log(b); // 100
console.log(c); // 200
}
let num1 = 100
let num2 = 200
fn`hello ${num1} world ${num2}`
// num1 就是该函数的第二个参数
// num2 就是该函数的第三个参数
三、函数新特性
3.1 函数默认值
当不传递实参的时候使用的值,在函数形参位置使用 赋值符号(=) 给形参赋值就行
箭头函数也能设置参数默认值,如果你设置了参数默认值, 那么不管多少个形参,都需要书写小括号
function fn(a = 10, b = 20) {
// 给形参 a 设置默认值为 10, 给形参 b 设置默认值为 20
console.group('fn 函数内的打印')
console.log(a)
console.log(b)
console.groupEnd()
}
fn()
// 没有给形参 a b 赋值,就使用默认值 10 20
fn(1000)
// 传递一个实参,给形参 a 赋值为 1000, 就不使用默认值 10,
// 没有给形参 b 赋值, 那么就使用默认值 20
fn(100, 200)
// 传递两个实参, 给形参 a 赋值为 100, 就不使用默认值 10,
// 给形参 b 赋值为 200, 就不使用默认值 20
// 箭头函数
let fn = (a = 10) => { console.log(a) }
fn(100) // 100
fn() // 10
3.2 箭头函数
语法: () => { }
() :书写函数形参的位置 , => :箭头函数的标志, { } :函数的函数体
特点:
没有 arguments,函数内一个伪数组, 是所有实参的集合;
没有 this,它的 this 是上下文(context) 的 this;
形参有且只有一个的时候, 可以不写小括号;
代码有且只有一句话的时候, 可以不写大括号,自动把这一句话的结果当做返回值
let f = () => {
console.log('我是 f 函数')
console.log(arguments) // arguments is not defined
// 2. 箭头函数内没有 this 使用的是上下文的 this
console.log(this)
// 箭头函数外面的函数 this 是谁, 箭头函数内的 this 就是谁,不能改变 this 指向
}
const acb = f(100)
console.log(acb) // undefined
四、拓展的对象功能
4.1对象简写语法
当 key 和 value 一模一样, 并且 value 是一个变量的时候,可以省略 value 和 冒号(:) 不写
const day = 12
const hours = 23
const minutes = 33
const seconds = 25
const obj = {
day, // 等价于你写了 day: day
hours,
minutes,
seconds,
// key 和 value 虽然一模一样
// 但是 value 不是一个变量
data: 'data'
}
console.log(obj) // {day: 12, hours: 23, minutes: 33, seconds: 25, data: "data"}
4.2 函数简写
当某一个 key 的值是一个函数, 并且不是箭头函数的时候,
可以直接省略 function 关键字和 冒号(:) 不写
const obj = {
name: '我是 obj 对象',
// f1 是一个函数, 但是不是箭头函数, 可以简写
f1: function () { console.log(' hello ') },
// f2 虽然是一个函数, 但是是一个箭头函数, 不能简写了
f2: () => { console.log(' hello ') },
// f3 是一个函数, 并且不是箭头函数
f3 () { console.log(' hello ') },
f4 () {
......
}
}
obj.f1(1, 2, 3)
obj.f3(10, 20, 30)
4.3扩展-对象浅拷贝
ES6 对象提供了 Object.assign() 实现浅拷贝,Object.assign 可以把多个源对象可枚举的属性拷贝给新的对象,
返回新的对象,第一个参数是新的对象
let obj1 = {name: 'jack'}
let obj2 = {sex:'男'}
let obj3 = {age:18}
let obj4 = {} // 目标对象
const obj = Object.assign(obj4, obj1, obj2, obj3)
console.log(obj1) // {name: "jack"}
console.log(obj2) // {sex: "男"}
console.log(obj3) // {age: 18}
console.log(obj4) // {name: "jack", sex: "男", age: 18}
console.log(obj) // {name: "jack", sex: "男", age: 18}
五、解构赋值
目的: 快速从 对象 或者 数组 中获取一些数据
5.1 解构对象 :
语法: let { 键名1, 键名2, ... } = 对象
别名: let { 键名: 别名 } = 对象
解构对象使用 { },快速从对象内获取一些数据
const obj = { name: 'Jack', age: 18 }
// 原先
const name = obj.name
const age = obj.age
const n = obj.name
console.log(name, age) // Jack 18
console.log(n) // Jack
// 解构
const { name, age } = obj
console.log(name, age) // Jack 18
// 别名
const { name: n, age: a } = obj
console.log(n, a) // Jack 18
// 扩展: 解构多维对象
const obj = {
name: 'Jack',
age: 18,
info: {
weight: 180,
height: 180,
data: {
desc: '我是一个好人',
hobby: [ '吃饭', '睡觉' ]
}
}
}
// 解构
const {
name: n,
age,
info: {
weight: w,
height: h,
data: {
desc,
hobby: [ a, b ]
}
}
} = obj
console.log(h) // 180
console.log(b) // 睡觉
5.2 解构数组:
语法: let [ 变量1, 变量2, 变量3, ... ] = 数组
按照数组的索引依次给 解构 内的 变量进行赋值
解构数组使用 [ ] ,快速从数组中获取一些数据
const arr = [ 100, 200, 300, 400, 500 ]
// 原先
const n1 = arr[0]
const n2 = arr[1]
const n3 = arr[2]
const n4 = arr[3]
const n5 = arr[4]
// 解构数组
const [ n1, n2, n3, n4, n5 ] = arr
console.log(n1, n2, n3, n4, n5)
// 多维数组的解构
const arr = [1, 2, [3, 4, [5, 6, [7, 8, [9, [10]]]]]]
const [a, b, [c, d, [e, f, [g, h, [i, [j]]]]]] = arr
console.log(arr)
console.log(e) // 5
六、Spread Operator 扩展运算符
展开运算符 (...)
当你使用在 数组 或者 对象 或者 函数实参位置的时候叫做 展开运算
多用于展开数组
const arr = [ 100, 200, 300 ]
console.log(arr) // [100, 200, 300]
console.log(...arr) // 100 200 300
const arr2 = [...arr, 400, 500, ...arr]
console.log(arr2) // [100, 200, 300, 400, 500, 100, 200, 300]
合并运算符 (...)
当你使用在 解构数组 或者 函数的形参位置的时候叫做 合并运算
const arr = [ 100, 200, 300, 400, 500 ]
// 在解构中使用合并运算符
// 把 arr 中 [0] 赋值给 a
// 把 arr 中 [1] 开始到末尾的所有内容都赋值给 b, 以一个新数组的形式出现
// 注意: 合并运算符必须写在最后一个的位置
const [ a, ...b ] = arr
console.log(a) // 100
console.log(b) // [200, 300, 400, 500]
// 在函数形参位置使用
// a 接受第一个实参
// 剩下的所有实参给到 b, 以一个新数组的形式出现
// 注意: 合并运算符必须写在最后一个的位置
function fn(a, ...b) {
console.log(a) // 100
console.log(b) // [200, 300, 400, 500]
}
fn(100, 200, 300, 400, 500)
// 箭头函数有一个特点, 没有 arguments
const fn = (...arg) => {
console.log(arg) // [10, 20, 30]
}
fn(10, 20, 30)
七、import export
按照需求去书写每一个独立的 模块(每一个 js 文件),
每一个 js 文件之间互相不干涉(每一个 js 文件是一个独立的文件作用域),
如果想在本文件内的某些内容,可以被其他文件访问, 那么需要在本文件内导出(开放, 暴露),
最后需要一个整合的 js 文件
导出: export default { }
导出一个默认对象, 把你想给其他文件使用的方法,书写在这个 {} 内部
导入: import 变量名 from 'js文件'
变量得到的内容是 该js文件 内导出的内容
- moment实现功能 为其他模块所使用文件
// 这里封装所有和时间操作相关的方法
// 作为一个功能函数文件出现
function format() { console.log('格式化时间') }
function diffTime() { console.log('获取时间差') }
const num = 100
const str = 'hello world'
// 因为模块化语法的出现
// 导致这里的两个方法, 只能在该文件内使用
// 其他文件绝对不可能使用这个方法
// 如果你想让其他文件内使用这个方法
// 就要在该文件内导出你想让其他文件使用的方法
export default {
// 其他文件就可以使用我这个文件内的 format 方法了
format,
diffTime,
num
}
- 某一模块
// 我是完成某功能的模块
// 我需要一个时间格式化方法
// 我就可以使用 moment 模块内的 format 方法
// 我需要在这里导入 moment.js 文件
// 导入 moment.js
import moment from './moment.js'
const num = 200
console.log('我导入的 moment ', moment)
console.log('完成顶部导航功能')
moment.format()
console.log('自己的 num ' , num)
console.log('导入的 num ' , moment.num)
- 整合
// 我作为整合模块出现
// 我把所有需要用到的内容都整合在一起
import './XXX.js'
import './XXX.js'
......
- 页面使用:
使用 script 标签的 src 属性引入指定的整合 js 文件
script 标签需要书写 type 属性, 值写成 module
<script src="./js/XXX.js" type="module"></script>
// 浏览器对于带有type="module"的<script>,都是异步加载
// jQuery 支持模块加载。
<script type="module">
import $ from "./jquery/src/jquery.js";
$('#message').text('Hi from jQuery!');
</script>
八、Promise
回调函数(callback),当你封装异步代码的时候, 并且需要在异步的结尾需要做一些事情的时候。
回调函数的缺点:
回调地狱, 是因为回调函数嵌套过多;
回调嵌套回调的时候, 代码的阅读和可维护性不高。
Promise 是来解决回调函数的回调地狱,一种优雅的对于异步代码封装的方案
promise 的三个状态:
继续 pending
成功 fulfilled
失败 rejected
// Promise 的基础语法
const p = new Promise(function (resolve, reject) {
// resolve 转换成功的方法
// 当书写 resolve() 的时候, 当前这个 promise 的状态就会变成 成功
// 就会执行 then 里面的函数
// reject 转换成失败的方法
// 当书写 reject() 的时候, 当前这个 promise 的状态就会变成 失败
// 就会执行 catch 里面的函数
// 这两个只能书写一个
// 书写封装的异步代码
const num = (Math.floor(Math.random() * 5) + 1) * 1000
setTimeout(() => {
const res = '后端给我的数据 ' + num
// 下面代码一旦执行, 就会把 promise 的状态改成成功
// resolve()
// 下面代码一旦执行, 就会把 promise 的状态改成失败
// reject()
if (num > 2500) {
// 因为会转换为成功
// 你在后面小括号里面书写的内容, 就会给到 then
resolve(res)
} else {
reject('!!!')
}
}, num)
})
// 执行成功的函数
p.then(function (r) {
// 会在 p 这个 promise 的状态由 继续 转变为 成功 的时候执行
console.log('成功了')
// r 是在 promsie 内部书写的 resolve 的小括号里面的内容
console.log(r)
})
// 执行失败的函数
p.catch(function (err) {
// 会在 p 这个 promise 的状态由 继续 转变为 失败 的时候执行
console.log('失败了')
console.log(err)
})
// Promise 的进阶语法
pAjax({ url: 'XXXXX' })
.then(r1 => {
// 这个 then 是第一个承诺的 then
console.log(r1)
// 实现需求2
// 在第一个承诺的 then 里面把第二个承诺 return 出去了
return pAjax({ url: 'XXXXXX', dataType: 'json' })
})
.then(r2 => {
// 这个 then 是因为第一个 then 里面 return 了一个新的承诺
// 所以这个 then 就是第二个承诺的 then
console.log(r2)
// 实现需求3:
return pAjax({ url: 'XXXXXXX', dataType: 'json'})
})
.then(r3 => {
// 第三个
console.log(r3)
})
8.1扩展 Promise.all
语法:
Promise.all([promise对象1,promise对象2,promise对象2.....]).then(res =>{
接受所有 Promise 完成的结果,以数组返回
})
目的:把多个 promise 对象封装成一个
缺点:必须所有的都成功,否则,一个结果也得不到
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('第一')
},Math.random() * 5 *1000)
// 当catch时 给 p1失败
// setTimeout(() => {
// reject('第一失败')
// },Math.random() * 5 *1000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('第二')
},Math.random() * 5 *1000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('第三')
},Math.random() * 5 *1000)
})
// 合成
Promise.all([p1,p2,p3]).then(res => {
console.log(res); // ["第一", "第二", "第三"]
})
// catch时
Promise
.all([p1,p2,p3]).then(res => {
console.log(res); // ["第一", "第二", "第三"]
})
.catch(err => {
console.log(err);
})
九、async函数
ES7 ~ ES8 之间出现的语法,
把异步代码写的看起来像同步代码, 本质还是异步,
把 Promise 的代码书写的更优雅。
其实就是 Generator 函数的语法糖,async函数就是将 Generator 函数的星号(*)替换成async,将 yield 替换成 await
async函数对 Generator 函数的改进,体现在以下四点:
内置执行器;更好的语义化;更广的适用性;返回值是 Promise
9.1 async关键字
async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,
才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,
只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数
// async 的语法,书写在函数的前面
async function fn() {}
const fn = async function () {}
const fn = async a => {}
.......
9.2 await 关键字
必须书写在 有async关键字的异步函数 内部,
await 后面等待的必须是一个 promise 对象, 否则等不了,
本该在 then 里面接受的结果, 可以直接在 await 前面定义变量接受。
// 简单使用
// 发送第一个请求,发送第二个请求时必须在第一个请求结束 并返回结果以后 再次请求,
// 第三个请求必须在第二个请求结束 并返回结果以后 再次请求并返回结果
fn()
async function fn() {
try {
// 实现需求1:
const r1 = await pAjax({ url: 'XXXXXX' })
console.log(r1)
// 实现需求2:
const r2 = await pAjax({ url: 'XXXXX', dataType: 'json' })
console.log(r2)
// 实现需求3:
const r3 = await pAjax({ url: 'XXXXX', dataType: 'json'})
console.log(r3)
} catch (err) {
console.error(err);
}
}
十、Class 类
ES5通过构造函数生成实例对象,如下:
function Person(name) {
this.name = name
}
Person.prototype.sayHi = function () {
// 是被 Person 的实例对象调用的
// 对象.sayHi()
// 标准的对象调用, this 就是 当前实例对象
// this.name 就是 p1.name
console.log(`hello ${ this.name }`)
}
const p1 = new Person('Jack')
p1.sayHi() // hello Jack
10.1 ES6类的语法 _原型方法
class 类名 {
constructor ( ) { } // 等价于构造函数体
方法名 ( ) { } // 构造函数原型上的方法
}
- 注意:
因为 构造函数 的本质是 函数, 所以可以当做普通函数调用
但是 类 的本质是 类, 不是函数, 所以不能当做普通函数调用
class People {
constructor () {
// 这个位置就等价于 ES5 的构造函数体
this.age = 20
}
// 直接书写原型上的方法
sayHi () { console.log('hello') }
}
const s = new People()
s.sayHi()
// 把 类 当做普通函数执行, 会报错
People( ) // Class constructor People cannot be invoked without 'new'
10.2 ES6类的语法 _静态方法
static 把函数当做对象,添加在自己身上的方法,
不是为了实例用的,也不能给实例用,调用时候加上类的名字。
- 注意:
如果静态方法包含 this 关键字, 这个 this 指的是类, 而不是实例;
父类的静态方法可以被子类继承;
静态方法可以和非静态方法重名。
class People {
constructor () {
// 这个位置就等价于 ES5 的构造函数体
this.age = 20
}
// 直接书写原型上的方法
sayHi () { console.log('hello') }
static Myprop() {
console.log( 'hello prop' ) // hello prop
}
}
// 直接用类的名字调用
People.Myprop()
const s = new People()
// 通过实例调用会报错
s.Myprop() // s.MyProp is not a function
十一、继承
书写子类的时候:
class 子类名 extends 父类 { }
在子类的 constructor 内书写, super( )
// 父类
class People {
constructor (name, age) {
this.name = name
this.age = age
}
sayHi () { console.log('hello world') }
// 静态方法可以被子类继承
static Myprop() {
console.log( 'hello prop' ) // hello prop
}
}
// 子类
class One extends People {
constructor (gender, name, age) {
// 相当于把父类的属性继承在自己的身上
super(name, age)
this.gender = gender
}
play () { console.log('play game') }
}
// 继承了静态方法
One.Myprop() // hello prop
const s = new One('男', '张三', 20)
十二、Generator函数
函数迭代器(函数生成器)
语法:
在定义函数的时候,在 function 后面加个 * 或者函数名前面加 * ,
函数内部可以使用 yield 关键字,
类似 return ,可以制造一个结果,然后让 Generator 暂停,
当再次回到 Generator 时候, 从上次 yield 继续向后执行代码。
Generator 的返回值是一个迭代器, 包含一个 next() 方法,
每次 next() 时候,就会执行到下一次 yield 位置为止。
// 有 * 之后,fn 不再是个函数
function* fn() {
console.log('第一段代码');
// 没有 * 也没有yield,此时什么也不会打印出来
yield "第一次结束"
console.log('第二段代码');
yield '第二次结束'
console.log('第三段代码');
return '第三次结束'
}
// fn()
// res 就是 fn 给生成的一个迭代器
const res = fn()
// 第一次,从 fn 的开头执行到第一个 yield
// 把 yield 后面的东西当做返回值
const first = res.next()
console.log(first); // 返回 {value:"第一次结束",done:false}
// 第二次, 从 第一次的 yield 后面到第二次的 yield 结束
const second = res.next()
console.log(second); // 返回 {value:"第二次结束",done:false}
// 第三次
const third = res.next()
console.log(third); // 返回 {value:"第三次结束",done:true}
十三、for … of 循环
是个关键字
语法:
for (let value of 数组) {}
目的:遍历迭代器
function* fn() {
console.log('第一段代码');
yield "1结束"
console.log('第二段代码');
yield '2结束'
console.log('第三段代码');
yield '3结束'
console.log('第四段代码');
yield '4结束'
console.log('第五段代码');
return '最终结束'
}
const res = fn()
console.log(res); // 返回迭代器
for (let value of res) {
console.log(value);
// 迭代器不只是 Generator 拿 Generator 举例
}
// 可以遍历数组,但没有函数部分,相比较节省性能。不能遍历对象
const arr = [ 'jack', '十八', '男']
for (let key in arr) {
console.log(key, arr[key])
}
for (let value of arr) {
console.log(value); // jack 十八 男
}
十四、Set 和 Map 数据结构
14.1 Set数据结构
Set 数据结构
迭代器结构的数据
语法:new Set()
可以在实例化的时候,传递一个数组,
数组里的每一个数据就是 set 数据类型的每一个数据
特点:不接受重复数据,
所以可以去重
常用方法:
1 add()
Set数据类型.add(要添加的数据)
2 delete()
Set数据类型.remove(要删除的数据)
3 has() 判断,返回 true 或 false
Set数据类型.has(要判断的数据) 是不是存在该数据
4 clear()
Set数据类型.clear() 清楚所有数据
5 forEach() 遍历
Set数据类型.forEach(function (item,item,set) { })
6 for of 遍历
for (let value of Set数据类型) {}
7 size 属性
Set数据类型.size 该数据类型有多少个数据
const s = new Set(['1','2','3'])
// add
s.add('hello')
console.log(s);
// forEach()
s.forEach(function (item, item, set) { })
// for of
for (const value of s) { }
// 去重
const arr = [1,2,3,4,2,3,5,3,4,2,5]
const res = [...new Set(arr)]
console.log(res);
14.2 Map数据结构
// 面试题
var obj = {}
var a = {name:'jack'}
var b = {name:'rose'}
obj[a] = 100
obj[b] = 200
console.log(obj[a]);
console.log(obj[b]);
Object 类型只能存储字符串作为 key
Map 数据结构:
值 = 值
它可以使用复杂数据类型作为 key 使用
语法:
new Map()
实例化的时候接受一个二维数组
里层数组的 [0] 作为 key
里层数组的 [1] 作为 value
方法:
1 set()
Map数据结构.set(key,value)
2 get()
Map数据结构.get(key)
注意:当 key 是复杂数据类型的时候,要注意地址指向
3 delete()
Map数据类型.delete(要删除的key)
4 clear()
Map数据类型.clear() 清楚所有
5 forEach() 遍历
Map数据类型.forEach(function (value, key, map) {})
6 for of 循环
for (let value of map数据结构) {}
7 has() 判断,返回 true 或 false
Map数据结构.has(需要判断的 key) 是不是存在该数据
8 size 属性
Map数据结构.size
const m = new Map([
['name','jack'],
// 存复杂数据类型
[{name:'Jack'}, {name:'Rose'}]
])
// set
m.set('age',18)
m.set([1,2,3],[4,5,6])
m.set(function () {console.log('rose');}, function() {console.log('jack');})
// forEach
m.forEach(function (value, key, map) { })
// for of
for (let [key, value] of m) {
console.log(key, value);
}