主题
数值新增特性与函数新增特性(Day2 2/4)
本页对应 Day02_ES6,共 6 段视频,约 1 小时 11 分。
学习目标
- 能声明并使用 rest 参数收集函数剩余实参,并说明其与
arguments在数据类型、命名方式和获取范围上的区别。 - 能使用 rest 参数配合数组方法编写函数,完成对任意数量实参的求和等计算任务。
- 能写出箭头函数的常见形式,并正确应用参数唯一时省略小括号、单条返回语句时省略大括号和
return的简写规则。 - 能说明箭头函数没有自身
arguments、没有自身this、不能作为构造函数、不能作为生成器函数的特点,并能判断其this指向。 - 能使用箭头函数编写数组排序、筛选、映射等回调函数场景。
- 能解释标签模板的调用形式及参数拆分规则,判断模板字符串中无变量和含变量时函数收到的参数结构。
知识点
本节回顾的定位
本节笔记是 ES6 基础内容的回顾提纲,重点包括 let/const、块级作用域、解构赋值、模板字符串和字符串新增方法。需要说明:转写稿主要对应后续“函数新增特性”等内容,和本节回顾提纲并不完全对应;为避免编造,下面严格按本节笔记范围扩写,不把 rest 参数、箭头函数等后续内容提前混入。原笔记代码目录先保留如下:
text
1. let 和 const
1.1 let 声明的变量
① 不能重复声明
② 不能提升
③ 全局变量不会作为全局对象的属性
④ 具有块级作用域
1.2 const
创建值不能修改的变量,称为常量
① 不能重复声明
② 不能提升
③ 全局变量不会作为全局对象的属性
④ 具有块级作用域
1.3 块级作用域
2. 解构赋值
2.1 数组解构赋值
2.2 对象解构赋值
3. 字符串新增特性
3.1 模板字符串
``
① 模板字符串直接写换行
② 字符串中插入变量、表达式非常方便 ${}
3.2 字符串对象新增方法
includes()
startsWith()
endsWith()
padStart()
padEnd()
trim()
trimStart()
trimEnd()
replaceAll()let 与 const:先判断“要不要改”
为什么需要:var 的问题不只是“旧”,而是它容易造成重复声明、变量提升、全局污染和块级作用域失效。let/const 让变量声明更可控。
怎么想:先问这个绑定后面是否还要重新赋值。要改,用 let;不准备改,用 const。const 限制的是“绑定不能重新指向”,不是“对象内容完全冻结”。
怎么做:
js
let count = 1;
count = 2;
const MAX = 10;
// MAX = 20; // 报错:常量不能重新赋值
const user = { name: '小乐' };
user.name = '小浪'; // 可以,改的是对象内部属性易错点:const 声明时必须初始化;let/const 不能在同一作用域重复声明;它们有暂时性死区,不能在声明前访问;全局 let/const 不会成为 window 的属性。
块级作用域:让变量只在花括号内活
为什么需要:循环变量、条件分支里的临时变量不应泄漏到外层。块级作用域让变量生命周期更清晰。
怎么想:看到 {},先判断里面声明的是 var 还是 let/const。只有后者才形成块级约束。
怎么做:
js
{
let a = 1;
const b = 2;
console.log(a, b);
}
// console.log(a); // 报错:a 只在块内有效易错点:var 没有块级作用域;对象字面量的大括号不是“作用域块”;循环中使用 let 能形成每轮独立绑定,这是 var 容易出错的地方。
解构赋值:按结构取值,少写重复代码
为什么需要:从数组或对象中取多个值时,传统写法重复且啰嗦。解构让“结构对应结构”。
怎么想:数组看位置,对象看属性名;需要改名时用“属性名: 新变量名”;需要兜底时给默认值。
怎么做:
js
const arr = [10, 20, 30];
const [x, y] = arr;
console.log(x, y); // 10 20
const obj = { name: '小乐', age: 18 };
const { name, age: userAge } = obj;
console.log(name, userAge); // 小乐 18易错点:数组解构按顺序,不按名字;对象解构变量名默认要和属性名一致;默认值只在对应值为 undefined 时生效;解构赋值语句外层加圆括号时要注意语法位置。
模板字符串:把拼接改成嵌入
为什么需要:传统字符串拼接引号、加号、换行都很乱。模板字符串让多行文本和变量插入更直观。
怎么想:普通文本直接写,变量或表达式放进 ${},反引号内换行会保留。
怎么做:
js
const name = '小乐';
const age = 18;
const msg = `我叫${name},
今年${age}岁。`;
console.log(msg);易错点:模板字符串用反引号,不是单双引号;${} 里可以写表达式,但不要把复杂逻辑堆太多;缩进和换行会原样进入字符串。
字符串新增方法:按用途记
为什么需要:判断包含、前后缀、补全、去空白、全部替换,都是实际开发高频操作。
怎么想:判断用 includes/startsWith/endsWith;补长度用 padStart/padEnd;去空白用 trim/trimStart/trimEnd;全局替换用 replaceAll。
怎么做:
js
const str = ' hello world ';
console.log(str.includes('hello')); // true
console.log(str.startsWith(' he')); // true
console.log(str.endsWith(' ')); // true
console.log('7'.padStart(2, '0')); // '07'
console.log(str.trim()); // 'hello world'
console.log('aaa'.replaceAll('a', 'b')); // 'bbb'易错点:includes 区分大小写;padStart 的参数是“目标总长度”,不是补几个字符;trim 返回新字符串,不改变原字符串;replaceAll 在旧环境可能需要注意兼容性。
回顾检查清单
能否说清 let/const 与 var 的四点差异;能否判断一个变量是否受块级作用域约束;能否写出数组、对象解构及重命名;能否用模板字符串替代加号拼接;能否按“判断、补全、去空白、替换”分类记住字符串新方法。达到这些,本节回顾就算过关。
rest 参数:先明确它解决什么问题
以前函数里想拿到全部实参,常用 arguments。但 arguments 是系统创建的伪数组,名字不能改,也不能直接使用数组方法。ES6 引入 rest 参数,形式是“点点点 + 变量名”,用于在函数中获取实参,尤其是“剩下的实参”,并替代 arguments 的常见用法。
怎么想:rest 参数本质上是声明了一个变量,这个变量是纯数组;它写在形参列表里,名字由开发者自定义。怎么做:
js
function fn01(...args) {
console.log(args);
}
fn01(); // []
fn01(100, 200, 250, 'hello', true); // [100, 200, 250, 'hello', true]调试时可以看到:什么都不传,args 是空数组,不报错;传多个实参,args 是包含全部实参的纯数组。
易错点:rest 参数必须放在其他形参后面,后面不能再写普通形参;只写一个 rest 参数时,小括号也不能省。还有,传入一个数组时,它是“一个整体元素”,不会被自动展开:
js
function fn(arr) {
console.log(arr); // [1, 2, 3] 是一个元素
}
fn([1, 2, 3]);rest 与 arguments 的区别
为什么需要对比:因为二者都能拿实参,但行为不同。怎么想:抓住三点——类型、名字、范围。
js
function fn02(name, age, ...data) {
console.log(name, age); // 高小乐 101
console.log(data); // ['司马姥姥', '欧阳姥姥', '爱新觉罗姥姥']
console.log(arguments); // 包含全部实参
}
fn02('高小乐', 101, '司马姥姥', '欧阳姥姥', '爱新觉罗姥姥');区别是:rest 得到纯数组,arguments 是伪数组;rest 变量名可自定义,arguments 名字由系统固定;rest 只收“没有对应形参的剩余实参”,arguments 收全部实参。易错点:不要以为 rest 一定拿全部实参,前面有形参时它只拿剩下的。
用 rest 改写求和
为什么需要:以前用 arguments 求和要先遍历;rest 是纯数组,可以直接用数组方法。怎么做:
js
function sum(...nums) {
return nums.reduce((prev, item) => prev + item);
}
console.log(sum(10, 20, 30)); // 60易错点:reduce 不传初始值时,空数组会报错;实际使用时可补初始值 0。这里重点是 rest 让“所有参数”变成了可直接处理的数组。
箭头函数的基本语法
箭头函数是一种声明函数的语法,可以不用 function 关键字,但创建出来的仍然是函数。怎么想:函数名通常用 const 声明,参数写在小括号里,函数体写在大括号里。
js
const fn01 = () => {};
console.log(typeof fn01); // function
const fn02 = (name, age) => {
console.log(`我叫${name},年龄是${age}`);
};
fn02('高小乐', 101);省略规则要分清:只有一个普通形参时,可省略小括号;函数体只有一条 return 语句时,可同时省略大括号和 return;两个条件都满足时可写到极简。
js
const fn03 = num => {
console.log(num * 2 + 100);
};
const fn04 = (n1, n2) => n1 * n2;
const fn05 = item => item * item;
console.log(fn05(5)); // 25易错点:无参数时小括号不能省;rest 参数即使只有一个,小括号也不能省;只有“单条返回语句”才能省大括号和 return,多条语句不能省。
箭头函数的特点与 this
为什么需要:箭头函数不只是写法短,它和普通函数有区别。怎么想:记住四个特点——没有自己的 arguments;没有自己的 this;不能用作构造函数;不能用作生成器函数。
js
const func = (name, age, ...args) => {
console.log(name, age, args);
// console.log(arguments); // 报错:箭头函数中没有 arguments
};
func('小乐', 89, '司马姥姥', '欧阳姥姥');
new func(); // TypeError:func is not a constructorthis 是重点。箭头函数没有自己的 this,使用时沿作用域链向上找。比如对象方法里的箭头函数、事件回调、forEach 回调,如果上层作用域没有普通函数包住,this 往往就是 window。
js
const user = {
name: '小乐',
getInfo() {
const fn = () => {
console.log(this); // user
};
fn();
}
};
user.getInfo();如果把 getInfo 本身写成箭头函数,它没有自己的 this,会继续向上找。调试时不要背“情况一、情况二”,只问:这个箭头函数外层最近的非箭头函数是谁?它的 this 是什么?另外,call 和 apply 也无法给箭头函数指定 this,因为它内部根本没有自己的 this 可指定。
尾逗号与标签模板
尾逗号是 ES2017 新增的容错特性:函数声明和调用时最后多一个逗号不再报语法错误。
js
function f(a, b,) {}
f(1, 2,);标签模板是函数调用和模板字符串结合使用:
js
function func(...args) {
console.log(args);
}
const a = 100;
const b = 200;
func`hello ${a} world ${b}`;
// [['hello ', ' world ', ''], 100, 200]规则是:模板字符串按插值位置分割成若干段,组成数组作为第一个参数;每个插值依次作为后续参数。没有插值时就相当于传一个数组。易错点:末尾分隔后可能产生空字符串;它不是普通括号调用。转写稿说明它的应用场景是后续页面模板处理,目前先掌握规则即可。
一、参数默认值与 rest 参数:先解决“实参怎么拿全”
为什么需要:以前函数里想拿全部实参,常用 arguments。但它是类数组,名字也固定;ES6 提供 rest 参数,用来获取实参,尤其是“剩余实参”,并且得到的是纯数组。转写里还提到参数默认值,声明时直接写默认值即可,这里不展开。
怎么想:rest 参数的形式是“三个点 + 变量名”,变量名可自定义;它必须放在其他形参后面,不能后面再写形参;不写其他形参、只写 rest 也可以。
怎么做:
js
function fn01(...args) {
console.log(args);
}
fn01(); // []
fn01(100, 200, 250, 'hello', true); // [100,200,250,'hello',true]
function fn02(name, age, ...data) {
console.log(name, age, data, arguments);
}
fn02('高小乐', 101, '司马姥姥', '欧阳姥姥', '爱新觉罗姥姥');name 拿到“高小乐”,age 拿到 101,data 只拿后三个;arguments 仍拿全部。
易错点:rest 参数必须最后;fn02(..., [1,2]) 时数组整体只是一个元素,不会被展开;rest 得到的是纯数组,而 arguments 是类数组。
二、rest 应用:求和
为什么需要:以前用 arguments 遍历求和,现在 rest 是纯数组,可直接用数组方法。
怎么想:把实参收进 nums,再用 reduce 累加。
怎么做:
js
function sum(...nums) {
return nums.reduce(function (prev, item) {
return prev + item;
});
}
console.log(sum(10, 20, 30)); // 60
console.log(sum(10,20,30,40,50,60)); // 210易错点:reduce 回调要 return;不传初始值时,第一项会当初始值。
三、箭头函数语法:从完整写法到省略写法
为什么需要:箭头函数是一种不用 function 关键字声明函数的新语法,但声明出来的仍是函数。
怎么想:基本形式是“变量 = (参数) => { 函数体 }”。只有一个普通参数时,可省小括号;函数体只有一条 return 语句时,可同时省大括号和 return。
怎么做:
js
const fn01 = () => {};
console.log(typeof fn01); // function
const fn02 = (name, age) => {
console.log(`我叫${name},年龄是${age}`);
};
fn02('高小乐', 101);
const fn03 = num => {
console.log(num * 2 + 100);
};
fn03(3);
const fn04 = (n1, n2) => n1 * n2;
console.log(fn04(100, 200));
const fn05 = item => item * item;
console.log(fn05(5)); // 25fn04、fn05 都只有一条返回语句,所以能省成一行。注意箭头是 =>,不是等号。
易错点:无参数不能省小括号;两个参数不能省小括号;多条语句不能省大括号;不是返回语句也不能省 return。只有一个 rest 参数时,小括号也不能省。
四、箭头函数特点:没有 arguments、没有自己的 this
为什么需要:ES6 想让新函数和旧函数区分开,箭头函数因此没有 arguments,也没有自己的 this。
怎么想:要拿实参就用 rest;要用 this 就沿作用域链往上找,找到外层普通函数的 this,找不到就是全局 window。call/apply 也改不了箭头函数的 this,因为它压根没有自己的 this。
怎么做:
js
const fn = (...args) => {
console.log(args);
};
fn('小乐', 89, '司马姥姥', '欧阳姥姥');
const user = {
name: '小乐',
getInfo: () => {
console.log(this);
}
};
user.getInfo(); // window(浏览器)按钮、li 的事件回调若写成箭头函数,也按“上层作用域”找 this,转写演示中结果仍是 window。
易错点:箭头函数不能 new,会报“不是构造器”;不能做 generator 生成器函数;不要用 call/apply 去改箭头函数的 this。
五、箭头函数应用:排序、筛选、映射
为什么需要:数组回调用箭头函数更简洁,特别适合 sort、filter、map。
怎么做:
js
const users = [/* 转写中的用户对象数组 */];
users.sort((a, b) => a.age - b.age); // 按年龄从小到大,会改原数组
const older = users.filter(item => item.age >= 40); // 40岁以上,新数组
const names = users.map(item => item.name); // 提取姓名,新数组易错点:sort 默认按字符串排,必须传比较函数;sort 是修改器方法;filter 回调返回 true 才保留;map 回调 return 什么,新数组元素就是什么。
六、尾逗号与标签模板
尾逗号是 ES2017/ES8 特性:函数声明和调用最后多一个逗号不再报语法错误,提高容错性。
标签模板是“函数调用 + 模板字符串”的结合:
js
function func(...args) {
console.log(args);
}
const a = 100, b = 200;
func`hello ${a} word ${b}`;
// [ [ 'hello ', ' word ', '' ], 100, 200 ]规则是:第一个参数是数组,模板字符串被变量切开后放进数组;后面的参数依次是变量值。插入 5 个变量,就给函数传 6 个参数,数组有 6 个元素。变量可理解为分隔符,末尾变量后会分出一个空字符串。
易错点:不是 func('hello');第一个参数永远是数组;现在先看规则即可,转写说明其应用场景是后续页面模板处理。
课堂画面






