ES6 语法
是一个官方发布的语法版本, ECMAScript2015
里面定义了新的语法规范
=> 定义变量
=> 定义函数
=> 对象赋值
=> 字符串
=> 新增数据类型
=> …
问题: 不兼容, 不用我们考虑
在后期, 我们会学习一些工具
把 ES6 的语法转换成 ES5 的语法
颜色主题 one dark pro
图标主题 vscode icon
let/const 统一和 var 定义变量的区别
在 ES6 里面
有两个定义变量的关键字
一个叫做 let => 变量
一个叫做 const => 常量(特殊的变量)
区别:
1. var 会进行预解析
=> let 和 const 不会进行预解析
=> 你如果需要使用 let 或者 const 定义的变量
=> 必须要先声明后使用
2. var 可以声明重复变量名
=> let 和 const 不允许声明重复变量名
=> 如果你使用 let 或者 const 定义变量
=> 那么你要保证你变量名的唯一性
3. var 没有块级作用域
=> let 和 const 有块级作用域
=> 块级作用域:
-> 只针对于 let 或者 const 定义的变量
-> 任何可以书写代码段的 大括号({}) 都能限制变量的使用范围
-> 比如
if () {}
while () {}
do {} while ()
switch () {}
for () {}
function fn() {}
-> 对象不是
var obj = { 不是代码段, 是数据 }
=> 你使用 let 或者 const 定义在某一个代码段里面的变量
-> 只能在这个代码段里面使用
-> 在当前代码段外面不能使用了
=> 块级作用域也是一个作用域
-> 可以在不同作用域定义同名变量
-> 变量的声明赋值和使用依旧遵循变量访问使用定义规则
// 4. 块级作用域变量
// let n1 = 100
// if (true) {
// let n1 = 200
// // 这里使用的是 if 的 {} 里面这个块级作用域里的 n1
// console.log(n1) // 200
// }
// // 这里使用的是全局作用域的 n1 变量
// console.log(n1) // 100
// 3. 块级作用域
// if (true) {
// var n1 = 100
// console.log(n1) // 100
// }
// 因为 if 并不是一个私有作用域, 所以不会限制 var 的使用范围
// 此时这个 n1 就是一个全局变量
// console.log(n1) // 100
// if (true) {
// // 因为 let 有块级作用域
// // 所以 此时定义的 n2 只能在当前这个 if 的 {} 里面使用
// // 当离开这个大括号以后, 就不能使用了
// let n2 = 200
// console.log(n2)
// }
// console.log(n2)
// if (true) {
// // 因为 const 有块级作用域
// // 所以此时定义的 n3 变量只能在当前这个 if 的 {} 里面使用
// // 离开这个 {} 以后就不能使用了
// const n3 = 300
// console.log(n3)
// }
// console.log(n3)
// 2. var 可以声明重复变量名
// var n1 = 100
// var n1 = 200
// 相当于第二次声明的 n1 其实没有意义
// 但是对 n1 进行了两次赋值, 第一次赋值为 100, 第二次赋值为 200
// console.log(n1)
// 因为 let 和 const 关键字不允许定义重复的变量名
// 当你写完 定义变量 以后, 就直接报错给你看
// let n2 = 200
// let n2 = 300
// const n3 = 300
// const n3 = 400
// 1. 预解析的区别
// console.log(num) // undefined
// 在预解析的时候, 会对 var 定义的变量进行提前声明
// 告诉浏览器这个变量可以使用, 但是还没有进行赋值
// var num = 100
// console.log(num) // 100
// console.log(n1)
// 因为 let 定义的变量不会进行预解析
// 所以此时还没有 n1 这个变量出现, 这里使用 n1 就会报错了
// let n1 = 200 // 定义了一个变量, 叫做 n1, 赋值为 200
// console.log(n1)
// console.log(n2)
// const n2 = 300
// console.log(n2)
let 和 const 的区别
1. let 定义变量的时候可以不赋值
=> const 定义变量的时候必须赋值
2. let 定义的变量可以被修改
=> const 定义的常量不允许被修改
=> 一经赋值, 再也不能修改
3. let 叫做变量
=> const 叫做常量
// 一个小问题
// const 定义了一个 obj 变量
// 存储的内容是一个对象的地址
// const obj = {
// name: ‘Jack’
// }
// 根据地址修改得对象空间内部的数据
// 我并没有给 obj 重新赋值
// obj.name = ‘Rose’
// console.log(obj.name) // Rose
// 现在是使用一个新对象的地址赋值给了 obj
// 相当于修改了 obj 这个变量存储的内容, 报错了
// obj = {
// name: ‘Jack’
// }
// console.log(obj.name)
// 1. let 定义变量可以不进行赋值
// let n1
// let n2
// console.log(n1, n2)
// n1 = 100
// n2 = 200
// console.log(n1, n2)
// const 定义变量的时候如果不赋值, 直接报错
// const n3
// 2. let 定义的变量可以被修改
// let n1 = 100
// console.log(n1)
// n1 = 200 // 给 n1 变量重新赋值, 因为是 let 定义的, 所以可以重新赋值
// console.log(n1)
// const n2 = 200
// console.log(n2)
// n2 = 300 // 给 n2 变量重新赋值, 因为是 const 定义的, 所以不允许被修改, 直接报错
let 在代码中的使用
因为块级作用域的存在, 有的时候能帮我们解决很多问题
比如我们的循环绑定事件
我们之间在做选项卡案例的时候
就是因为使用 var 定义变量
所以我们在事件里面的时候不能使用 i
因为我们点击的时候循环已经结束了
所以我们单独把索引保存在了元素的身上
需求: 三个按钮, 点击每一个的时候给我对应的索引
var btns = document.querySelectorAll(‘button’)
// for (var i = 0; i < btns.length; i++) {
// btns[i].setAttribute(‘index’, i)
// btns[i].addEventListener(‘click’, function () {
// // 我需要做的就是点击每一个按钮的时候, 打印出这个按钮对应的索引
// // 因为你点击的时候, 循环已经结束
// // var 又没有块级作用域, 所以其实每次循环的时候 i 都是全局变量
// // 所以打印出来的都是 3
// console.log(i)
// // 要想拿到索引, 需要提前保存一下这个索引在元素身上
// // 使用的时候, 直接拿到当前这个元素身上的 index 自定义属性
// console.log(this.getAttribute(‘index’))
// })
// }
for (let i = 0; i < btns.length; i++) {
btns[i].addEventListener(‘click’, function () {
// 之所以每次都能打印出对应的索引
// 是因为 let 的 块级作用域
// 在循环的过程中相当于有三个 {} 出现
// 每一个 {} 里面有一个 let 定义的变量 i
console.log(i)
})
}
当循环开始的时候
{
let i = 0
btns[0].addEventListener(‘click’, function () { console.log(i) })
}
{
let i = 1
btns[1].addEventListener(‘click’, function () { console.log(i) })
}
{
let i = 2
btns[2].addEventListener(‘click’, function () { console.log(i) })
}
{
let i = 3
结束循环
}
当循环开始的时候
var i = 3
{
btns[0].addEventListener(‘click’, function () { console.log(i) })
}
{
btns[1].addEventListener(‘click’, function () { console.log(i) })
}
{
btns[2].addEventListener(‘click’, function () { console.log(i) })
}
{
结束循环
}
箭头函数
在 ES6 的语法里面, 箭头函数是对 函数表达式 的简写
函数表达式 - 也叫做匿名函数
var fn = function () { }
div.onclick = function () { }
div.addEventListener(‘click’, function () { })
arr.forEach(function () { })
setTimeout(function () { }, 1000)
setInterval(function () { }, 1000)
var obj = {
fn: function () { }
}
…
箭头函数的语法
() => {}
(): 表示书写形参的位置
=>: 表示箭头函数的规定符号
{}: 表示这个函数的代码段
// 原先的赋值时函数定义
let fn = function (a, b) {
console.log(‘我是 fn 函数’)
console.log(a)
console.log(b)
}
fn(10, 20)
console.log(’=’)
let fun = (a, b) => {
console.log(‘我是 fun 箭头函数’)
console.log(a)
console.log(b)
}
fun(100, 200)
箭头函数的几个特点
箭头函数独右的, 普通函数不能这样玩
1. 箭头函数 只有一个形参 的时候可以省略小括号不写
没有形参或者多个形参必须写小括号
2. 箭头函数 只有一句话 的时候可以省略大括号不写
并且会自动把这一句话的结果返回
3. 箭头函数里面 没有 arguments 这个所有实参的集合
在箭头函数里面就没有的使用
4. 箭头函数里面没有 this 关键字
官方: 箭头函数里面的 this 是外部作用域的 this, 学名叫做上下文(context)
私人: 箭头函数写在哪一行, 上一行的 this 就是这个箭头函数里面的 this
// 小案例
var div = document.querySelector(‘div’)
div.onclick = () => {
// 这个函数虽然被当作了事件处理函数使用
// 但是是一个箭头函数
// 因为箭头函数里面没有 this, 使用的就是外部作用域的 this
// 又因为当前这个箭头函数的外部作用域就是 全局
// 全局作用域的 this 就是 window
// 所以, 此时这个事件处理函数里面的 this 就是 window
console.log(this) // window
}
// 特点4
// let fn = () => { // 这个函数写在了 57 行, 那么 56 行的 this 就是这个箭头函数里面的 this
// // 因为箭头函数里面的 this 是外部作用域的 this
// // 当前这个 fn 函数的外部作用域就是全局作用域
// // 又因为全局作用域的 this 就是 window
// // 所以这个函数里面的 this 就是 window
// console.log(this)
// }
// fn()
// var div = document.querySelector(‘div’)
// div.onclick = function () {
// let fn = () => { // 这个函数写在了 69 行, 那么 68 行的 this 其实就是箭头函数内部的 this
// // 因为箭头函数里面的 this 是外部作用域的 this
// // 当前这个 fn 函数的外部作用域是 div 的事件处理函数
// // 又因为 div 的事件处理函数里面 this 指向 div
// // 所以这个 fn 函数里面的 this 指向 div
// console.log(this)
// }
// fn()
}
// 特点3
// let fn = function () {
// console.log(arguments)
// }
// fn(10, 20, 30)
// console.log(’’)
// let fun = () => {
// console.log(arguments)
// }
// fun(100, 200, 300)
// // 小案例
// // 筛选数组中所有大于 10 的数据
// let arr = [100, 20, 3, 9, 22, 11, -10, -20, 0]
// // 我可以使用 filter 方法
// let res = arr.filter(function (item) {
// return item > 10
// })
// console.log(res)
// // 可以在后面那个函数的位置使用箭头函数
// let res2 = arr.filter(item => item > 10)
// console.log(res2)
// // 把数组按照大小进行排序
// // 使用 sort 方法, 进行排序
// // arr.sort(function (a, b) { return a - b })
// arr.sort((a, b) => b - a)
// console.log(arr)
// 特点2
// 我的函数的作用就是传递一个数字进来
// 我给你返回一个这个数字加上 10 的结果
// 如果你传递一个 20, 那么你能得到一个 30 的结果
// let fn = (a) => {
// return a + 10
// }
// var res = fn(20)
// console.log(res)
// console.log(’=’)
// 当前这个函数只有一句话, 就是 a + 10
// 并且会在执行函数的时候, 自动把 a + 10 的结果当作这个函数的返回值
// 当你调用这个函数的时候, 传递进来一个 20
// 会得到一个 20 + 10 的结果
// let fun = a => a + 10 // => let fun = function (a) { return a + 10 }
// var res2 = fun(20)
// console.log(res2)
// 特点1
// let fn = (a) => {
// console.log(‘我是 fn 箭头函数’)
// console.log(a)
// }
// fn(10)
// console.log(’’)
// // 这个 a 就是当前这个函数内部的一个形参
// // 只是因为只有一个形参, 所以把小括号省略了
// let fun = a => {
// console.log(‘我是 fun 箭头函数’)
// console.log(a)
// }
// fun(100)
函数的参数默认值
给函数的形参直接设置一个默认值
当你不传递实参的时候就使用默认值
当你传递里实参的时候就使用你传递的
书写方式
直接在书写形参的时候使用 等于号(=) 给其赋值一个默认值
如果你给箭头函数设置形参默认值, 那么不管几个形参都必须写小括号
// 以前定义参数的方式
function fn(a, b) {
console.log(a)
console.log(b)
}
fn(10, 20) // 本次调用的时候 10 赋值给 a, 20 赋值给 b
console.log(’=’)
fn(30) // 本次调用的时候 30 赋值给 a, b 没有被赋值就是 undefined
console.log(’=================’)
fn() // 本次调用的时候, 没有传递实参, 那么 a 和 b 都是 undefined
console.log('=================================')
// 带有参数默认值的函数
function fun(a, b = 200) {
// 给该函数的形参 b 设置了一个默认值
// 如果你没有传递第二个实参, 那么 b 的值就是 200
// 如果你传递了第二个实参, 那么 b 的值就是你传递的实参
console.log(a)
console.log(b)
}
fun(10, 20) // 本次调用的时候, 10 赋值给了 a, 20 赋值给了 b
console.log('=================================')
fun(100) // 本次调用的时候, 100 赋值给了 a, b 没有进行赋值, 那么就使用默认值 200
console.log('=================================')
fun() // 本次调用的时候, 一个参数也没有传递, a 没有默认是就是 undefined, b 使用默认值
console.log('=================================')
// 箭头函数也可以使用参数默认值
let f = (a = 100) => {
console.log(a)
}
f(10) // 本次调用的时候, 10 赋值给了 a
console.log('=================================')
f() // 本次调用的时候, 没有实参赋值, 那么 a 就使用默认值就是 100
模板字符串
ES6 里面新规定的一种定义字符串的方式
定义
使用 反引号(``) 表示字符串
反引号: 英文输入模式, tab 的上边, 1 的左边
和单引号双引号的作用是一样的
能使用的字符串常用方法都是一样的
特点
1. 可以直接换行书写
单引号和双引号不能换行
2. 可以用 指定语法 在 模板字符串 里面直接解析变量
当你需要在模板字符串里面解析变量的时候
你就写 KaTeX parse error: Expected '}', got 'EOF' at end of input: …t color = `rgb({ fn() }, ${ fn() }, ${ fn() })div.style.background = color }, 300) // 特点2 // let name = 'Jack' // let age = 18 // let gender = '男' // let s1 = 'My name is ' + name + ', i\'m ${ age } years old!' // console.log(s1) // let s2 =
My name is ${ name }, i’m ${ age } years old!// console.log(s2) // 特点1 // let s1 = 'hello world' // 不能换行 // let s2 =
// hello
// world
// // console.log(s1) // console.log(s2) // let s1 = 'hello world' // let s2 = "hello world" // let s3 =
hello world`
// console.log(s1, typeof s1)
// console.log(s2, typeof s2)
// console.log(s3, typeof s3)
解构赋值
=> 快速从 对象 或者 数组 中获取成员
=> 就是把 对象 或者 数组 中的成员单独得到
数组的解构赋值
=> 快速从数组中获取成员
=> 在定义变量的时候直接使用 [] 将变量包裹起来
=> 等于号 左边的顺序 和 等于号右边的顺序是一一对应的
let arr = [10, 20, 30]
// 当我想获取数组中的索引为 [0] 的数据
let a = arr[0]
// 当我想获取数组中的索引为 [1] 的数据
let b = arr[1]
// 当我想获取数组中的索引为 [2] 的数据
let c = arr[2]
console.log(a, b, c)
// 用解构赋值的方式
let [n1, n2, n3] = arr
// n1 变量获取的就是 arr[0]
// n2 变量获取的就是 arr[1]
// n3 变量获取的就是 arr[2]
console.log(n1, n2, n3)
对象的解构赋值
=> 快速从对象中获取成员
=> 直接在定义变量的时候使用 {} 包裹起来
=> 这个 {} 里面写的名字是你定义的变量名, 也是对象内部的成员名称
问题一: 一次解构多个
=> 其实不是, 你可以一次性解构多个
=> 只要在你定义变量时候的 {} 里面多写几个 obj 里面的成员名称就好了
=> 多个名称之间使用 逗号 分隔
问题二: 换一个名字
=> 在解构的时候可以给定义一个别的变量名
=> 在定义变量的时候写 { 你要从对象中获取的成员: 你要自己起的变量名 } = 对象
// 小案例 - 交换变量
// var n1 = 5
// var n2 = 6
// 以前
// var n3 = n1
// n1 = n2
// n2 = n3
// console.log(n1, n2)
// 可以用解构赋值
// var [n1, n2] = [n2, n1]
// console.log(n1, n2)
展开运算符
一个运算的符号, 样子 …
通常在展开数组或者展开对象的时候使用
展开什么数据, 就写在什么数据变量名的前面
展开数组
…数组
就是相当于把 [] 干掉, 其余不动
1. 合并数组使用
多个数组组合成一个数组
取代了 concat 的作用
2. 传参的时候使用
可以把数组展开传递到一个函数里面
展开对象
…对象
就是相当于把 {} 干掉, 其余不动
1. 合并对象使用
把一个对象里面的成员全部添加到另一个对象里面
let o1 = {
name: ‘Jack’,
age: 18
}
let o2 = {
…o1, // 把 o1 对象里面的所有成员, 在这里再写一遍
gender: ‘男’
}
let o3 = {
…o2,
score: 99
}
console.log(o1)
console.log(o2)
console.log(o3)
// 2. 展开数组作用2
// function fn(a, b, c) {
// console.log(a)
// console.log(b)
// console.log©
// }
// let arr = [100, 200, [300, 400]]
// fn(10, 20, 30)
// fn(arr) // 这个是只传递了一个参数, 就是 arr 给了形参 a
// fn(…arr) // 把 arr 展开放在参数的位置 fn(100, 200, 300)
// let res = Math.max(…arr) // 把 arr 里面的每一个传递到 Math.max 方法里面
// console.log(res)
// 1. 展开数组作用1
// let arr2 = [6, 7, 8, 9, 10]
// let abc = [‘hello’, ‘world’, ‘你好’, ‘世界’]
// let arr = [1, 2, 3, 4, 5, …arr2, …abc]
// console.log(arr)
// var arr = [1, 2, 3, 4, 5]
// console.log(arr) // 打印原样 arr 数据
// console.log(…arr) // 打印展开的 arr 数据
// console.log(1, 2, 3, 4, 5)
合并运算符
一个运算符号, 样子 …
和展开运算符长得一模一样, 只是使用的位置不一样
当 …
使用在数组或者对象的变量前面
使用在函数实参的位置的时候
叫做展开运算
当 …
使用在数组的解构赋值位置
使用在函数的形参的位置的时候
叫做合并运算符
常用的位置
在箭头函数里面使用
为了自己把所有实参搞一个集合
// 我想把 1 单独拿出来, 2 ~ 9 继续保持一个数组的样子给我
// 1
// [2, 3, 4, 5, 6, 7, 8, 9]
// let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
// a 接收数组里面索引 [0]
// b 变量接收的是从做因 [1] 到结尾的所有内容, 组合成一个新的数组给我
// … 是把剩下的所有内容给了变量 b
// let [a, c, …b] = arr
// console.log(a)
// console.log(b)
// 在函数的形参位置
// function fn(a, …b) {
// // 从第二个实参开始, 到结尾的所有实参, 组合成一个新的数组给了变量 b
// console.log(a)
// console.log(b)
// }
// fn(10, 20, 30, 40, 50, 60)
let fn = (…arg) => {
// …arg 就相当于把所有的实参, 都拿到一起
// 放在一个数组里面, 赋值给 arg 这个形参
// 就相当于我们自己制作了一个所有实参的集合
console.log(arg)
}
fn(10, 20, 30, 40, 50, 60)
对象的简单书写方式
当你书写一个对象的时候, 可以有一些简写的形式
当满足某些条件的时候, 可以简写
条件
1. 当你给一个对象里面书写成员的时候
如果对象的成员名和值是一模一样的
并且值你使用的是一个变量
那么可以只写一个
2. 当你给一个对象里面的某一个成员赋值为一个函数的时候
赋值的函数不能是箭头函数
必须是标准的写的有 function 关键字的函数
可以直接省略 冒号 和 function 关键字
let age = 18
let obj = {
name: ‘Jack’,
age, // 等价于 age: age
// fn: function () { console.log(‘我是 obj 里面的 fn 函数’) }, // 可以简写, 里面是有 this 的
fn () {
console.log(‘我是 obj 里面的 fn 函数’)
console.log(this)
},
f: () => {
console.log(‘我是 obj 里面的 f 箭头函数’)
console.log(this)
} // 不可以简写, 里面没有 this
}
console.log(obj)
obj.fn() // obj 对象
obj.f() // window
// let age = 18
// let obj = {
// name: ‘Jack’,
// // age: age // 把全局 age 变量的值赋值给 obj 的 age 成员
// age, // 等价于 age: age
// abc: ‘abc’ // 不能简写, 因为后面的值位置使用的不是变量
// // abc, // 等价于 abc: abc
// }
// console.log(obj)
说几个概念
1. 我们的 script 标签其实省略了一个内容
type 属性, 标准的 script 标签应该有一个 type 属性
只是 text/javascript
// 在我的文件里面书写一些数据和方法
let f = () => { console.log(‘我是一个获取时间差的方法’) }
let fn = () => { console.log(‘我是处理年月日的方法’) }
let fun = () => { console.log(‘我是处理时分秒的方法’) }
let obj = {
name: ‘我是一个 a.js 文件里面的 obj 对象’
}
let arr = [‘我是 a.js 文件里面的 arr 数组’]
// 这里书写我导出的内容
export default {
// 表示把当前文件我定义好的 f 函数导出了, 将来谁导入 a.js 文件的时候, 谁就能得到 f 函数
f,
// 表示把当前文件我定义好的 fn 函数导出了, 将来谁导入 a.js 文件的时候, 就能得到 fn 函数
fn
}
// 我是 b 模块
// 我负责一些渲染页面的操作
// 在当前文件准备一些数据
let f = () => { console.log(‘我是渲染页面的方法’) }
let obj = {
name: ‘我是 b.js 文件里面的 obj 对象’
}
// 确定本文件导出的内容
export default {
f,
obj
}
// 我是 main 模块
// 我负责整合 a 和 b 模块再这里使用
// 导入 a.js 这个模块文件
// moduleA 变量接收的就是 a.js 文件里面导出的内容
import moduleA from ‘./a.js’
console.log('moduleA: ’ , moduleA)
// 导入 b.js 这个模块文件
// moduleB 变量接收的就是 b.js 文件里面导出的内容
import moduleB from ‘./b.js’
console.log('moduleB: ’ , moduleB)
moduleA.f() // 调用 a.js 文件里面的 f 函数
moduleB.f() // 调用 b.js 文件里面的 f 函数
模块化语法
导出
当这个模块提供一些方法或者能力的时候, 那么这个文件就要导出
一个模块里面可以写若干个方法, 也可以写若干个数据
但是你导出几个, 别人才能用几个
语法: export default { 你要导出的内容 }
多个文件可以有重名的方法和属性存在, 只要不再一个文件里面就没事
导入
当这个模块需要用到某一个其他模块里面提供的方法的时候, 那么这个模块就需要导入
你导入的模块整个 js 文件里面, 导出了几个方法或者数据
那么你就能使用几个方法或者数据
语法: import 变量 from '哪一个文件'
变量 => 接收的内容就是后面那个文件导出的对象
例子:
a.js 负责操作事件
b.js 负责操作渲染页面
main.js 负责整合这两个文件
a.js 和 b.js 需要导出
main.js 需要导入
a.js 里面写了 100 个方法, 但是导出了 3 个
b.js 里面写了 10 个方法, 但是导出了 5 个
main.js 里面导入 a.js 文件以后, 只能使用 a.js 文件里面导出的 3 个方法
main.js 里面导入 b.js 文件以后, 只能使用 b.js 文件里面导出的 5 个方法