主题
3-2展开运算符和箭头函数
本页对应 第三章JavaScript进阶,共 5 段视频,约 48 分钟。
学习目标
- 能够说明
arguments对象只存在于函数内部、以伪数组形式保存所有实参,并用传统for循环完成不定数量实参的求和。 - 能够使用剩余参数
...args接收多余实参,并对比说明其与arguments在真数组、forEach使用及箭头函数支持上的区别。 - 能够使用展开运算符展开数组,完成数组最大值/最小值求解与数组合并,并解释其与剩余参数“展开”和“收集”的区别。
- 能够将普通匿名函数改写为箭头函数,并正确应用省略小括号、省略大括号与
return、返回对象时加小括号等简写规则。 - 能够分析箭头函数中
this沿用上一层作用域的特点,并判断在 DOM 事件回调或定时器等场景中是否应使用箭头函数。
知识点
函数提升:先声明,后调用更灵活
为什么需要:函数声明和变量一样,在代码执行前会经历“提升”。理解它,才能解释为什么有的函数能在写之前调用,有的却报错。
怎么想:函数提升提升到当前作用域最前面;只提升声明,不提升调用;函数表达式不存在提升现象;函数提升能让声明和调用顺序更灵活。
怎么做:观察下面代码,fn()能执行,是因为函数声明被提升;fun()在赋值前调用会报错,因为var fun只提升变量声明,此时值是undefined。
html
<body>
<script>
function fn() {
console.log('函数提升')
}
var fun
// 函数提升
// 1.函数提升提升到当前作用域最前面
// 2.函数提升只提升声明,不提升调用
// 3.函数表达式不存在提升的现象
// 4.函数提升能够使函数的声明调用更灵活
fn()
fun()
fun = function () {
console.log('函数表达式不存在提升')
}
fun()
</script>
</body>易错点:不要把“函数表达式”当“函数声明”。fun()写在赋值前会报TypeError,调试时先看调用位置是否早于赋值。
arguments对象:了解即可,能拿全部实参
为什么需要:封装求和函数时,实参个数不确定,形参没法提前写全。arguments能拿到调用时传入的所有实参。
怎么想:它是函数内部内置的伪数组,只存在于函数中;有length,能通过下标和for循环访问,但没有forEach等真数组方法。
怎么做:用传统for循环累加。转写稿里调试时发现:传1,2得到3,传1,2,3得到6,传1,2,3,4得到10。
html
<body>
<script>
// arguments对象获取所有实参
function sum() {
// 1. arguments 只存在于函数中 伪数组
// 2. arguments 可以得到传递过来的所有实参 [1, 2]
// console.log(arguments)
let sum = 0
for (let i = 0; i < arguments.length; i++) {
sum += arguments[i]
}
console.log(sum)
}
sum(1, 2)
sum(1, 2, 3)
sum(1, 2, 3, 4)
// console.log(arguments) 外面无法使用
</script>
</body>易错点:arguments是伪数组,不能直接forEach;函数外访问会报arguments is not defined。开发中更提倡剩余参数。
剩余参数:把多余实参收成真数组
为什么需要:arguments是伪数组,使用受限;剩余参数能把“剩余”的实参收集成真数组,还能解决形参和实参个数不匹配的问题。
怎么想:...other写在最末形参前,表示“后面不确定的实参都收进other”。other是真数组,可用forEach。
怎么做:求和时用forEach累加;只固定前两个参数时,可写function sum(x, y, ...other)。
html
<body>
<script>
// 剩余参数获得多余的实参 返回真数组
// function sum(x, y, ...other) {
// console.log(x, y, other)
// }
// sum(1, 2)
// sum(1, 2, 3)
// sum(1, 2, 3, 4)
// 1. 求和效果
function sum(...other) {
// console.log(other) // [1, 2, 3, 4]
let sum = 0
other.forEach(function (ele) {
sum += ele
})
console.log(sum)
}
sum(1, 2)
sum(1, 2, 3)
sum(1, 2, 3, 4)
</script>
</body>易错点:...是语法符号,必须放在最末形参前;使用变量时不要再写...。与arguments区别:剩余参数是真数组;箭头函数不支持arguments,但支持剩余参数。
展开运算符:把数组拆成一个个元素
为什么需要:Math.max不能直接接收数组,数组合并也不想得到二维数组。展开运算符能把数组“拆散”。
怎么想:剩余参数是“凝聚”,展开运算符是“拆散”。展开不修改原数组。
怎么做:console.log(...arr)展开输出;Math.max(...arr)求最大值;[...arr1, ...arr2]合并数组。转写稿调试:Math.max(arr)得到NaN,改成Math.max(...arr)得到3。
html
<body>
<script>
// 展开运算符:将一个数组/对象进行展开,我们先讲展开数组
// 1. 基本使用, 不修改原数组
const arr = [1, 2, 3]
console.log(...arr)
console.log(arr)
//2. 使用场景: 求数组最大值/最小值,数组合并
// console.log(Math.max(1, 3, 8))
console.log(Math.max(...arr)) // 3
console.log(Math.min(...arr)) // 1
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
// console.log([arr1, arr2])
console.log([...arr1, ...arr2])
</script>
</body>易错点:Math.max(arr)不是展开,会得到NaN;[arr1, arr2]是二维数组,合并要用[...arr1, ...arr2]。
箭头函数:更简洁的匿名函数写法
为什么需要:匿名函数场景很多,箭头函数比函数表达式更简洁。
怎么想:把function替换成=>。细节:只有一个形参可省小括号;函数体只有一句可省大括号并自动返回;返回对象要用小括号包裹;箭头函数没有arguments,但有剩余参数。
怎么做:
html
<body>
<script>
// 箭头函数
// 1. 基本语法
// const fn = function () {
// console.log('我是函数表达式')
// }
// fn()
// const fn = () => {
// console.log('我是箭头函数')
// }
// fn()
// 2. 细节使用
// 2.1 如果只有一个形参则可以省略小括号,其余个数不能省略,如果没有参数则写空的小括号
// const sum = (x) => {
// console.log(x + x)
// }
// sum(2)
// const sum = x => {
// console.log(x + x)
// }
// sum(2)
// 2.2 如果函数体只有一句代码,则可以省略大括号,这句代码就是返回值(省略return)
// const sum = x => {
// return x + x
// }
// const sum = x => x + x
// console.log(sum(5))
// 2.3 如果返回的是一个对象,则需要用小括号把对象包裹起来
// const fn = function() {
// return {name: '佩奇'}
// }
// const fn = () => ({ name: '佩奇' })
// console.log(fn())
// 2.4 箭头函数里面没有 arguments,但是有剩余参数
const fn = (...other) => {
// console.log(arguments)
console.log(other)
}
fn(1, 2)
</script>
</body>易错点:返回对象写成() => { name: '佩奇' }会把大括号当函数体;箭头函数属于表达式函数,不存在函数提升;剩余参数不能省小括号。
箭头函数中的this:沿用上一层作用域
为什么需要:普通函数this由调用者决定;箭头函数本身没有this,它沿用上一层作用域的this,用错场景会拿不到按钮。
怎么想:先找箭头函数外层作用域的this是谁。转写稿调试:btn1用箭头函数后this.style.color报错,因为this是window;定时器里普通函数this是window,改成箭头函数后沿用外层按钮的this,能重新启用按钮。
怎么做:
html
<body>
<button class="btn1">点击</button>
<button class="btn2">5秒后启用</button>
<script>
// 1. 以前this的指向: 指向调用者
// console.log(this) // window
// // 普通函数
// function fn() {
// console.log(this) // window
// }
// window.fn()
// // 对象方法里面的this
// const obj = {
// name: 'andy',
// sayHi: function () {
// console.log(this) // obj
// }
// }
// obj.sayHi()
// 2. 箭头函数的中this指向-沿用上一层作用域的this
const fn = () => {
console.log(this) // window
}
fn()
// const obj = {
// name: 'andy',
// sayHi: () => {
// console.log(this) // window
// }
// }
// obj.sayHi()
const obj = {
name: 'andy',
sayHi: function () {
const fun = () => {
console.log(this) // obj
}
fun()
}
}
obj.sayHi()
// 3. 我们可以根据需求来选择是否使用箭头函数 this
// document.querySelector('.btn1').addEventListener('click', function () {
// this.style.color = 'red'
// })
document.querySelector('.btn1').addEventListener('click', () => {
// this.style.color = 'red'
// 此处不能用 this 指向 Window不是 按钮了
document.querySelector('.btn1').style.color = 'red'
})
document.querySelector('.btn2').addEventListener('click', function () {
this.disabled = true
// setTimeout(function () {
// console.log(this) // 定时器里面的this 指向 window
// this.disabled = false
// }, 5000)
setTimeout(() => {
console.log(this) //
this.disabled = false
}, 5000)
})
</script>
</body>易错点:DOM事件里想用this指向按钮,就别用箭头函数;定时器里想沿用外层this,可用箭头函数。判断标准永远是“外层作用域的this是谁”。
ES6对象简写:属性与方法更紧凑
为什么需要:当属性名和变量名一致、方法还是匿名函数时,重复写很啰嗦。
怎么想:属性名和属性值一致时只写属性名;对象方法可省略: function。
怎么做:
html
<body>
用户名: <input type="text" class="username"> <br>
密 码: <input type="password" class="password"> <br>
<button>点击</button>
<script>
// ES6对象属性和方法的简写
// 1. 对象属性的简写 (点击按钮生成对象)
document.querySelector('button').addEventListener('click', function () {
const username = document.querySelector('.username').value
const password = document.querySelector('.password').value
// const obj = {
// username: username,
// password: password
// }
// 属性名和属性值相同的时候,可以只写属性名
// 2. 对象方法的简写
// const obj = {
// username,
// password,
// sayHi: function() {
// console.log('hi~')
// }
// }
const obj = {
username,
password,
sayHi() {
console.log('hi~')
}
}
console.log(obj)
})
</script>
</body>易错点:简写属性要求变量名和属性名一致;方法简写不是箭头函数,方法里的this仍按调用对象判断。
解构赋值到底解决什么问题
为什么需要:从数组或对象里取数据再逐个赋给变量,代码会重复、啰嗦。比如要先 arr[0]、arr[1],再写变量声明。
怎么想:解构就是“把结构拆开”,右侧提供数组或对象,左侧按相同结构写变量,让取值动作在一行里完成。
怎么做:数组解构看“位置”,对象解构看“属性名”。
易错点:不要把数组解构和对象解构的匹配规则混了:数组按顺序,对象按属性名。
数组解构:按位置依次赋值
为什么需要:数组取值依赖下标,多个变量赋值时重复写下标不简洁。
怎么想:右侧数组的值按位置赋给左侧变量,变量顺序对应值的位置。
怎么做:
html
<body>
<script>
// 数组解构
// 1. 基本语法
const [a, b, c] = [1, 2, 3]
console.log(a)
console.log(b)
console.log(c)
// 2. 典型的使用场景 交换2个变量的值
let x = 1
let y = 2;
[y, x] = [x, y]
console.log(x, y);
// 3. js 2个特殊情况需要加分号
// 3.1 如果是小括号开头的则需要加分号
(function () { })();
(function () { })();
// 3.2 如果是中括号开头的则需要加分号
</script>
</body>易错点:以 ( 或 [ 开头的语句前要注意分号,否则可能和上一行连在一起被解析。交换变量时 [y, x] = [x, y] 就是利用数组解构完成交换。
变量和值不匹配:默认值、剩余、按需忽略
为什么需要:实际数据可能多一个、少一个,不能假设左右完全一样。
怎么想:变量多值少,缺的得到 undefined;想避免 undefined 就给默认值;值多变量少,可以用剩余元素收集,或空位跳过。
怎么做:
html
<body>
<script>
// 数组解构变量和值不匹配的情况
// 1. 变量多,值少的情况
// const [a, b, c, d] = ['小米', '华为', '苹果']
// console.log(a)
// console.log(b)
// console.log(c)
// console.log(d) // undefined
// 2. 防止undefined传值,可以设置默认值
// const [a, b, c, d = '三星'] = ['小米', '华为', '苹果']
// console.log(a)
// console.log(b)
// console.log(c)
// console.log(d)
// 3. 变量少,值多的情况
// const [a, b] = ['小米', '华为', '苹果']
// console.log(a)
// console.log(b)
// 4. 利用剩余参数解决变量少值多的情况
// const [a, ...b] = ['小米', '华为', '苹果']
// console.log(a)
// console.log(b)
// 5. 按需导入,忽略某些值
const [a, , c, d] = ['小米', '华为', '苹果', 'vivo']
console.log(a)
console.log(c)
console.log(d)
</script>
</body>易错点:默认值不是“值少就一定用”,而是对应位置为 undefined 时才生效;...b 要放在最后,它收集剩余值。转写稿里把“真数组”误写成“帧数组”、把“伪数组”误写成“尾数组”,这里纠正:解构剩余得到的 b 是数组;它和展开运算符方向相反——解构里的 ... 是收集,展开运算符是把数组拆开。
对象解构:按属性名取值与重命名
为什么需要:对象属性多,逐个 user.username、user.age 取值重复。
怎么想:左侧变量名默认要和对象属性名一致;不一致时用“属性名: 新变量名”重命名。
怎么做:
html
<body>
<script>
const username = 'andy'
const user = {
username: '小明',
age: 18
}
// 1. 对象解构赋值基本使用
// const { username, age, gender } = user
// console.log(username) // 小明
// console.log(age) // 18
// console.log(gender) // undefined
// 1.1 要求变量名和属性名必须一致
// 1.2 如果变量名和属性名不一致,则默认为 undefined
// 1.3 变量名不要和外面的变量名冲突,否则会报错
// 2. 更改解构变量名(重命名) 变量名: 新变量名
// const { username: uname, age } = user
// console.log(uname) // 小明
// console.log(age) // 18
// 3. 对象数组解构
const arr = [
{
username: '小明',
age: 18
}
]
const [{ username: uname, age }] = arr
console.log(uname)
console.log(age)
</script>
</body>易错点:外部已经有 const username = 'andy',再写 const { username } = user 会变量名冲突,所以要重命名成 uname;对象里没有的属性解构出来是 undefined。
多维解构与函数参数解构
为什么需要:后台返回的数据常常是“对象里套数组、数组里套对象”,只取需要的一层能减少中间变量。
怎么想:按数据结构逐层对齐;函数参数也可以直接解构,调用时把整个对象传进去。
怎么做:
html
<body>
<script>
// 1. 这是后台传递过来的数据
const msg = {
"code": 200,
"msg": "获取新闻列表成功",
"data": [
{
"id": 1,
"title": "5G商用自己,三大运用商收入下降",
"count": 58
},
{
"id": 2,
"title": "国际媒体头条速览",
"count": 56
},
{
"id": 3,
"title": "乌克兰和俄罗斯持续冲突",
"count": 1669
},
]
}
// 需求1: 请将以上msg对象 采用对象解构的方式 只选出 data 方面后面使用渲染页面
// const { data } = msg
// console.log(data)
// 需求2: 上面msg是后台传递过来的数据,我们需要把data选出当做参数传递给 函数
// const { data } = msg
// msg 虽然很多属性,但是我们利用解构只要 data值
function render({ data }) {
// const { data } = arr
// 我们只要 data 数据
// 内部处理
console.log(data)
}
render(msg)
// 需求3, 为了防止msg里面的data名字混淆,要求渲染函数里面的数据名改为 myData
function render({ data: myData }) {
// 要求将 获取过来的 data数据 更名为 myData
// 内部处理
console.log(myData)
}
render(msg)
</script>
</body>易错点:函数参数解构 render({ data }) 表示调用时传入对象,并从对象里取 data;要改名就用 { data: myData }。演示时两个同名 render 不要同时保留,实际代码里保留一种写法即可。
课件图示








代码示例
示例一:函数参数(arguments / 剩余参数)与展开运算符
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>函数参数与展开运算符</title>
</head>
<body>
<script>
// 1. arguments:函数内部内置的伪数组,包含调用时传入的所有实参
function sum1() {
let sum = 0
for (let i = 0; i < arguments.length; i++) {
sum += arguments[i]
}
console.log('arguments求和:', sum)
}
sum1(1, 2) // 3
sum1(1, 2, 3) // 6
sum1(1, 2, 3, 4) // 10
// 2. 剩余参数:把多余的实参收集成一个“真数组”
function sum2(...other) {
let sum = 0
other.forEach(function (ele) {
sum += ele
})
console.log('剩余参数求和:', sum)
}
sum2(1, 2) // 3
sum2(1, 2, 3, 4) // 10
// 前两个参数固定,后面数量不定
function fn(x, y, ...other) {
console.log(x, y, other)
}
fn(1, 2) // 1 2 []
fn(1, 2, 3, 4) // 1 2 [3, 4]
// 3. 展开运算符:把数组拆散成一个个元素
const arr = [1, 2, 3]
console.log(...arr) // 1 2 3
console.log(arr) // [1, 2, 3] 原数组不受影响
console.log(Math.max(...arr)) // 3
console.log(Math.min(...arr)) // 1
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
console.log([...arr1, ...arr2]) // [1, 2, 3, 4, 5, 6]
</script>
</body>
</html>关键行与易错点
arguments是伪数组,只能用传统for循环遍历,不能调用forEach;并且只存在于函数内部,在函数外访问会报arguments is not defined。- 剩余参数
...other写在形参最末位置;定义时加...,使用时不要再加...,直接用变量名。 - 剩余参数得到的是真数组,可以使用
forEach、push等方法;开发中提倡优先使用剩余参数,arguments作为了解即可。 - 剩余参数(凝聚)与展开运算符(拆散)写法相同但语义相反:前者把多个实参收集成数组,后者把数组展开成多个元素。
Math.max(arr)直接传数组会返回NaN,必须先写Math.max(...arr)展开;展开运算符不会修改原数组。
示例二:箭头函数的语法细节与 this 指向
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>箭头函数与 this</title>
</head>
<body>
<button class="btn1">点击</button>
<button class="btn2">5秒后启用</button>
<script>
// 1. 基本语法:把 function 换成箭头
const fn = () => {
console.log('我是箭头函数')
}
fn()
// 2.1 只有一个形参可以省略小括号
const sum1 = x => x + x
console.log(sum1(5)) // 10
// 2.2 函数体只有一句代码,可省略大括号与 return,这句代码就是返回值
const sum2 = x => x + x
console.log(sum2(5)) // 10
// 2.3 返回对象时,要用小括号把对象包裹起来
const getObj = () => ({ name: '佩奇' })
console.log(getObj()) // {name: '佩奇'}
// 2.4 箭头函数没有 arguments,但有剩余参数
const fn2 = (...other) => {
console.log(other) // [1, 2]
}
fn2(1, 2)
// 3. 箭头函数本身没有 this,沿用上一层作用域的 this
const obj = {
name: 'andy',
sayHi: function () {
const fun = () => {
console.log(this) // obj
}
fun()
}
}
obj.sayHi()
// 4. 场景一:点击变红 —— 用普通函数,this 指向按钮
// document.querySelector('.btn1').addEventListener('click', function () {
// this.style.color = 'red'
// })
// 改成箭头函数后 this 指向 window,只能重新获取元素
document.querySelector('.btn1').addEventListener('click', () => {
document.querySelector('.btn1').style.color = 'red'
})
// 5. 场景二:点击禁用,5 秒后启用 —— 定时器里要用 this 就用箭头函数
document.querySelector('.btn2').addEventListener('click', function () {
this.disabled = true
setTimeout(() => {
console.log(this) // btn2,沿用外层普通函数的 this
this.disabled = false
}, 5000)
})
</script>
</body>
</html>关键行与易错点
- 箭头函数属于表达式函数,不存在函数提升,必须先声明再调用。
- 只有一个形参时才能省略小括号;没有参数必须写空小括号
();有两个及以上参数不能省略;有剩余参数(...other)时也不能省略小括号,否则报“意外的标记”。 - 省略大括号时才会自动
return;一旦写了大括号,就必须手动写return。 - 返回对象字面量必须写成
() => ({ name: '佩奇' }),直接写{}会被当成函数体,返回undefined。 - 箭头函数内没有
arguments,会报arguments is not defined,需要动态获取实参时用剩余参数。 - 事件回调里想用
this拿到 DOM 元素,就用普通函数;定时器等内部函数想沿用外层this,就用箭头函数。
示例三:解构赋值与 ES6 对象简写
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>解构赋值与对象简写</title>
</head>
<body>
用户名: <input type="text" class="username"><br>
密 码: <input type="password" class="password"><br>
<button>点击</button>
<script>
// 1. 数组解构:按位置一一对应
const [a, b, c] = [1, 2, 3]
console.log(a, b, c) // 1 2 3
// 交换两个变量的值
let x = 1
let y = 2
;[y, x] = [x, y]
console.log(x, y) // 2 1
// 2. 变量和值不匹配的情况
const [m, n, o, p = '三星'] = ['小米', '华为', '苹果']
console.log(m, n, o, p) // 小米 华为 苹果 三星(默认值兜底)
const [e, ...others] = ['小米', '华为', '苹果']
console.log(e, others) // 小米 ['华为', '苹果']
const [q, , s] = ['小米', '华为', '苹果'] // 逗号占位,忽略某些值
console.log(q, s) // 小米 苹果
// 3. 对象解构:变量名要和属性名一致
const user = {
username: '小明',
age: 18
}
const { username, age } = user
console.log(username, age) // 小明 18
// 重命名:属性名: 新变量名
const { username: uname } = user
console.log(uname) // 小明
// 4. 解构后台返回的数据,并在函数形参中直接解构
const msg = {
code: 200,
msg: '获取新闻列表成功',
data: [
{ id: 1, title: '5G商用自己,三大运用商收入下降', count: 58 },
{ id: 2, title: '国际媒体头条速览', count: 56 }
]
}
function render({ data: myData }) {
console.log(myData)
}
render(msg)
// 5. ES6 对象属性与方法的简写
document.querySelector('button').addEventListener('click', function () {
const username = document.querySelector('.username').value
const password = document.querySelector('.password').value
const obj = {
username, // 属性名和属性值同名,可只写属性名
password,
sayHi() { // 方法简写,不再写 function
console.log('hi~')
}
}
console.log(obj)
})
</script>
</body>
</html>关键行与易错点
- 数组解构按位置赋值:变量多、值少时多出来的变量是
undefined,可写d = '三星'设默认值;变量少、值多时可用...others收集剩余项。 - 用逗号占位
const [q, , s]可以跳过不需要的值,实现按需导入。 - 以
(或[开头的语句前面必须加分号,否则会和上一行连起来解析报错,例如;[y, x] = [x, y]。 - 对象解构要求变量名与属性名一致,不一致会得到
undefined;改名用属性名: 新变量名,且新变量名不要与外部变量重名,否则报错。 - 函数形参直接解构
function render({ data }) {},调用时传整个对象即可,只取需要的属性;改名写{ data: myData }。 - 对象简写要求变量名与属性名相同才能只写一个;方法简写
sayHi() {}里不能再写function。
常见错误
① 函数表达式在赋值语句之前调用
- 现象:
TypeError: fun is not a function,控制台停在调用那一行。 - 原因:
var fun只做了变量提升(值为undefined),函数表达式整体不参与提升,赋值动作发生在调用之后。 - 修法:把赋值挪到调用之前;若确实需要"先调用后定义",改用函数声明
function fn(){}。
js
fun() // ❌ TypeError
var fun = function () {}
fun() // ✅
fn() // ✅ 函数声明可以先用后写
function fn() {}② 剩余参数没放在形参最后,或在函数体内又写成 ...rest
- 现象:
SyntaxError: Rest parameter must be last formal parameter;或函数体里console.log(...other)拿到的不是数组。 - 原因:
...只能出现在形参列表末尾;函数体内...是展开运算符,语义完全不同。 - 修法:形参写作
(x, y, ...other);函数体内直接用other,不要再带三个点。
js
function sum(x, ...rest, y) {} // ❌ 语法错误
function sum(x, ...rest) {} // ✅
function f(...rest) { console.log(...rest) } // 打印 1 2,不是 [1,2]
function f(...rest) { console.log(rest) } // ✅ 打印 [1,2]③ 箭头函数返回对象时漏掉外层小括号
- 现象:
const fn = () => { name: '佩奇' }调用后得到undefined,控制台里name也不高亮。 - 原因:
{}被解析成函数体而不是对象字面量,里面的name:被当成标签语句。 - 修法:用小括号把对象包起来
() => ({ name: '佩奇' }),或老老实实写return {}。
④ 在箭头函数里用 this 操作 DOM / 拿事件源
- 现象:
Cannot read properties of undefined (reading 'style'),或点了按钮颜色不变、this打印出来是window。 - 原因:箭头函数没有自己的
this,沿用外层作用域;事件回调外层是全局作用域,this就是window。 - 修法:需要
this指向事件源的场景,回调写成普通函数function () {};确实要用箭头函数,就用document.querySelector(...)显式取元素。
js
btn.addEventListener('click', function () { this.style.color = 'red' }) // ✅
btn.addEventListener('click', () => { this.style.color = 'red' }) // ❌ this 是 window⑤ 定时器里用普通函数,想沿用外层 this
- 现象:5 秒后按钮没有恢复,
this打印为window,this.disabled = false静默失效或报错。 - 原因:
setTimeout由window调用,普通回调里的this指向window。 - 修法:定时器回调换成箭头函数,让它沿用外层(事件处理函数)的
this。
js
btn.addEventListener('click', function () {
this.disabled = true
setTimeout(() => { this.disabled = false }, 5000) // ✅ 箭头沿用 btn
})⑥ 在箭头函数里使用 arguments,或用 forEach 遍历 arguments
- 现象:
ReferenceError: arguments is not defined;或arguments.forEach is not a function。 - 原因:箭头函数没有
arguments绑定;arguments是伪数组,没有数组方法。 - 修法:箭头函数用
(...rest)代替;必须用arguments时用传统for循环或Array.from(arguments)。
⑦ 对象解构的变量名写错,或与外部同名变量冲突
- 现象:解构结果全是
undefined;或SyntaxError: Identifier 'username' has already been declared。 - 原因:对象解构按属性名匹配,不是按位置;
const { username } = user会新建同名变量,和外部const username重复声明。 - 修法:属性名与变量名不一致或重名时用重命名语法
{ username: uname };只取值不建变量的场景才可考虑重命名规避。
js
const username = 'andy'
const { username } = user // ❌ 重复声明
const { username: uname } = user // ✅
const { nick } = user // ❌ 对象没有 nick 属性,得到 undefined⑧ 直接 Math.max(arr) 求数组最值
- 现象:返回
NaN。 - 原因:
Math.max接收的是若干个数字参数,不是数组。 - 修法:展开后传入
Math.max(...arr);Math.max本身不修改原数组。
⑨ 解构语句紧跟上一行代码时缺少分号
- 现象:上一行以函数调用/赋值结尾,下一行以
(或[开头,报TypeError: ... is not a function或意外解析。 - 原因:JS 自动分号插入(ASI)不覆盖以
(、[开头的新行,被当成上一行表达式的延续。 - 修法:在
(、[开头的行首补分号,或上一行末尾显式写分号。
js
const a = 1
;[a, b] = [b, a] // 行首加分号
;(function () {})() // IIFE 前加分号练习
练习 1|参数收集
写一个 sum 函数,能对任意个实参求和;再写一个 sumFrom(start, ...nums),第一个参数作为初始值,其余全部累加。
js
function sum(...nums) {
let total = 0
nums.forEach(n => total += n)
return total
}
function sumFrom(start, ...nums) {
return nums.reduce((acc, n) => acc + n, start)
}
sum(1, 2, 3) // 6
sumFrom(10, 1, 2) // 13练习 2|展开运算符三连
给定 const arr = [3, 1, 8, 2]、const a = [1, 2]、const b = [3, 4],分别求最大值、最小值、合并成一个新数组,并说明原数组是否被修改。
js
Math.max(...arr) // 8
Math.min(...arr) // 1
const merged = [...a, ...b] // [1,2,3,4]
// 以上均不修改原数组练习 3|接口数据解构
从下面数据中,只取出 data 并重命名为 list,再用数组解构取第一条的 title 和 count,count 缺失时默认为 0。
js
const msg = { code: 200, data: [{ id: 1, title: '5G商用', count: 58 }] }
const { data: list } = msg
const [{ title, count = 0 }] = list练习 4|this 判断
下面两段代码能否达到预期?不能的话怎么改?
js
// A:点击后按钮变红
document.querySelector('.btn1').addEventListener('click', () => {
this.style.color = 'red'
})
// B:点击后禁用,5 秒后恢复
document.querySelector('.btn2').addEventListener('click', function () {
this.disabled = true
setTimeout(function () { this.disabled = false }, 5000)
})参考:A 不行,箭头函数 this 是 window,改为普通函数或显式查询元素;B 不行,定时器回调改为箭头函数 setTimeout(() => { this.disabled = false }, 5000)。
练习 5|综合改写
把下面的传统写法改成"对象简写 + 箭头函数 + 解构参数"的形式。
js
// 原版
function render(options) {
const data = options.data
const title = options.title
return { data: data, title: title, log: function () { console.log(title) } }
}
// 改写
const render = ({ data, title }) => ({
data,
title,
log() { console.log(title) }
})原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 函数参数-arguments对象 | 07:54 |
| 2 | 剩余参数 | 07:32 |
| 3 | 展开运算符 | 08:25 |
| 4 | 箭头函数的基本使用 | 12:32 |
| 5 | 箭头函数中的this | 11:54 |
小结
- 判断"能不能先用后写"只看一点:这个东西是声明还是赋值——函数声明能提升,函数表达式、箭头函数、
const/let变量都不能。 - 形参个数不确定时优先用剩余参数:它是真数组,能直接上
forEach/reduce,并且是唯一在箭头函数里可用的"收集实参"手段。 arguments只剩"读老代码"的价值:伪数组、只能在普通函数中用、不能用数组方法,新代码不要主动选它。- 记住一句话区分两个
...:出现在形参位置是凝聚(收成数组),出现在调用/字面量位置是拆散(展开成元素),方向相反、场景互不重叠。 - 解构的匹配规则是"数组看位置、对象看名字":数组缺值给
undefined、可用默认值或...兜底,对象名不一致必须重命名。 - 箭头函数的四个省略规则(省括号、省大括号、省
return、返回对象加圆括号)只是语法糖,写不写都不影响功能,可读性优先。 - 用不用箭头函数的唯一决策依据是
this该指向谁:需要指向事件源/调用者就用普通函数,需要沿用外层this(如定时器、map回调)就用箭头函数。 - 越是"简写"越容易踩坑:返回对象、
this、arguments、行首分号这四类问题,几乎都出现在把代码压缩成一行的时候,提交前重点回看这几处。