声明(let和const)
let 声明的就是变量,变量一旦初始化之后,还可以重新赋值
const声明的就是常量,常量一旦初始化,就不能重新赋值了,否则就会报错
注意事项
- 使用
const声明常量,一旦声明,就必须立即初始化,不能留到以后赋值 const声明的常量,允许在不重新赋值的情况下修改它的值,比方说对象.属性名的方式- 默认使用
const,只有当确实需要改变变量的值的时候才使用let
let、const 与 var 的区别
1.重复声明
已经存在的变量或常量,又声明了一遍
var 允许重复声明,let、const 不允许
2.变量提升
var会提升变量的声明到当前作用域的顶部
3.暂时性死区
只要作用域内存在 let、const ,它们所声明的变量或常量就自动“绑定”这个区域,不再受到外部作用域的影响
4.window 对象的属性和方法
全局作用域中,var 声明的变量,通过 function 声明的函数,会自动变成 window 对象的属性或方法
5.块级作用域
var没有块级作用域,let、const可以形成块级作用域
应用
body {
padding: 50px 0 0 150px;
}
.btn {
width: 100px;
height: 100px;
margin-right: 20px;
font-size: 80px;
cursor: pointer;
}
<button class="btn">0</button>
<button class="btn">1</button>
<button class="btn">2</button>
// 1.var
var btns = document.querySelectorAll('.btn')
for (var i = 0; i < btns.length; i++) {
btns[i].addEventListener(
'click',
function () {
console.log(i) // 3 3 3...
},
false
)
}
// 2.闭包
// 利用立即执行函数形成闭包
var btns = document.querySelectorAll('.btn')
for (var i = 0; i < btns.length; i++) {
;(function (index) {
btns[index].addEventListener(
'click',
function () {
console.log(index) // 0 -> 0 1 -> 1 2 -> 2
},
false
)
})(i)
}
// 3.let/const
//块级作用域
let btns = document.querySelectorAll('.btn')
for (let i = 0; i < btns.length; i++) {
btns[i].addEventListener(
'click',
function () {
console.log(i) // 0 -> 0 1 -> 1 2 -> 2
},
false
)
}
模板字符串
模板字符串与一般字符串的区别
const person = {
username: '小龙',
age: 18,
sex: 'male'
}
const info1 = '我的名字是:' + person.username + ', 性别:' + person.sex + ', 今年' + person.age + '岁了'
console.log(info1)
const info2 = `我的名字是:${person.username}, 性别:${person.sex}, 今年${person.age}岁了`
console.log(info2)
和其他东西一起使用的时候,使用模板字符串,方便注入
其他情况下使用模板字符串或一般字符串都行
注意事项
1.输出多行字符串
const info1 = '第1行\n第2行'
console.log(info1)
//第1行
//第2行
const info2 = `第1行\n第2行`
const info3 = `第1行
第2行`
console.log(info2)
//第1行
//第2行
console.log(info3)
//第1行
//第2行
模板字符串中,所有的空格、换行或缩进都会被保留在输出之中
2.输出 ` 和 \ 等特殊字符
const info = `'`\`
console.log(info) // '`\
3.模板字符串的注入
const username = 'xiaohu'
const person = { age: 18, sex: 'male' }
const getSex = function (sex) {
return sex === 'male' ? '男' : '女'
}
const info = `${username}, ${person.age + 2}, ${getSex(person.sex)}`
console.log(info) // xiaohu, 20, 男
只要最终可以得出一个值的就可以通过 ${} 注入到模板字符串中
模板字符串应用
将数据动态添加到 ul 列表中
<p>学生信息表</p>
<ul id="list">
<li style="list-style: none">信息加载中……</li>
</ul>
// 数据
const students = [
{
username: '王五',
age: 18,
sex: 'male'
},
{
username: 'ZhangSan',
age: 28,
sex: 'male'
},
{
username: 'LiSi',
age: 20,
sex: 'female'
}
]
const list = document.getElementById('list')
let html = ''
for (let i = 0; i < students.length; i++) {
html += `<li>我的名字是:${students[i].username},${students[i].sex},${students[i].age}</li>`
}
list.innerHTML = html
箭头函数
什么是箭头函数
1.认识箭头函数
const add = (x, y) => {
return x + y
}
console.log(add(1, 1))
2.箭头函数的结构
const/let 函数名 = 参数 => 函数体
3.如何将一般函数改写成箭头函数
// 声明形式
function add() {}
// 声明形式->函数表达式形式
const add = function () {}
// 函数表达式形式->箭头函数
const add = () => {}
箭头函数注意事项
1.单个参数
// 单个参数可以省略圆括号
const add = x => {
return x + 1
}
console.log(add(1))
// 无参数或多个参数不能省略圆括号
const add = () => {
return 1 + 1
}
const add = (x, y) => {
return x + y
}
console.log(add(1, 1))
2.单行函数体
// 单行函数体可以同时省略 {} 和 return
const add = (x, y) => {
return x + y
}
const add = (x, y) => x + y
console.log(add(1, 1))
// 多行函数体不能再化简了
const add = (x, y) => {
const sum = x + y
return sum
}
3.单行对象
const add = (x, y) => {
return {
value: x + y
}
}
// 如果箭头函数返回单行对象,可以在 {} 外面加上 (),让浏览器不再认为那是函数体的花括号
const add = (x, y) => ({ value: x + y })
console.log(add(1, 1))
箭头函数的 this 指向
先复习一下非箭头函数中的this指向
// 1.全局作用域中的 this 指向
console.log(this) // window
// 2.一般函数(非箭头函数)中的 this 指向
'use strict'
function add() {
console.log(this)
}
add() // 严格模式就指向 undefined 非严格模式下指向 undefined -> window
const calc = {
add: add
}
calc.add() // calc
const adder = calc.add
adder() // undefined->window(非严格模式下)
//点击事件
document.onclick = function () {
console.log(this)
}
document.onclick() // document
//构造函数
function Person(username) {
this.username = username
console.log(this) // p
}
const p = new Person('xiaozhang')
// 只有在函数调用的时候 this 指向才确定,不调用的时候,不知道指向谁
// this 指向和函数在哪儿调用没关系,只和谁在调用有关
// 没有具体调用对象的话,this 指向 undefined,在非严格模式下,转向 window
说回箭头函数,箭头函数没有自己的 this,它会指向外层作用域的 this
const calc = {
add: () => {
console.log(this)
}
}
calc.add() // window
// 2.练习
// 'use strict'
const calc = {
add: function () {
// this
const adder = () => {
console.log(this)
}
adder()
}
}
calc.add() // calc
// adder 的 this -> add 方法中的 this , add 方法被 calc 调用 -> add 方法中的 this 指向 calc
// 所以 adder 的 this 指向 calc
const addFn = calc.add
addFn() // undefined->window
// addFn 函数表达式直接调用,this 指向 window (非严格模式) , undefined(严格模式)
不适用箭头函数的场景
1.作为构造函数
// 箭头函数没有 this 构造函数中的 this 指向实例对象 所以不能这么写
const Person = () => {}
new Person()
2.需要 this 指向调用对象的时候
document.onclick = () => {
console.log(this) //window
}
document.addEventListener(
'click',
() => {
console.log(this) //window
},
false
)
3.需要使用 arguments 的时候
// 箭头函数中没有 arguments
function add() {
console.log(arguments)
}
add(1, 2, 3, 4, 5) // Arguments(5) [1, 2, 3, 4, 5, callee: ƒ, Symbol(Symbol.iterator): ƒ]
const add2 = () => console.log(arguments)
add2() // Uncaught ReferenceError: arguments is not defined
箭头函数的应用
<button id="btn">开始</button>
<span id="result">0</span>
const btn = document.getElementById('btn')
const result = document.getElementById('result')
//以前的做法
// const timer = {
// time: 0,
// start: function () {
// // 提前保存一下 this 有时候也用 self
// var that = this
// btn.addEventListener(
// 'click',
// function () {
// setInterval(function () {
// console.log(this)
// that.time++
// result.innerHTML = that.time
// }, 1000)
// },
// false
// )
// }
// }
// 箭头函数的做法
// 箭头函数没有自己的 this ,利用这个特性,可以让它指向 start 方法
const timer = {
time: 0,
start: function () {
// this
btn.addEventListener(
'click',
() => {
// this
setInterval(() => {
console.log(this)
this.time++
result.innerHTML = this.time
}, 1000)
},
false
)
}
}
timer.start()
解构赋值
解构赋值是什么
1.认识解构赋值
// 1.认识解构赋值
// const arr = [1, 2, 3]
// const a = arr[0]
// const b = arr[1]
// const c = arr[2]
// console.log(a, b, c) // 1 2 3
const [a, b, c] = [1, 2, 3]
console.log(a, b, c) // 1 2 3
2.什么是解构赋值
解析某一数据的结构,将我们想要的东西提取出来,赋值给变量或常量
数组解构赋值的原理
// 1.模式(结构)匹配
;[] = [1, 2, 3]
// 2.索引值相同的完成赋值
const [a, b, c] = [1, 2, 3]
console.log(a, b, c) // 1 2 3
// 不取的,可以直接用逗号跳过
const [a, [, , b], c] = [1, [2, 4, 5], 3]
console.log(a, b, c) // 1 5 3
数组解构赋值的默认值
// 1.默认值的基本用法
const [a, b] = [] // undefined undefined
const [a, b] = [undefined, undefined] // undefined undefined
const [a = 1, b = 2] = [] // 1 2
console.log(a, b)
// 2.默认值的生效条件
// 只有当一个数组成员严格等于(===)undefined 时,对应的默认值才会生效
const [a = 1, b = 2] = [3, 0] // 3 0
const [a = 1, b = 2] = [3, null] // 3 null
const [a = 1, b = 2] = [3] // 3 2
console.log(a, b)
// 3.默认值表达式
// 如果默认值是表达式,默认值表达式是惰性求值的
const func = () => {
console.log('我被执行了')
return 2
}
// const [x = func()] = [1] // 1
const [x = func()] = [] // 2
console.log(x)
数组解构赋值的应用
// 1.常见的类数组的解构赋值
// arguments
function func() {
const [a, b] = arguments
console.log(a, b) // 1 2
}
// func()
func(1, 2)
// NodeList
console.log(document.querySelectorAll('p')) // [p, p, p]
const [p1, p2, p3] = document.querySelectorAll('p')
console.log(p1, p2, p3) // <p>1</p> <p>2</p> <p>3</p>
// 2.函数参数的解构赋值
const array = [1, 1]
// const add = arr => arr[0] + arr[1]
const add = ([x = 0, y = 0]) => x + y
console.log(add(array)) // 2
console.log(add([])) // 0
// 3.交换变量的值
let x = 1
let y = 2
// let tmp = x
// x = y
// y = tmp
// console.log(x, y)
;[x, y] = [y, x] // 2 1
console.log(x, y)
对象解构赋值的原理
// 1.模式(结构)匹配
// {}={}
// 2.属性名相同的完成赋值
const { age, username } = { username: 'xiaofeng', age: 18 }
// const { age: age, username: username } = { username: 'xiaofeng', age: 18 }
console.log(age, username) // 18 "xiaofeng"
// 取别名
const { age: age, username: uname } = { username: 'xiaofeng', age: 18 }
console.log(age, uname) // 18 "xiaofeng"
对象解构赋值的注意事项
// 1.默认值的生效条件
// 对象的属性值严格等于 undefined 时,对应的默认值才会生效
const { username = 'ZhangSan', age = 0 } = { username: 'lisi' }
console.log(username, age) // lisi 0
// 2.默认值表达式
// 如果默认值是表达式,默认值表达式是惰性求值的
// 3.将一个已经声明的变量用于解构赋值
// 如果将一个已经声明的变量用于对象的解构赋值,整个赋值需在圆括号中进行
let x = 2
;({ x } = { x: 1 })
console.log(x) // 1
// 4.可以取到继承的属性
const { toString } = {}
console.log(toString) // ƒ toString() { [native code] }
对象解构赋值的应用
// 1.函数参数的解构赋值
// 以前的做法
// const logPersonInfo = user => console.log(user.username, user.age)
// 现在的做法
const logPersonInfo = ({ age = 0, username = 'ZhangSan' }) => console.log(username, age)
logPersonInfo({ username: 'lisi', age: 18 }) // lisi 18
// 相当于 { age, username:username }={ username: 'lisi', age: 18 }
logPersonInfo({}) // ZhangSan 0
// 2.复杂的嵌套
const obj = {
x: 1,
y: [2, 3, 4],
z: {
a: 5,
b: 6
}
}
const { x, y, z } = obj
console.log(x, y, z) // 1 [2, 3, 4] {a: 5, b: 6}
const {
y,
y: [, yy],
z,
z: { b }
} = obj
console.log(yy, y, z, b) // 3 (3) [2, 3, 4] {a: 5, b: 6} 6
// [, yy] = [2, 3, 4] yy 是 y 数组的第二个元素
其它数据类型的解构赋值
// 1.字符串的解构赋值
// 数组形式的解构赋值
const [a, b, , , c] = 'hello'
console.log(a, b, c) // h e o
// 对象形式的解构赋值
const { 0: a, 1: b, length } = 'hello'
console.log(a, b, length) // h e 5
console.log('hello'.length) // 5
// 字符串既可以按数组形式来解构赋值,也可以按对象形式来解构赋值
// 2.数值和布尔值的解构赋值
// 先将等号右边的值转为对象
console.log(new Number(123)) // Number {123}
const { a = 1, toString } = 123
console.log(a, toString) // 1 ƒ toString() { [native code] }
const { b = 2, toString } = true
console.log(b, toString) // 2 ƒ toString() { [native code] }
// 3.undefined 和 null 的解构赋值
// 由于 undefined 和 null 无法转为对象,所以对它们进行解构赋值,都会报错
const { toString } = undefined
// es6.js:25 Uncaught TypeError: Cannot destructure property 'toString' of 'undefined' as it is undefined.
const { toString } = null
// Uncaught TypeError: Cannot destructure property 'toString' of 'null' as it is null.
对象字面量的增强
属性和方法的简洁表示法
// 1.对象字面量是什么
// 实例化构造函数生成对象
const person = new Object()
person.age = 18
person.speak = function () {}
// 对象字面量
const person = {
age: 18,
speak: function () {}
}
// 2.属性的简洁表示法
// 键名和变量或常量名一样的时候,可以只写一个
const age = 18
const person = {
// age: age
age
}
console.log(person) // {age: 18}
// 3.方法的简洁表示法
// 方法可以省略冒号和 function 关键字
const person = {
// speak: function () {}
speak() {}
}
console.log(person) // {speak: ƒ}
方括号语法
// 1.方括号语法的用法
const prop = 'age'
const person = {}
// 相当于 person.prop = 18
person[prop] = 18
// 方括号语法可以写在对象字面量中
const person = {
[prop]: 18
}
console.log(person) // {age: 18}
// 2.方括号中可以放什么
// [值或通过计算可以得到值的(表达式)] 比如变量 函数 字符串 字符串拼接
const prop = 'age'
const func = () => 'age2'
const person = {
// [prop]: 18
// [func()]: 18
// ['sex']: 'male'
['s' + 'ex']: 'male'
}
console.log(person) // {sex: "male"}
// 3.方括号语法和点语法的区别
// 点语法是方括号语法的特殊形式
// const person = {}
// person.age 等价于 person['age']
// 属性名由数字、字母、下划线以及 $ 构成,并且数字还不能打头的时候可以使用点语法
// age18_$ √
// 18age ×
// 合法标识符可以用来作为变量或常量名
// 当你的属性或方法名是合法标识符时,可以使用点语法,其他情况下请使用方括号语法
函数参数默认值
函数参数的默认参数值是什么
// 1.认识函数参数的默认值
// 调用函数的时候传参了,就用传递的参数;如果没传参,就用默认值
multiply(2, 1)
multiply(2)
// 2.函数参数默认值的基本用法
const multiply = (x, y) => {
if (typeof y === 'undefined') {
y = 1
}
return x * y
}
// 新的写法
const multiply = (x, y = 1) => x * y
console.log(multiply(2)) // 2
函数参数默认值的注意事项
// 1.默认值的生效条件
// 不传参数,或者明确的传递 undefined 作为参数,只有这两种情况下,默认值才会生效
const multiply = (x, y = 1) => x * y
console.log(multiply(2, 0)) // 0
console.log(multiply(2, null)) // 0
console.log(multiply(2, undefined)) // 2
console.log(multiply(2)) // 2
// 2.默认值表达式
// 如果默认值是表达式,默认值表达式是惰性求值的
// 3.设置默认值的小技巧
// 函数参数的默认值,最好从参数列表的右边开始设置
// const multiply = (x = 1, y) => x * y
// console.log(multiply(undefined, 2)) // 2 第一个参数如果是默认值,想使用默认值需要传 undefined
const multiply = (x, y = 1) => x * y
console.log(multiply(2)) // 2
// 3.设置默认值的小技巧
// 函数参数的默认值,最好从参数列表的右边开始设置
// const multiply = (x = 1, y) => x * y
// console.log(multiply(undefined, 2)) // 2 第一个参数如果是默认值,想使用默认值需要传 undefined
const multiply = (x, y = 1) => x * y
console.log(multiply(2)) // 2
函数参数默认值的应用
// 1.接收很多参数的时候
// 不推荐
const logUser = (username = 'ZhangSan', age = 0, sex = 'male') => console.log(username, age, sex)
logUser('lisi', 18, 'male') // lisi 18 male
logUser() // ZhangSan 0 male
// 2.接收一个对象作为参数
// 推荐 解构赋值
const logUser = ({ username = 'zhangsan', age = 0, sex = 'male' } = {}) => console.log(username, age, sex)
logUser({
username: 'lisi',
age: 18,
sex: 'male'
}) // lisi 18 male
logUser({}) // zhangsan 0 male
logUser() // zhangsan 0 male
剩余参数
剩余参数是什么
// 1.认识剩余参数
const add = (x, y, z, ...args) => {}
// 2.剩余参数的本质
const add = (x, y, ...args) => {
console.log(x, y, args)
}
add() // undefined undefined []
add(1) // 1 undefined []
add(1, 2) // 1 2 []
add(1, 2, 3, 4, 5) // 1 2 (3) [3, 4, 5]
// 剩余参数永远是个数组,即使没有值,也是空数组
剩余参数的注意事项
// 1.箭头函数的剩余参数
// 箭头函数的参数部分即使只有一个剩余参数,也不能省略圆括号
const add = (...args) => {}
// 2.使用剩余参数替代 arguments 获取实际参数
const add = function () {
console.log(arguments) // Arguments(2) [1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]
}
const add = (...args) => {
console.log(args)
}
add(1, 2) // [1, 2]
// 3.剩余参数的位置
// 剩余参数只能是最后一个参数,之后不能再有其他参数,否则会报错
const add = (x, y, ...args,z) => {
console.log(args) // Uncaught SyntaxError: Rest parameter must be last formal parameter
}
剩余参数的应用
// 1.完成 add 函数
const add = (...args) => {
let sum = 0
for (let i = 0; i < args.length; i++) {
sum += args[i]
}
// reduce
return sum
}
console.log(add()) // 0
console.log(add(1, 1)) // 2
console.log(add(1, 2, 3)) // 6
// 2.与解构赋值结合使用
// 剩余参数不一定非要作为函数参数使用
const [num, ...args] = [1, 2, 3, 4]
console.log(num, args) // 1 [2, 3, 4]
// 和数组解构赋值结合起来使用
const func = ([num, ...args]) => {
console.log(...args)
}
func([1, 2, 3]) // 2 3
// 剩余元素(报错提示是元素) 和解构赋值结合来用就不适合称之为参数
// 必须是最后一个 否则报错 Uncaught SyntaxError: Rest element must be last element
const { x,...z ,y } = { a: 3, x: 1, y: 2, b: 4 }
const func = ({ x, y, ...z }) => {
console.log({ ...z })
}
func({ a: 3, x: 1, y: 2, b: 4 }) // {a: 3, b: 4}
展开运算符
数组展开运算符的基本用法
// 1.认识展开运算符
// Math.min 只接受参数列表,不接受数组
console.log(Math.min([3, 1, 2])) // NaN
console.log(Math.min(3, 1, 2)) // 1
// [3, 1, 2]->3, 1, 2
// 2.数组展开运算符的基本用法
console.log(Math.min(...[3, 1, 2]))
// 相当于
// console.log(Math.min(3, 1, 2))
区分剩余参数和展开运算符
// 1.根本区别
// 展开运算符
// [3,1,2]->3,1,2
// 剩余参数
// 3,1,2->[3,1,2]
// 2.区分剩余参数和展开运算符
// 剩余参数
const add = (...args) => {
console.log(args) // [1, 2, 3]
// 展开运算符
console.log(...args) // 1 2 3
console.log(...[1, 2, 3]) // 1 2 3
console.log(1, 2, 3) // 1 2 3
}
add(1, 2, 3)
console.log([...[1, 2, 3], 4]) // [1, 2, 3, 4]
// [1, 2, 3]->1,2,3
数组展开运算符的应用
// 1.复制数组
const a = [1, 2]
const b = a
a[0] = 3
console.log(b) // [3,2]
const c = [...a]
a[0] = 5
console.log(a) // [5, 2]
console.log(c) // [3, 2]
// 2.合并数组
const a = [1, 2]
const b = [3]
const c = [4, 5]
console.log([...a, ...b, ...c]) // [1, 2, 3, 4, 5]
console.log([...b, ...a, ...c]) // [3, 1, 2, 4, 5]
console.log([1, ...b, 2, ...a, ...c, 3]) // [1, 3, 2, 1, 2, 4, 5, 3]
// 3.字符串转为数组
// 字符串可以按照数组的形式展开
console.log(...'alex') // a l e x
console.log('a', 'l', 'e', 'x') // a l e x
console.log([...'alex']) // ["a", "l", "e", "x"]
console.log('alex'.split('')) // ["a", "l", "e", "x"]
// 4.常见的类数组转化为数组
// arguments
function func() {
console.log([...arguments])
}
func(1, 2)
// NodeList
console.log(document.querySelectorAll('p'))
console.log([...document.querySelectorAll('p')].push)
对象展开运算符的基本用法
// 1.展开对象
// 对象不能直接展开,必须在 {} 中展开
const apple = {
color: '红色',
shape: '球形',
taste: '甜'
}
console.log(...apple) // Uncaught TypeError: Found non-callable @@iterator
console.log([...apple]) // Uncaught TypeError: apple is not iterable
// 对象的展开:把属性罗列出来,用逗号分隔,放到一个 {} 中,构成新对象
console.log({ ...apple }) // {color: "红色", shape: "球形", taste: "甜"}
console.log({ ...apple } === apple) // false
// 2.合并对象
const apple = {
color: '红色',
shape: '球形',
taste: '甜'
}
const pen = {
color: '黑色',
shape: '圆柱形',
use: '写字'
}
console.log({ ...pen }) // {color: "黑色", shape: "圆柱形", use: "写字"}
console.log({ ...apple, ...pen }) // {color: "黑色", shape: "圆柱形", taste: "甜", use: "写字"}
// 新对象拥有全部属性,相同属性,后者覆盖前者
console.log({ pen, apple }) // {pen: {…}, apple: {…}}
console.log({ ...pen, apple }) // {color: "黑色", shape: "圆柱形", use: "写字", apple: {…}}
对象展开运算符的注意事项
// 1.空对象的展开
// 如果展开一个空对象,则没有任何效果
console.log({ ...{} }) // {}
console.log({ ...{}, a: 1 }) // {a: 1}
// 2.非对象的展开
// 如果展开的不是对象,则会自动将其转为对象,再将其属性罗列出来
console.log({ ...1 }) // {}
console.log(new Object(1)) // Number {1}
console.log({ ...undefined }) // {}
console.log({ ...null }) // {}
console.log({ ...true }) // {}
// 如果展开运算符后面是字符串,它会自动转成一个类似数组的对象,因此返回的不是空对象
console.log({ ...'hello' }) // {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
console.log([...'hello']) // ["h", "e", "l", "l", "o"]
console.log(...'hello') // h e l l o
console.log({ ...[1, 2, 3] }) // {0: 1, 1: 2, 2: 3}
// 3.对象中对象属性的展开
// 不会展开对象中的对象属性
const apple = {
feature: {
taste: '甜'
}
}
const pen = {
feature: {
color: '黑色',
shape: '圆柱形'
},
use: '写字'
}
console.log({ ...apple }) // {feature: {…}}
console.log({ ...apple, ...pen }) // {feature: {…}, use: "写字"}
对象展开运算符的应用
// 1.复制对象
const a = { x: 1, y: 2 }
// const b = a // 复制引用地址
const c = { ...a } // 复制对象
console.log(c, c === a) // {x: 1, y: 2} false
// 2.用户参数和默认参数
// 第一种
const logUser = ({ username = 'ZhangSan', age = 0, sex = 'male' } = {}) => {
console.log(username, age, sex) // {x: 1, y: 2} false
}
//第二种
const logUser = userParam => {
const defaultParam = {
username: 'ZhangSan',
age: 0,
sex: 'male'
}
const param = { ...defaultParam, ...userParam }
console.log(param.username) // ZhangSan
const { username, age, sex } = { ...defaultParam, ...userParam }
console.log(username, age, sex) // ZhangSan 0 male
}
logUser()
Set
Set 是什么
// 1.什么是 Set
// 集合
// [1, 2]
// 数组是一系列有序的数据集合
// Set 是一系列无序、没有重复值的数据集合
// 2.理解 Set
const s = new Set()
s.add(1)
s.add(2)
// Set 中不能有重复的成员
s.add(1)
console.log(s) // Set(2) {1, 2}
// Set 没有下标去标示每一个值,所以 Set 是无序的,也不能像数组那样通过下标去访问 Set 的成员
Set 实例的方法和属性
// 1.方法
// add
const s = new Set()
s.add(1).add(2).add(2)
console.log(s) // Set(2) {1, 2}
// // has
// console.log(s.has(1)) // true
// console.log(s.has(3)) // false
// // delete
// s.delete(1)
// console.log(s) // Set(1) {2}
// // 使用 delete 删除不存在的成员,什么都不会发生,也不会报错
// s.delete(3)
// console.log(s) // Set(1) {2}
// // clear
// s.clear()
// console.log(s) // Set(0) {}
// forEach
s.forEach(function (value, key, set) {
// Set 中 value = key
console.log(value, key, set === s) // 1 1 true // 2 2 true
console.log(this) // document
}, document)
console.log(s)
// 按照成员添加进集合的顺序遍历
// 2.属性
// size
console.log(s.size) // 2
console.log(s) // Set(2) {1, 2}
Set 构造函数的参数
// 1.数组
const s = new Set([1, 2, 1])
console.log(s) // Set(2) {1, 2}
// // 2.字符串、arguments、NodeList、Set 等
console.log(new Set('hi')) // Set(2) {"h", "i"}
function func() {
console.log(new Set(arguments))
}
func(1, 2, 1) // {1, 2}
console.log(new Set(document.querySelectorAll('p'))) // Set(3) {p, p, p}
const s = new Set([1, 2, 1])
console.log(new Set(s) === s) // false set当做参数相当于复制了一个 set
console.log(s) // {1, 2}
Set 的注意事项
// 1.判断重复的方式
const s = new Set([NaN, 2, NaN])
console.log(1 === 1) // true
console.log(NaN === NaN) // false
// Set 对重复值的判断基本遵循严格相等(===)
// 但是对于 NaN 的判断与 === 不同,Set 中 NaN 等于 NaN
console.log(s) // Set(2) {NaN, 2}
const s = new Set()
s.add({}).add({})
console.log({} === {}) // false
console.log(s) // Set(2) {{…}, {…}}
// 2.什么时候使用 Set
// ① 数组或字符串去重时
// ② 不需要通过下标访问,只需要遍历时
// ③ 为了使用 Set 提供的方法和属性时(add delete clear has forEach size 等)
Set 的应用
// 1.数组去重
const s = new Set([1, 2, 1])
console.log(s) // Set(2) {1, 2}
console.log(...s) // 1 2
console.log([...s]) // [1, 2]
console.log([...new Set([1, 2, 1])]) // [1, 2]
// 2.字符串去重
const s = new Set('abbacbd')
console.log([...s].join('')) // abcd
console.log(s) // Set(4) {"a", "b", "c", "d"}
console.log([...new Set('abbacbd')].join('')) // abcd
// 3.存放 DOM 元素
console.log(document.querySelectorAll('p')) // NodeList(3) [p, p, p]
const s = new Set(document.querySelectorAll('p'))
s.forEach(function (elem) {
elem.style.color = 'red'
elem.style.backgroundColor = 'yellow'
})
Map
Map 是什么
// 1.认识 Map
// 映射
// Map 和对象都是键值对的集合
// 键->值,key->value
const person = {
name: 'xiaolong',
age: 18
}
const m = new Map()
m.set('name', 'xiaolong')
m.set('age', 18)
console.log(m) // Map(2) {"name" => "xiaolong", "age" => 18}
// 2.Map 和对象的区别
// 对象一般用字符串当作键
const obj = {
name: 'xiaolong',
true: 'true',
[{}]: 'object'
}
console.log(obj) // {name: "xiaolong", true: "true", [object Object]: "object"}
console.log({}.toString()) // [object Object]
// 基本数据类型:数字、字符串、布尔值、undefined、null
// 引用数据类型:对象([]、{}、函数、Set、Map 等)
// 以上都可以作为 Map 的键
const m = new Map()
m.set('name', 'xiaolong')
m.set(true, 'true')
m.set({}, 'object')
m.set(new Set([1, 2]), 'set')
m.set(undefined, 'undefined')
console.log(m) // Map(5) {"name" => "xiaolong", true => "true", {…} => "object", Set(2) => "set", undefined => "undefined"}
Map 实例的属性和方法
// 1.方法
// set
const m = new Map()
// 使用 set 添加的新成员,键如果已经存在,后添加的键值对覆盖已有的
m.set('age', 18).set(true, 'true').set('age', 20)
console.log(m) // Map(2) {"age" => 20, true => "true"}
// get
console.log(m.get('age')) // 20
// get 获取不存在的成员,返回 undefined
console.log(m.get('true'))
console.log(m.get(true)) // true
// has
console.log(m.has('age')) // true
console.log(m.has('true')) // false
// delete
m.delete('age')
console.log(m) // Map(1) {true => "true"}
m.delete('name')
// 使用 delete 删除不存在的成员,什么都不会发生,也不会报错
console.log(m) // Map(1) {true => "true"}
// clear
m.clear()
console.log(m) // Map(0) {}
const m = new Map()
// 使用 set 添加的新成员,键如果已经存在,后添加的键值对覆盖已有的
m.set('age', 18).set(true, 'true').set('age', 20)
// forEach
m.forEach(function (value, key, map) {
console.log(value, key, map === m) // 20 "age" true // true true true
console.log(this) // document // document
}, document)
// 2.属性
// size
// 对象没有类似的属性
console.log(m.size) // 2
Map 构造函数的参数
// 1.数组
console.log(new Map(['name', 'xiaohu', 'age', 18]))
// Uncaught TypeError: Iterator value name is not an entry object
// 只能传二维数组,而且必须体现出键和值
console.log(
new Map([
['name', 'xiaohu'],
['age', 18]
])
) // Map(2) {"name" => "xiaohu", "age" => 18}
// 2.Set、Map 等
// Set 中也必须体现出键和值
const s = new Set([
['name', 'xiaohu'],
['age', 18]
])
console.log(new Map(s)) // Map(2) {"name" => "xiaohu", "age" => 18}
console.log(s) // Set(2) {Array(2), Array(2)}
// Map
// 复制了一个新的 Map
const m1 = new Map([
['name', 'xiaohu'],
['age', 18]
])
console.log(m1) // Map(2) {"name" => "xiaohu", "age" => 18}
const m2 = new Map(m1)
console.log(m2, m2 === m1) // Map(2) {"name" => "xiaohu", "age" => 18} false
Map 的注意事项
// 1.判断键名是否相同的方式
// 基本遵循严格相等(===)
// 例外就是 NaN,Map 中 NaN 也是等于 NaN
console.log(NaN === NaN) // false
const m = new Map()
m.set(NaN, 1).set(NaN, 2)
console.log(m) // Map(1) {NaN => 2}
// 2.什么时候使用 Map
// 如果只是需要 key -> value 的结构,或者需要字符串以外的值做键,使用 Map 更合适
// 只有模拟现实世界的实体时,才使用对象
Map 的应用
const [p1, p2, p3] = document.querySelectorAll('p')
console.log(p1, p2, p3) // <p>1</p><p>2</p><p>3</p>
// 第一种使用map给p标签添加颜色
const m = new Map()
m.set(p1, 'red')
m.set(p2, 'green')
m.set(p3, 'blue')
console.log(m) // Map(3) {p => "red", p => "green", p => "blue"}
// 第二种使用map给p标签添加颜色
const m = new Map([
[
p1,
{
color: 'red',
backgroundColor: 'yellow',
fontSize: '40px'
}
],
[
p2,
{
color: 'green',
backgroundColor: 'pink',
fontSize: '40px'
}
],
[
p3,
{
color: 'blue',
backgroundColor: 'orange',
fontSize: '40px'
}
]
])
m.forEach((propObj, elem) => {
for (const p in propObj) {
elem.style[p] = propObj[p]
}
})
console.log(m) //Map(3) {p => {…}, p => {…}, p => {…}}
Iterator
Iterator是什么
// 1.Iterator 的作用
// Iterator:遍历器(迭代器)
// for() 接触最多的遍历
// [1,2].forEach
// new Set().forEach
// Iterator 也是用来遍历的
// 2.寻找 Iterator
const it = [1, 2][Symbol.iterator]()
console.log(it) // Array Iterator {}
// 3.使用 Iterator
const it = [1, 2][Symbol.iterator]()
console.log(it.next()) // {value: 1, done: false}
console.log(it.next()) // {value: 2, done: false}
console.log(it.next()) // {value: undefined, done: true}
console.log(it.next()) // {value: undefined, done: true}
// it:可遍历对象(可迭代对象)
// Symbol.iterator:可遍历对象的生成方法
// 4.什么是 Iterator
// Symbol.iterator(可遍历对象的生成方法) -> it(可遍历对象) -> it.next() -> it.next() -> ...(直到 done 为 true)
Iterator 解惑
// 1.为什么需要 Iterator 遍历器
// 遍历数组:for 循环和 forEach 方法
// 遍历对象:for in 循环
// Iterator 遍历器是一个统一的遍历方式
console.log([][Symbol.iterator]()) // Array Iterator {}
console.log({}[Symbol.iterator]) // undefined -> 对象可以手动添加 Iterator
// 2.如何更方便的使用 Iterator
// Symbol.iterator->it->next()
// 我们一般不会直接使用 Iterator 去遍历
// for..of
for...of 的用法
// 1.认识 for...of
const arr = [1, 2, 3]
// 第一种
const it = arr[Symbol.iterator]()
console.log(it.next()) // {value: 1, done: false}
console.log(it.next()) // {value: 2, done: false}
console.log(it.next()) // {value: 3, done: false}
console.log(it.next()) // {value: undefined, done: true}
// 第二种
let next = it.next()
console.log(next) // {value: 1, done: false}
while (!next.done) {
next = it.next()
console.log(next) // {value: 2, done: false} // {value: 3, done: false} // {value: undefined, done: true}
}
// 第三种
for (const item of arr) {
console.log(item) // 1 // 2 // 3
}
// for...of 循环只会遍历出那些 done 为 false 时,对应的 value 值
// 2.与 break、continue 一起使用
const arr = [1, 2, 3]
for (const item of arr) {
if (item === 2) {
// break
continue
}
console.log(item) // 1 3
}
// 3.在 for...of 中取得数组的索引
const arr = [1, 2, 3]
// keys() 得到的是索引的可遍历对象,可以遍历出索引值
console.log(arr.keys()) // Array Iterator {}
for (const key of arr.keys()) {
console.log(key) // 0 // 1 // 2
}
// values() 得到的是值的可遍历对象,可以遍历出值
for (const value of arr.values()) {
console.log(value) // 1 // 2 // 3
}
for (const value of arr) {
console.log(value) // 1 // 2 // 3
}
// entries() 得到的是索引+值组成的数组的可遍历对象
for (const entries of arr.entries()) {
console.log(entries) // [0, 1] // [1, 2] // [2, 3]
}
// 解构赋值
for (const [index, value] of arr.entries()) {
console.log(index, value) // 0 1 // 1 2 // 2 3
}
原生可遍历和非原生可遍历
// 1.什么是可遍历
// 只要有 Symbol.iterator 方法,并且这个方法可以生成可遍历对象,就是可遍历的
// 只要可遍历,就可以使用 for...of 循环来统一遍历
// 2.原生可遍历的有哪些
// 数组
// 字符串
// Set
// Map
// arguments
// NodeList
for (const item of [1, 2, 3]) {
console.log(item) // 1 // 2 // 3
}
for (const item of 'hi') {
console.log(item) // h // i
}
for (const item of new Set([1, 2])) {
console.log(item) // 1 // 2
}
for (const item of new Map([
['hello', 'world'],
[1, 2]
])) {
console.log(item) // ["hello", "world"] // [1, 2]
}
for (const elem of document.querySelectorAll('p')) {
console.log(elem)
elem.style.color = 'red' // <p style="color: red;">1</p> // <p style="color: red;">2</p> // <p style="color: red;">3</p>
}
// 3.非原生可遍历的有哪些
// 一般的对象
const person = { sex: 'male', age: 18 }
console.log(person[Symbol.iterator]()) // Uncaught TypeError: person[Symbol.iterator] is not a function
//手写对象的 iterator 方法
person[Symbol.iterator] = () => {
let index = 0
return {
next() {
index++
if (index === 1) {
return {
value: person.age,
done: false
}
} else if (index === 2) {
return {
value: person.sex,
done: false
}
} else {
return {
done: true
}
}
}
}
}
for (const item of person) {
console.log(item) // 18 // male
}
// 有 length 和索引属性的对象
const obj = {
0: 'xiaohu',
1: 'male',
length: 2
}
// 直接拿数组的方法过来使用
// obj[Symbol.iterator] = Array.prototype[Symbol.iterator]
// 自己手写
obj[Symbol.iterator] = () => {
let index = 0
return {
next() {
let value, done
if (index < obj.length) {
value = obj[index]
done = false
} else {
value = undefined
done = true
}
index++
return {
value,
done
}
}
}
}
for (const item of obj) {
console.log(item) // xiaohu // male
}
使用了 Iterator 的场合
// 原生可遍历的
// Array 数组
// String 字符串
// Set
// Map
// 函数的 arguments 对象
// NodeList 对象
// for...of
// 1.数组的展开运算符
console.log(...[1, 2, 3]) // 1 2 3
console.log(1, 2, 3) // 1 2 3
console.log(...'str') // s t r
console.log(...new Set([1, 2, 3])) // 1 2 3
console.log(...{}) // 对象没有iterator,直接使用会报错 Uncaught TypeError: Found non-callable @@iterator
// 2.数组的解构赋值
// const [a, b] = [1, 2] // 1 2
// const [a, b] = [...[1, 2]] // 1 2
// const [a, b] = 'hi' // h i
// const [a, b] = [...'hi'] // h i
// const [a, b] = [...new Set([3, 4])] // 3 4
console.log(a, b)
// 3.Set 和 Map 的构造函数
// new Set(iterator)
// new Map(iterator)
ES6的新增方法
字符串的新增方法
includes()
// 判断字符串中是否含有某些字符
// 1.基本用法
console.log('abc'.includes('a')) // true
console.log('abc'.includes('ab')) // true
console.log('abc'.includes('bc')) // true
console.log('abc'.includes('ac')) // false
// 2.第二个参数
// 表示开始搜索的位置,默认是 0
console.log('abc'.includes('a')) // true
console.log('abc'.includes('a', 0)) // true
console.log('abc'.includes('a', 1)) // false
// 3.应用
// https://www.imooc.com/course/list
// https://www.imooc.com/course/list?c=fe&sort=pop&name=value
let url = 'https://www.imooc.com/course/list?'
const addURLParam = (url, name, value) => {
url += url.includes('?') ? '&' : '?'
url += `${name}=${value}`
return url
}
url = addURLParam(url, 'c', 'fe')
console.log(url) // https://www.imooc.com/course/list?&c=fe
url = addURLParam(url, 'sort', 'pop')
console.log(url) // https://www.imooc.com/course/list?&c=fe&sort=pop
padStart() 和 padEnd()
// 补全字符串长度
// 1.基本用法
console.log('x'.padStart(5, 'ab')) // ababx
console.log('x'.padEnd(5, 'ab')) // xabab
console.log('x'.padEnd(4, 'ab')) // xaba
// 2.注意事项
// 原字符串的长度,等于或大于最大长度,不会消减原字符串,字符串补全不生效,返回原字符串
console.log('xxx'.padStart(2, 'ab')) // xxx
console.log('xxx'.padEnd(2, 'ab')) // xxx
// 用来补全的字符串与原字符串长度之和超过了最大长度,截去超出位数的补全字符串,原字符串不动
console.log('abc'.padStart(10, '0123456789')) // 0123456abc
console.log('abc'.padEnd(10, '0123456789')) // abc0123456
// 如果省略第二个参数,默认使用空格补全长度
console.log('x'.padStart(4)) // ' x'
console.log('x'.padEnd(4)) // 'x '
// 3.应用
// 显示日期格式
// 2020
// 10
// 10
// 2020-10-10
// 2020-01-01
console.log('10'.padStart(2, 0)) // 10
console.log('1'.padStart(2, 0)) // 01
trimStart() 和 trimEnd()
// 清除字符串的首或尾空格,中间的空格不会清除
// 1.基本用法
const s = ' a b c '
console.log(s) // ' a b c '
console.log(s.trimStart()) // 'a b c '
console.log(s.trimLeft()) // 'a b c '
console.log(s.trimEnd()) // ' a b c'
console.log(s.trimRight()) // ' a b c'
console.log(s.trim()) // 'a b c'
// 2.应用
const usernameInput = document.getElementById('username')
const btn = document.getElementById('btn')
btn.addEventListener(
'click',
() => {
console.log(usernameInput.value)
// 验证
console.log(usernameInput.value.trim())
if (usernameInput.value.trim() !== '') {
// 可以提交
console.log('可以提交')
} else {
// 不能提交
console.log('不能提交')
}
// 手动提交
},
false
)
数组的新增方法
includes()
// 1.基本用法
// 判断数组中是否含有某个成员
console.log([1, 2, 3].includes('2')) // false
console.log([1, 2, 3].includes(2)) // true
// 第二个参数表示搜索的起始位置,默认值是 0
console.log([1, 2, 3].includes(2, 2)) // false
// 基本遵循严格相等(===),但是对于 NaN 的判断与 === 不同,includes 认为 NaN === NaN
console.log(NaN === NaN) // false
console.log([1, 2, NaN].includes(NaN)) // true
// 2.应用
// 去重
// [1, 2, 1]
const arr = []
for (const item of [1, 2, 1]) {
if (!arr.includes(item)) {
arr.push(item)
}
}
console.log(arr) // [1, 2]
Array.from()
// 将其他数据类型转换成数组
// 1.基本用法
console.log(Array.from('str')) // ["s", "t", "r"]
// 2.哪些可以通过 Array.from() 转换成数组
// 2.1.所有可遍历的
// 数组、字符串、Set、Map、NodeList、arguments
console.log(Array.from(new Set([1, 2, 1]))) // [1, 2]
console.log([...new Set([1, 2, 1])]) // [1, 2]
// 2.2.拥有 length 属性的任意对象
const obj = {
0: 'a',
1: 'b',
name: 'xiaolong',
length: 3
}
console.log(Array.from(obj)) // ["a", "b", undefined] 只会转化 number key 的 value ,其它 undefined
console.log([...obj]) // Uncaught TypeError: obj is not iterable 对象不可遍历,所以报错
// 3.第二个参数
// 作用类似于数组的 map 方法,用来对每个元素进行处理,将处理后的值放入返回的数组
console.log(
[1, 2].map(value => {
return value * 2
})
) // [2, 4]
console.log(Array.from('12', value => value * 2)) // [2, 4]
console.log(Array.from('12').map(value => value * 2)) // [2, 4]
// 4.第三个参数
Array.from(
'12',
value => {
console.log(this) // window // window
},
document
)
Array.from(
'12',
function () {
console.log(this) //document // document
},
document
)
find() 和 findIndex()
// find():找到满足条件的一个立即返回
// findIndex():找到满足条件的一个,立即返回其索引
// 1.基本用法
console.log(
[1, 5, 10, 15].find((value, index, arr) => {
console.log(this) // window // window // window
return value > 9
}, document) // 10
)
console.log(
[1, 5, 10, 15].find(function (value, index, arr) {
console.log(this) // document // document // document
return value > 9
}, document) // 10
)
console.log(
[1, 5, 10, 15].findIndex((value, index, arr) => {
return value > 9
}, document) // 2
)
// 2.应用
const students = [
{
name: '张三',
sex: '男',
age: 16
},
{
name: '李四',
sex: '女',
age: 22
},
{
name: '王二麻子',
sex: '男',
age: 32
}
]
console.log(students.find(value => value.sex === '女')) // {name: "李四", sex: "女", age: 22}
console.log(students.findIndex(value => value.sex === '女')) // 1
对象的新增方法
Object.assign()
// 用来合并对象
// 1.基本用法
// Object.assign(目标对象, 源对象1,源对象2,...): 目标对象
const apple = {
color: '红色',
shape: '圆形',
taste: '甜'
}
const pen = {
color: '黑色',
shape: '圆柱形',
use: '写字'
}
console.log(Object.assign(apple, pen)) // {color: "黑色", shape: "圆柱形", taste: "甜", use: "写字"}
// Object.assign 直接合并到了第一个参数中,返回的就是合并后的对象
console.log(apple) // {color: "黑色", shape: "圆柱形", taste: "甜", use: "写字"}
console.log(Object.assign(apple, pen) === apple) // true
// 可以合并多个对象
console.log(Object.assign({}, apple, pen)) // {color: "黑色", shape: "圆柱形", taste: "甜", use: "写字"}
console.log(apple) // {color: "黑色", shape: "圆柱形", taste: "甜", use: "写字"}
console.log({ ...apple, ...pen }) // {color: "黑色", shape: "圆柱形", taste: "甜", use: "写字"}
// 2.注意事项
// 2.1.基本数据类型作为源对象
// 与对象的展开类似,先转换成对象,再合并
console.log(Object.assign({}, undefined)) // {}
console.log(Object.assign({}, null)) // {}
console.log(Object.assign({}, 1)) // {}
console.log(Object.assign({}, true)) // {}
console.log(Object.assign({}, 'str')) // {0: "s", 1: "t", 2: "r"}
// 2.2.同名属性的替换
// 后面的直接覆盖前面的
const apple = {
color: ['红色', '黄色'],
shape: '圆形',
taste: '甜'
}
const pen = {
color: ['黑色', '银色'],
shape: '圆柱形',
use: '写字'
}
console.log(Object.assign({}, apple, pen)) // {color: ["黑色", "银色"], shape: "圆柱形", taste: "甜", use: "写字"}
// 3.应用
// 合并默认参数和用户参数
const logUser = userOptions => {
const DEFAULTS = {
username: 'ZhangSan',
age: 0,
sex: 'male'
}
const options = Object.assign({}, DEFAULTS, userOptions)
// const options = Object.assign({}, DEFAULTS, undefined) 不传参 undefined 转换成对象是 {}
console.log(options)
}
logUser() // {username: "ZhangSan", age: 0, sex: "male"}
logUser({}) // {username: "ZhangSan", age: 0, sex: "male"}
logUser({ username: 'wangwu' }) // {username: "wangwu", age: 0, sex: "male"}
Object.keys()、Object.values() 和 Object.entries()
// 1.基本用法
const person = {
name: 'xiaolong',
age: 18
}
console.log(Object.keys(person)) // ["name", "age"]
console.log(Object.values(person)) // ["xiaolong", 18]
console.log(Object.entries(person)) // [["name", "age"],["xiaolong", 18]]
// 2.与数组类似方法的区别
console.log([1, 2].keys()) // Array Iterator {}
console.log([1, 2].values()) // Array Iterator {}
console.log([1, 2].entries()) // Array Iterator {}
console.log(person.keys) // undefined
// 数组的 keys()、values()、entries() 等方法是实例方法,返回的都是 Iterator
// 对象的 Object.keys()、Object.values()、Object.entries() 等方法是构造函数方法,返回的是数组
// Object.keys() 方法之前就有,ES6 补全了 value 和 entries 方法,为了保证一致性 , Object 的三个方法采用 ES6 之前的做法
// 3.使用 for...of 循环遍历对象
const person = {
name: 'xiaohu',
age: 18
}
for (const key of Object.keys(person)) {
console.log(key) // name // age
}
for (const value of Object.values(person)) {
console.log(value) // xiaohu // 18
}
for (const entries of Object.entries(person)) {
console.log(entries) // ["name", "xiaohu"] // ["age", 18]
}
for (const [key, value] of Object.entries(person)) {
console.log(key, value) // name xiaohu // age 18
}
// Object.keys()/values()/entires() 并不能保证顺序一定是你看到的样子,这一点和 for in 是一样的
Promise
Promise 是什么
// 1.认识 Promise
// Promise 是异步操作的一种解决方案
// 回调函数
document.addEventListener(
'click',
() => {
console.log('这里是异步的')
},
false
)
console.log('这里是同步的')
// 2.什么时候使用 Promise
// Promise 一般用来解决层层嵌套的回调函数(回调地狱 callback hell)的问题
// 运动
const move = (el, { x = 0, y = 0 } = {}, end = () => {}) => {
el.style.transform = `translate3d(${x}px, ${y}px, 0)`
el.addEventListener(
'transitionend',
() => {
// console.log('end');
end()
},
false
)
}
const boxEl = document.getElementById('box')
document.addEventListener(
'click',
() => {
move(boxEl, { x: 150 }, () => {
move(boxEl, { x: 150, y: 150 }, () => {
move(boxEl, { y: 150 }, () => {
// console.log('object');
move(boxEl, { x: 0, y: 0 })
})
})
})
},
false
)
* {
padding: 0;
margin: 0;
}
#box {
width: 300px;
height: 300px;
background-color: red;
transition: all 0.5s;
}
<div id="box"></div>
Promise 的基本用法
// 1.实例化构造函数生成实例对象
console.log(Promise)
// Promise 解决的不是回调函数,而是回调地狱
const p = new Promise(() => {})
// 2.Promise 的状态
const p = new Promise((resolve, reject) => {
// Promise 有 3 种状态,一开始是 pending(未完成),执行 resolve,变成 fulfilled(resolved),已成功
// 执行 reject,变成 rejected,已失败
// Promise 的状态一旦变化,就不会再改变了
// pending->fulfilled
// resolve()
// pending->rejected
reject()
})
// 3.then 方法
p.then(
() => {
console.log('success')
},
() => {
console.log('error')
}
)
// 4.resolve 和 reject 函数的参数
const p = new Promise((resolve, reject) => {
// Promise 有 3 种状态,一开始是 pending(未完成),执行 resolve,变成 fulfilled(resolved),已成功
// 执行 reject,变成 rejected,已失败
// Promise 的状态一旦变化,就不会再改变了
// pending->fulfilled
resolve('succ')
resolve({ username: 'xiaolong' })
// pending->rejected
reject('reason')
reject(new Error('reason'))
})
p.then(
data => {
console.log('success', data)
},
err => {
console.log('error', err)
}
)
console.log(p)
Promise的实例方法
then()
// 1.什么时候执行
// pending->fulfilled 时,执行 then 的第一个回调函数
// pending->rejected 时,执行 then 的第二个回调函数
// 2.执行后的返回值
// then 方法执行后返回一个新的 Promise 对象
const p = new Promise((resolve, reject) => {
resolve()
// reject()
})
const p2 = p
.then(
() => {},
() => {}
)
.then()
.then()
console.log(p, p2, p === p2) // Promise {<fulfilled>: undefined} Promise {<pending>} false
// 3.then 方法返回的 Promise 对象的状态改变
const p = new Promise((resolve, reject) => {
// resolve()
reject()
})
p.then(
() => {
// console.log('success')
},
() => {
console.log('err') // err
// 在 then 的回调函数中,return 后面的东西,会用 Promise 包装一下
// return undefined
// 等价于
// return new Promise(resolve => {
// resolve(undefined)
// })
return 123
// return new Promise(resolve => {
// resolve(123)
// })
// 默认返回的永远都是成功状态的 Promise 对象
// return new Promise((resolve, reject) => {
// reject('reason')
// })
}
)
.then(
data => {
console.log('success2', data) // success2 123
// return undefined
return new Promise(resolve => {
resolve(undefined)
})
},
err => {
console.log('err2', err)
}
)
.then(
data => {
console.log('success3', data) // success3 undefined
},
err => {
console.log('err3', err)
}
)
// 4.使用 Promise 解决回调地狱
// 运动
const move = (el, { x = 0, y = 0 } = {}, end = () => {}) => {
el.style.transform = `translate3d(${x}px, ${y}px, 0)`
el.addEventListener(
'transitionend',
() => {
end()
},
false
)
}
const boxEl = document.getElementById('box')
const movePromise = (el, point) => {
return new Promise(resolve => {
move(el, point, () => {
resolve()
})
})
}
document.addEventListener(
'click',
() => {
movePromise(boxEl, { x: 150 })
.then(() => {
return movePromise(boxEl, { x: 0, y: 0 })
})
.then(() => {
return movePromise(boxEl, { x: 150, y: 150 })
})
.then(() => {
return movePromise(boxEl, { y: 150 })
})
},
false
)
catch()
// 1.有什么用
then(
data => {},
err => {}
)
then(data => {})
// catch 专门用来处理 rejected 状态
// catch 本质上是 then 的特例
then(null, err => {})
// 2.基本用法
new Promise((resolve, reject) => {
// resolve(123)
reject('reason')
})
.then(data => {
console.log(data)
})
// .then(null, err => {
// console.log(err)
// })
.catch(err => {
console.log(err) // reason
// return undefined
throw new Error('reason') // Error: reason
})
.then(data => {
console.log(data)
})
.catch(err => {
console.log(err)
})
// catch() 可以捕获它前面的错误
// 一般总是建议,Promise 对象后面要跟 catch 方法,这样可以处理 Promise 内部发生的错误
finally()
// 1.什么时候执行
// 当 Promise 状态发生变化时,不论如何变化都会执行,不变化不执行
new Promise((resolve, reject) => {
// resolve(123)
reject('reason')
})
.finally(data => {
console.log(data)
})
.catch(err => {})
// 2.本质
// finally() 本质上是 then() 的特例
new Promise((resolve, reject) => {
// resolve(123)
reject('reason')
})
.finally(data => {
console.log(data)
})
.catch(err => {})
// 等同于
new Promise((resolve, reject) => {
// resolve(123)
reject('reason')
})
.then(
result => {
return result
},
err => {
return new Promise((resolve, reject) => {
reject(err)
})
}
)
.then(data => {
console.log(data)
})
.catch(err => {
console.log(err)
})
Promise.resolve() 和 Promise.reject()
// 1.Promise.resolve()
// 是成功状态 Promise 的一种简写形式
new Promise(resolve => resolve('foo'))
// 简写
Promise.resolve('foo')
// 参数
// 一般参数
Promise.resolve('foo').then(data => {
console.log(data) // foo
})
// Promise
// 当 Promise.resolve() 接收的是 Promise 对象时,直接返回这个 Promise 对象,什么都不做
const p1 = new Promise(resolve => {
setTimeout(resolve, 1000, '我执行了')
// 相当于
// setTimeout(() => {
// resolve('我执行了')
// }, 1000)
})
Promise.resolve(p1).then(data => {
console.log(data) // 我执行了
})
// 等价于
// p1.then(data => {
// console.log(data) // 我执行了
// })
console.log(Promise.resolve(p1) === p1) // true
// 当 resolve 函数接收的是 Promise 对象时,后面的 then 会根据传递的 Promise 对象的状态变化决定执行哪一个回调
new Promise(resolve => resolve(p1)).then(data => {
console.log(data) // 我执行了
})
// 具有 then 方法的对象
// 立即执行对象里的then()
// 对象里面的then()带有两个形式参数分别表示 resolve, reject 函数
// function func(obj) {
// obj.then(1, 2)
// }
// func({
// then(resolve, reject) {
// console.log(a, b)
// }
// })
// 等价于
const thenable = {
then(resolve, reject) {
resolve('data') // data
// reject('reason')
}
}
Promise.resolve(thenable).then(
data => console.log(data),
err => console.log(err)
)
console.log(Promise.resolve(thenable)) // Promise {<pending>} 后面的then不能返回 ,通过对象里的 resolve 和 reject 函数调用决定then执行什么
// 2.Promise.reject()
// 失败状态 Promise 的一种简写形式
new Promise((resolve, reject) => {
reject('reason') // Uncaught (in promise) reason
})
// 等价于
// Promise.reject('reason') // Uncaught (in promise) reason
// 参数
// 不管什么参数,都会原封不动地向后传递,作为后续方法的参数
const p1 = new Promise(resolve => {
setTimeout(resolve, 1000, '我执行了')
})
Promise.reject(p1).catch(err => console.log(err)) // Promise {<pending>}
new Promise((resolve, rejcet) => {
resolve(123)
})
.then(data => {
// return data
// return Promise.resolve(data) 简写形式
return Promise.reject('reason')
})
.then(data => {
console.log(data)
})
.catch(err => console.log(err)) // reason
Promise.all()
// 1.有什么用
// Promise.all() 关注多个 Promise 对象的状态变化
// 传入多个 Promise 实例,包装成一个新的 Promise 实例返回
// 2.基本用法
const delay = ms => {
return new Promise(resolve => {
setTimeout(resolve, ms)
})
}
const p1 = delay(1000).then(() => {
console.log('p1 完成了')
// return 'p1'
return Promise.reject('reason')
})
const p2 = delay(2000).then(() => {
console.log('p2 完成了')
return 'p2'
// return Promise.reject('reason')
})
// Promise.all() 的状态变化与所有传入的 Promise 实例对象状态有关
// 所有状态都变成 resolved,最终的状态才会变成 resolved
// 只要有一个变成 rejected,最终的状态就变成 rejected
const p = Promise.all([p1, p2])
p.then(
data => {
console.log(data)
},
err => {
console.log(err)
}
)
// p1 完成了 // reason // p2 完成了
Promise.race() 和 Promise.allSettled()
const delay = ms => {
return new Promise(resolve => {
setTimeout(resolve, ms)
})
}
const p1 = delay(1000).then(() => {
console.log('p1 完成了')
return 'p1 成功了直接输出最终 data'
// return Promise.reject('reason')
})
const p2 = delay(2000).then(() => {
console.log('p2 完成了')
// return 'p2'
return Promise.reject('reason')
})
// 1.Promise.race()
// Promise.race() 的状态取决于第一个完成的 Promise 实例对象,如果第一个完成的成功了,那最终的就成功;如果第一个完成的失败了,那最终的就失败
const racePromise = Promise.race([p1, p2])
racePromise.then(
data => {
console.log(data)
},
err => {
console.log(err)
}
)
// p1 完成了 // p1 成功了直接输出最终 data // p2 完成了
// 2.Promise.allSettled()
// Promise.allSettled() 的状态与传入的Promise 状态无关
// 永远都是成功的
// 它只会忠实的记录下各个 Promise 的表现
const allSettledPromise = Promise.allSettled([p1, p2])
allSettledPromise.then(data => {
console.log('succ', data) // succ (2) [{status: "fulfilled", value: "p1"}, {status: "rejected", reason: "reason"}]
})
Promise 的注意事项
// 1.resolve 或 reject 函数执行后的代码
// 推荐在调用 resolve 或 reject 函数的时候加上 return,不再执行它们后面的代码
new Promise((resolve, reject) => {
// return resolve(123)
return reject('reason')
})
// 2.Promise.all/race/allSettled 的参数问题
// 参数如果不是 Promise 数组,会将不是 Promise 的数组元素转变成 Promise 对象
Promise.all([1, 2, 3]).then(datas => {
console.log(datas)
})
// 等价于
// Promise.all([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]).then(datas => {
// console.log(datas)
// })
// 不只是数组,任何可遍历的都可以作为参数
// 数组、字符串、Set、Map、NodeList、arguments
Promise.all(new Set([1, 2, 3])).then(datas => {
console.log(datas)
})
// 3.Promise.all/race/allSettled 的错误处理
const delay = ms => {
return new Promise(resolve => {
setTimeout(resolve, ms)
})
}
const p1 = delay(1000).then(() => {
console.log('p1 完成了')
// return 'p1'
return Promise.reject('reason')
})
// .catch(err => {
// console.log('p1', err)
// })
const p2 = delay(2000).then(() => {
console.log('p2 完成了')
return 'p2'
// return Promise.reject('reason')
})
// .catch(err => {
// console.log('p2', err)
// })
const allPromise = Promise.all([p1, p2])
allPromise
.then(datas => {
console.log(datas)
})
.catch(err => console.log(err))
// 错误既可以单独处理,也可以统一处理
// 一旦被处理,就不会在其他地方再处理一遍
Promise 的应用
// 异步加载图片
const loadImgAsync = url => {
return new Promise((resolve, reject) => {
const img = new Image()
img.onload = () => {
resolve(img)
}
img.onerror = () => {
reject(new Error(`Could not load image at ${url}`))
}
img.src = url
})
}
const imgDOM = document.getElementById('img')
loadImgAsync('https://gitee.com/lulalalula/images/raw/master/img/20210517151714.png')
.then(img => {
setTimeout(() => {
imgDOM.src = img.src
}, 1000)
})
.catch(err => {
console.log(err)
})
<img src="https://gitee.com/lulalalula/images/raw/master/img/20210517151713.png" alt="" id="img" />
Class
Class 是什么
// 1.认识 Class
// 人类:类
// 具体的人:实例、对象
// 类可以看做是对象的模板,用一个类可以创建出许多不同的对象
// 2.Class 的基本用法
// 类名一般大写
// class Person {}
class Person {
// 实例化时执行构造方法,所以必须有构造方法,但可以不写出来
constructor(name, age) {
console.log('实例化时执行构造方法')
// this 代表实例对象,上面定义的是实例属性/方法
this.name = name
this.age = age
// 一般在构造方法中定义属性,方法不在构造方法中定义
// this.speak = () => {}
}
// 各实例共享的方法
speak() {
console.log('speak')
}
}
// Person()
const zs = new Person('ZS', 18)
const ls = new Person('LS', 28)
console.log(zs.name)
console.log(zs.age)
console.log(zs.speak)
zs.speak()
console.log(ls.name)
console.log(ls.age)
console.log(ls.speak)
console.log(zs.speak === ls.speak)
}
// 3.Class 与构造函数
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
speak() {
console.log('speak')
}
// run(){}
}
Person.prototype.run = function () {}
console.log(typeof Person)
console.log(Person.prototype.speak)
// function Person(name, age) {
// this.name = name
// this.age = age
// }
// Person.prototype.speak = function () {}
Class 的两种定义形式
// 1.声明形式
// class Person {
// constructor() {}
// speak() {}
// }
// 2.表达式形式
//构造函数
// function Person() {}
// const Person = function () {}
//类
const Person = class {
constructor() {
console.log('constructor')
}
speak() {}
}
new Person()
// (function () {
// console.log('func')
// })()
// func()
// 立即执行的匿名类
new (class {
constructor() {
console.log('constructor')
}
})()
实例属性、静态方法和静态属性
// 1.实例属性
// 方法就是值为函数的特殊属性
class Person {
//实例属性
age = 18
sex = 'male'
getSex = function () {
return this.sex
}
constructor(name, sex) {
this.name = name
// 上面的 age = 18 相当于 this.age = 18
// this.sex = sex
}
// speak() {
// this.age = 18
// }
}
const p = new Person('xiaohu')
console.log(p.name) // xiaohu
console.log(p.age) // 18
console.log(p.sex) // male
console.log(p.getSex()) // male
// 2.静态方法
// 类的方法
class Person {
constructor(name) {
this.name = name
}
speak() {
console.log('speak')
console.log(this)
}
static speak() {
console.log('人类可以说话')
// this 指向类
console.log(this)
}
}
const p = new Person('xiaohu')
p.speak() // speak // Person {name: "xiaohu"}
Person.speak() // 人类可以说话 // Class Person{...}
// 3.静态属性
// 类的属性
class Person {
constructor(name) {
this.name = name
}
// 不要这么写,目前只是提案,有兼容性问题
static version = '1.0'
// 折中的办法是写成类方法
static getVersion() {
return '1.0'
}
}
Person.version = '1.0'
const p = new Person('xiaohu')
console.log(p.name) // xiaohu
console.log(Person.version) // 1.0
console.log(Person.getVersion()) // 1.0
私有属性和方法
// 1.为什么需要私有属性和方法
// 一般情况下,类的属性和方法都是公开的
// 公有的属性和方法可以被外界修改,造成意想不到的错误
class Person {
constructor(name) {
this.name = name
}
speak() {
console.log('speak')
}
getName() {
return this.name
}
}
const p = new Person('xiaoliu')
console.log(p.name) // xiaoliu
p.speak() // speak
// ....
p.name = 'zs'
console.log(p.name) // zs
// 2.模拟私有属性和方法
// 2.1._ 开头表示私有
class Person {
constructor(name) {
this._name = name
}
speak() {
console.log('speak')
}
getName() {
return this._name
}
}
const p = new Person('xiaohu')
console.log(p.name) //undefined 约定俗成加_就是私有方法,调用name访问不到
p.name = 'zd'
console.log(p.getName()) // xiaohu
// 2.2.将私有属性和方法移出类
;(function () {
let name = ''
class Person {
constructor(username) {
// this.name = name
name = username
}
speak() {
console.log('speak')
}
getName() {
return name
}
}
// 将类添加到全局作用域中
window.Person = Person
})()
;(function () {
const p = new Person('xiaohu')
console.log(p.name) // undefined
console.log(p.getName()) // xiaohu
})()
extends
// 1.子类继承父类
class Person {
constructor(name, sex) {
this.name = name
this.sex = sex
this.say = function () {
console.log('say')
}
}
speak() {
console.log('speak')
}
static speak() {
console.log('static speak')
}
}
Person.version = '1.0'
class Programmer extends Person {
constructor(name, sex) {
super(name, sex)
}
}
const zs = new Programmer('zs', '男')
console.log(zs.name) // zs
console.log(zs.sex) // 男
zs.say() // say
zs.speak() // speak
Programmer.speak() // static speak
console.log(Programmer.version) // 1.0
class Person {
constructor(name, sex) {
this.name = name
this.sex = sex
this.say = function () {
console.log('say')
}
}
speak() {
console.log('speak')
}
static speak() {
console.log('static speak')
}
}
Person.version = '1.0'
// 2.改写继承的属性或方法
class Programmer extends Person {
constructor(name, sex, feature) {
// this 操作不能放在 super 前面
super(name, sex)
this.feature = feature
}
hi() {
console.log('hi')
}
// 同名覆盖
speak() {
console.log('Programmer speak')
}
static speak() {
console.log('Programmer static speak')
}
}
Programmer.version = '2.0'
const zs = new Programmer('zs', '男', '秃头')
console.log(zs.name) // zs
console.log(zs.sex) // 男
console.log(zs.feature) // 秃头
zs.say() // say
zs.speak() // Programmer speak
zs.hi() // hi
Programmer.speak() // Programmer static speak
console.log(Programmer.version) // 2.0
super
// 1.作为函数调用
// 代表父类的构造方法,只能用在子类的构造方法中,用在其他地方就会报错
// super 虽然代表了父类的构造方法,但是内部的 this 指向子类的实例
class Person {
constructor(name) {
this.name = name
console.log(this)
}
}
class Programmer extends Person {
constructor(name, sex) {
super(name, sex)
}
}
new Person('xiaolong') // Person {name: "xiaolong"}
new Programmer('xiaohu') // Programmer {name: "xiaohu"}
// 2.作为对象使用
// 2.1.在构造方法中使用或一般方法中使用
// super 代表父类的原型对象 Person.prototype
// 所以定义在父类实例上的方法或属性,是无法通过 super 调用的
// 通过 super 调用父类的方法时,方法内部的 this 指向当前的子类实例
class Person {
constructor(name) {
this.name = name
console.log(this) // this 指向 Person {name: "xiaolong"}
}
speak() {
console.log('speak')
console.log(this)
}
static speak() {
console.log('Person speak')
console.log(this)
}
}
class Programmer extends Person {
constructor(name, sex) {
super(name, sex) // Programmer {name: "xiaohu"} super作为函数调用,代表父类构造方法,this 指向当前子类实例
console.log(super.name) // undefined 因为 super 指向父类 prototype ,父类实例的属性无法调用
super.speak()
// speak spuer 作为对象调用的父类原型上的方法
// Programmer {name: "xiaohu"} 通过 super 对象调用父类方法,this 指向当前子类实例
}
speak() {
super.speak()
console.log('Programmer speak')
}
// 2.2.在静态方法中使用
// 指向父类,而不是父类的原型对象
// 通过 super 调用父类的方法时,方法内部的 this 指向当前的子类,而不是子类的实例
static speak() {
super.speak()
console.log('Programmer speak')
}
}
new Person('xiaolong')
new Programmer('xiaohu')
Programmer.speak()
// Person speak
// class Programmer extends Person {...}
// Programmer speak
// 3.注意事项
// 使用 super 的时候,必须显式指定是作为函数还是作为对象使用,否则会报错
class Person {
constructor(name) {
this.name = name
}
speak() {
console.log('speak')
}
}
class Programmer extends Person {
constructor(name, sex) {
super(name, sex)
console.log(super())
console.log(super.speak)
}
}
参考:
慕课前端课程