代码示例
示例一:rest 参数与代替 arguments(含求和案例)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>rest 参数</title>
</head>
<body>
<script>
// 1. 不传参也不报错,args 是纯数组
function fn01(...args) {
console.log(args, Array.isArray(args)); // [] true
}
fn01();
fn01(100, 200, 250, '字符串', true);
// 2. rest 只能放最后,前面的形参先被"领走"
function fn02(name, age, ...data) {
console.log('name:', name); // 高小乐
console.log('age:', age); // 101
console.log('rest:', data); // ['司马姥姥','欧阳姥姥','爱新觉罗姥姥']
console.log('arguments:', arguments); // 全部 5 个实参(伪数组)
}
fn02('高小乐', 101, '司马姥姥', '欧阳姥姥', '爱新觉罗姥姥');
// 3. 传一个数组进去,rest 里只有一个元素(这个元素才是数组)
function fn03(name, age, ...data) {
console.log(data.length); // 1
console.log(data); // [ [1, 2, 3] ]
}
fn03('高小乐', 78, [1, 2, 3]);
// 4. 应用案例:计算所有参数的和
function sum(...nums) {
return nums.reduce((prev, item) => prev + item, 0);
}
console.log(sum(10, 20, 30)); // 60
console.log(sum(10, 20, 30, 40, 50, 60)); // 210
</script>
</body>
</html>要点解释:
...args就是 rest 参数,形式为「三个点 + 自定义变量名」,变量名由开发者自己起,而arguments的名字是系统给的、改不了。- 易错点一:rest 拿到的是纯数组,可以直接用
forEach、reduce、map;arguments是伪数组,不能直接调数组方法。 - 易错点二:rest 只收「前面形参领剩下的」实参,
arguments收全部实参——这就是fn02里data只有 3 个、arguments有 5 个的原因。 - 易错点三:rest 参数必须放在其他形参后面,写成
(...args, name)是语法错误;不写其他形参、只写 rest 是允许的。 - 求和用
reduce时别忘写初始值0,否则空调用会报错。
示例二:箭头函数的语法与各种省略写法
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>箭头函数语法</title>
</head>
<body>
<script>
// 1. 没有参数、没有函数体:也是正儿八经的函数
const fn01 = () => {};
console.log(fn01); // 输出样子不像传统函数
console.log(typeof fn01); // function
fn01(); // 可以正常调用
// 2. 有参数 + 有函数体(推荐换行缩进,可读性更好)
const fn02 = (name, age) => {
console.log(`我叫${name},今年${age}岁`);
};
fn02('高小乐', 101);
fn02('曹操', 12);
// 3. 只有一个参数:可以省略小括号
const fn03 = num => {
console.log(num * 2 + 100);
};
fn03(3);
// 4. 函数体只有一条 return 语句:大括号和 return 一起省略
const fn04 = (n1, n2) => n1 * n2;
console.log(fn04(100, 200)); // 20000
// 5. 极致精简:一个参数 + 一条 return 语句,括号全省略
const fn05 = item => item * item;
console.log(fn05(5)); // 25
</script>
</body>
</html>要点解释:
- 箭头函数是声明函数的另一种语法,用变量形式写到前面(
const更合适,函数名一般不重新赋值),搞出来的东西typeof仍然是function。 () => {}中小括号写参数,箭头后面是函数体,与function写法一一对应。- 省略小括号的条件:参数只有一个,且必须是普通形参;两个参数、或一个 rest 参数(
(...args) => {})都不能省。 - 省略大括号和
return的条件:函数体只有一条语句,且这条语句是return语句。多一条语句就不行,不是 return 语句也不行。 - 易错点:
item => item * item里那个是箭头=>,别看成等号;省略后代码可读性下降,团队协作时不要为了"装逼"过度省略。
示例三:箭头函数的四个特点与 this 应用
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>箭头函数特点</title>
</head>
<body>
<button id="btn">点我</button>
<ul id="list">
<li>1</li><li>2</li><li>3</li>
</ul>
<script>
// 特点一:没有 arguments,要用 rest 参数代替
const func = (name, age, ...args) => {
console.log(`我叫${name},年龄${age}`, args);
};
func('高小乐', 89, '司马姥姥', '欧阳姥姥');
// 特点二:不能用作构造函数
// new func('小乐', 1); // TypeError: func is not a constructor
// 特点三:没有自己的 this,使用时沿作用域向上找
const user = {
name: '小乐',
getInfo: function () {
console.log('外层普通函数 this:', this); // user 对象
const inner = () => {
console.log('箭头函数 this:', this); // 还是 user 对象
};
inner();
}
};
user.getInfo();
// 普通对象里直接写箭头函数:this 就是 window
const obj = {
getInfo: () => {
console.log('对象方法中的箭头函数 this:', this); // window
}
};
obj.getInfo();
// 事件回调里的箭头函数:this 依然是 window(普通函数才是被点的元素)
const btn = document.querySelector('#btn');
btn.addEventListener('click', () => {
console.log('事件回调 this:', this); // window
});
// 数组方法回调:箭头函数最适合当回调函数
const lis = document.querySelectorAll('#list li');
lis.forEach(item => {
item.addEventListener('click', () => {
console.log('还是 window:', this); // window
});
});
</script>
</body>
</html>要点解释:
- 没有 arguments:箭头函数里访问
arguments会直接报「不存在」,需要取实参就用 rest 参数...args,这也是 ES6 想淘汰arguments的设计意图。 - 不能 new:
new func()会报func is not a constructor,箭头函数不能实例化,也不能用作生成器函数。 - 没有自己的 this:这是最关键的一条。不要记"情况一情况二",只记一句话——箭头函数里没有
this,用到时就沿作用域链往上找,找的是声明这个箭头函数的地方,跟谁调用它没关系。 - 所以
obj.getInfo里的箭头函数找不到 this → 往上找是全局 →window;事件回调、forEach回调里的箭头函数同理都是window。 - 易错点:
user.getInfo里那个inner箭头函数之所以this是user,是因为它被包在普通函数里,普通函数的this被user调用指定了;只要往上能找到一个「非箭头函数的函数」,就用它的this。 - 易错点:因为压根没有
this,所以call/apply/bind也无法给箭头函数指定this,指定了也没用。
常见错误
rest 参数写在形参中间
- 现象:
function f(...args, x) {}一保存就 SyntaxError,整个文件不执行。 - 原因:rest 的语义是“接收剩余实参”,规范要求它必须是最后一个形参。
- 修法:改成
function f(x, ...args) {};如果目的就是拿全部实参,那就只写 rest,不要前置形参。
- 现象:
只写了 rest,却以为拿到了全部实参
- 现象:
function f(name, age, ...rest)传 5 个参数,rest只有 3 个,怀疑数据丢了。 - 原因:前面的形参已经“消费”掉对应位置的实参,rest 只收没人要的剩余部分。
- 修法:需要全量就
(...args);需要部分就核对形参顺序与调用顺序是否对齐。
- 现象:
在箭头函数里用 arguments
- 现象:箭头函数内写
arguments报 ReferenceError;若外面还包着一层普通函数,则拿到的是外层函数的 arguments,结果对不上。 - 原因:箭头函数内部不创建自己的 arguments,只能沿作用域链向外找。
- 修法:一律改用 rest 参数
(...args) => {},拿到的是真数组,还能直接forEach/reduce。
- 现象:箭头函数内写
对箭头函数用 new 或 call/apply 绑定 this
- 现象:
new Fn()报Fn is not a constructor;Fn.call(obj)执行后函数内 this 仍是 window。 - 原因:箭头函数不能作构造函数;它没有自己的 this,call/apply 没有可绑定的目标。
- 修法:需要实例化或需要动态 this,就用
function声明或 class;只是想固定外层 this,就在外层保存变量再引用。
- 现象:
把对象方法、事件回调写成箭头函数
- 现象:
obj.getInfo = () => console.log(this)输出 window;btn.addEventListener('click', () => console.log(this))也输出 window,拿不到按钮元素。 - 原因:箭头函数的 this 取决于“定义位置的上层作用域”,与“谁调用它”无关;对象字面量的大括号不是作用域,事件回调的 this 注入对它也不生效。
- 修法:这类“谁调用我,this 就是谁”的场景用普通函数;非用箭头不可时,用
e.currentTarget或外层const that = this。
- 现象:
标签模板把第一个参数当字符串用
- 现象:
tag`hello ${a}`里对第一个参数直接.toUpperCase()或参与字符串拼接,报错或结果错乱。 - 原因:标签模板的第一个参数永远是“字符串片段数组”,插值从第二个参数开始依次传入,不是拼好的字符串。
- 修法:按结构取参——i 个插值对应 i+1 个片段(含首尾空串)和 i 个额外参数;先把结构打出来再写逻辑。
- 现象:
省略括号/大括号的边界没搞清
- 现象:
...args => {}报错;a, b => a + b报错;x => { x * x }调用返回 undefined。 - 原因:省小括号只允许“恰好一个普通形参”(rest 参数即使只有一个也不能省);省大括号+return 只允许“函数体仅一条 return 语句”,写成
{}就是块语句,没有返回值。 - 修法:条件不满足就写全;返回对象字面量时包一层
() => ({ ... })。
- 现象:
sort 回调参数方向搞反
- 现象:想按年龄升序,结果排成降序,或顺序莫名其妙。
- 原因:
sort((a, b) => ...)里 a、b 是“相邻两个元素”的固定比较位,返回值正负决定是否交换,凭直觉写容易反。 - 修法:升序写
(a, b) => a.age - b.age,降序反过来;不确定时先在回调里打印 a、b 确认。
练习
this 指向分析:不运行代码,先写出下面四段各自的输出并说明依据,再上机验证。 ① 对象方法内定义的箭头函数输出 this;② 按钮 click 回调写成箭头函数输出 this;③
arr.forEach(() => console.log(this));④ 箭头函数里再套一层箭头函数输出 this。 要求:每段都用“定义位置 → 沿作用域链找最近的非箭头函数/全局”这条思路作答,而不是背结论。rest vs arguments 对照:写
function log(first, ...rest),函数体内分别打印rest、arguments、Array.isArray(rest)、Array.isArray(arguments)、rest.length、arguments.length,用log(1,2,3,4)调用并解释每一处差异。再把log改写为箭头函数版本,观察哪一行必须改掉、为什么。极限简写:把下列函数改写成最简箭头函数,改完逐个调用验证。 ①
function f(){ return 1 }②function f(n){ return n * 2 }③function f(a,b){ return a + b }④function f(x){ return { v: x } }⑤function f(...args){ return args.length }其中至少有一个不能省到“最短形态”,找出来并说明原因。标签模板规则推演:定义
function tag(parts, ...vals){ console.log(parts, vals) },调用tag`A${1}B${2}C`与tag`plain`,先写出预期输出再运行。然后补一个函数,把 parts 和 vals 还原成完整字符串,并与原模板字符串比对是否相等。链式改造:给定用户数组(含 name、age),用箭头函数写一行链式代码:按年龄降序 → 取前 3 人 → 提取姓名数组。再补一版“取 40 岁以上用户姓名”,要求全程不改动原数组,并在注释里标出每一步用的是哪个方法、回调返回值的含义。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 函数新增特性-rest参数 | 12:04 |
| 2 | 函数新增特性-rest参数应用案例 | 02:41 |
| 3 | 箭头函数的语法 | 13:18 |
| 4 | 箭头函数的特点 | 22:19 |
| 5 | 箭头函数的应用 | 08:31 |
| 6 | 标签模板 | 12:22 |
小结
- rest 的关键词是“剩余”不是“全部”,位置约束(必须在最后)和语义是同一件事的两面,记住语义就不会写错位置。
- 箭头函数不是 function 的“简写皮肤”,而是一类不同的函数对象:无 arguments、无自己的 this、不可 new、不可作 generator——四个限制背后是同一个设计意图:把普通函数和构造函数、把旧实参机制和新实参机制彻底分开。
- 判断箭头函数的 this 只有两步:看它在哪儿定义 → 沿作用域链找最近的非箭头函数或全局。它与“谁调用”“call/apply 传了什么”无关,所以不需要记忆场景清单。
- 选型口诀:需要“谁调用我,this 就是谁”(对象方法、事件回调、构造函数)→ 普通函数;需要“沿用外层上下文”(数组方法回调、定时器里的 this)→ 箭头函数。
- 语法糖的省略都有硬边界:一个普通形参才省括号,一条 return 语句才省大括号和 return。边界模糊时写全,代价只是多敲几个字符,写错则直接报错或返回 undefined。
- 标签模板先记结构后谈用途:第一参数恒为片段数组,插值按序跟在后面,i 个插值 = i+1 个片段 + i 个参数;暂时想不通用途没关系,结构错了一切都错。
- 尾逗号属于容错性增强,不改变语义;但 rest 参数后面不能跟尾逗号,这是它“必须在最后”的延伸。
- 学习方法上,先抓住设计意图(ES6 想淘汰 arguments、想区分函数角色),比收集几十条 this 规则清单可靠得多——规则清单永远列不完,意图只有一条。