主题
回顾 数组与函数(Day6 2/3)
本页对应 Day06_JavaScript,共 7 段视频,约 1 小时 19 分。
学习目标
- 能通过代码演示区分“函数名引用”与“函数名加小括号调用”,并说明不加小括号时函数体不执行。
- 能使用
return设置函数返回值,判断有返回值、无返回值及return提前结束函数的执行结果。 - 能准确区分形参与实参,说明调用时实参按顺序为形参赋值,并分析实参数量多于或少于形参数量时的结果。
- 能为形参设置默认值,分别使用 ES5 判断赋值和 ES6 默认参数语法,并说明带默认值的形参应放在参数列表后部的原因。
- 能使用
arguments获取调用时传入的全部实参,利用其length和索引遍历参数,并实现可变参数数量函数。 - 能判断变量是全局变量还是局部变量,验证全局变量在函数内外及不同脚本中的可访问范围。
知识点
本节笔记代码(保留)
text
1. 数组
1.1 概念: 元素、索引
1.2 创建数组(声明数组)
1.3 读写数组中的元素
1.4 稀疏数组
1.5 遍历数组
1.6 数组添加元素
1.7 数组删除元素
1.8 多维数组
1.9 字符串的数组特性 / 类数组、伪数组
2. 函数
2.1 函数是js中一种数据类型
2.2 函数的组成
函数名、函数体、参数、返回值
2.3 创建函数
① function 关键字方式
② 表达式方式(直接量方式)
③ Function 函数 对应 Array、Number、String、Boolean
④ Function 构造函数数组回顾:先抓住“元素、索引、创建与读写”
为什么需要:数组用来成批保存数据,后续函数参数、类数组、arguments 都要用到“索引 + length”的结构。怎么想:先分清元素和索引,索引从 0 开始;创建数组可用直接量,也可调用 Array()。转写稿里反复强调:Array 本身是变量,值是函数;Array() 才调用并返回新数组,Array 不加括号得到的是函数本身。怎么做:读写用 arr[索引];遍历用循环;增删可用 push、slice 等。易错点:Array 与 Array() 不同;稀疏数组有长度但部分位置没有值;类数组/伪数组“像数组”,但类型不一定是数组,后面 arguments 就是典型。
函数调用:函数名后加小括号才执行
为什么需要:函数体只有被调用才执行,否则只是“把函数当数据放着”。怎么想:看到函数名,先问“后面有没有小括号”。怎么做:
js
function fn() {
console.log('我是fn');
}
fn; // 不调用,函数体不执行
fn(); // 调用,输出“我是fn”转写稿调试过:res1 = Array() 得到空数组;res2 = Array 得到 Array 函数本身;res3 = Array(200, 300, 400) 才得到含元素的数组。易错点:把函数名赋给变量,不等于调用;fn06 里写了 console.log(100),但只写 res1 = fn06 没有输出,res1 是函数本身;写 res2 = fn06() 才会执行函数体,而它的返回值另算。
返回值:输出是输出,返回是返回
为什么需要:函数常常要“算出结果交给调用者”,而不是自己直接输出。怎么想:return 右边的表达式的值,会成为函数调用表达式的值。怎么做:
js
function f01() {
return 10 * 5;
}
console.log(f01()); // 50转写稿还演示了 return 可结束函数:执行到 return 后,后面的语句不再执行。没有 return、或 return; 右边为空,都表示没有返回值;若强行取调用表达式的值,得到 undefined。易错点:console.log 是输出,不是返回;函数里输出 100,不代表返回 100。fn06() 的返回值仍是 undefined。
形参与实参:按顺序赋值
为什么需要:函数要接收外部数据。怎么想:形参是创建函数时写的“没有赋值的变量”;实参是调用时传入的数据,按顺序给形参赋值。怎么做:
js
function func(name, age) {
console.log(name, age);
}
func('高小乐', 78);数量不一致时:实参多于形参,多出的没有对应形参,不起作用;实参少于形参,后面的形参没有被赋值,使用时得到 undefined。默认值要放在后面:ES5 可在函数体判断 if (address === undefined) address = '上海';,ES6 可直接写 function f(name, age = 10) {}。易错点:不能靠“空着第二个参数”跳过它,空字符串也是值;默认值放前面会失去意义。
arguments:函数内部收集所有实参
为什么需要:有些函数不规定参数个数,比如 push、Array、slice 的后续参数。怎么想:arguments 是系统创建、只在函数内部可用的变量,值是类数组,包含调用时传入的所有实参。怎么做:
js
function sum() {
let res = 0;
for (let i = 0; i < arguments.length; i++) {
res += arguments[i];
}
return res;
}
console.log(sum(1, 2, 3, 4, 5)); // 15易错点:arguments 不是真数组,但有索引和 length;函数外使用会报未定义;它适合“可变参数数量”的函数。
全局变量与作用域提醒
为什么需要:变量不是哪里都能用。怎么想:在函数外创建的是全局变量,函数内创建的是局部变量;for、if 是语句不是函数,所以其中用 var 创建的变量仍按全局处理。函数名本身也是变量,也有作用域。易错点:转写稿里的“that/va”应理解为 let/var 的对比提醒;本节作用域规则按 var 讲,let 与 var 不一样,后续再学。
函数调用:函数名加小括号才是执行
为什么需要:初学者最容易把“函数名”和“函数调用”混为一谈。函数体写了不代表会执行,必须通过调用触发。
怎么想:函数名本身是一个变量,它的值是函数;函数名后面加上小括号,才是“调用这个函数”,函数体里的语句才会执行。只写函数名,相当于把一个函数值放在那里,什么也不会发生。
怎么做:
js
function fn() {
console.log("我是 fn");
}
fn; // 只是取函数本身,不执行函数体
fn(); // 调用,输出“我是 fn”易错点:不要看到 fn 就以为执行了。判断标准很简单:后面有没有小括号。没有小括号,函数体不执行。
返回值:输出是输出,返回是返回
为什么需要:函数经常要“算出一个结果”交给调用者继续使用。直接 console.log 只是输出,不等于把结果返回给外部。
怎么想:return 用来设置返回值;函数调用表达式的值,就是这次调用得到的返回值。业务上需要计算结果时,优先把结果返回,至于要不要输出,由使用函数的人决定。
怎么做:
js
function f01() {
return 10 * 5;
}
console.log(f01()); // 50return 右边写表达式;表达式可以是直接量、变量或运算式。return 还有第二个作用:结束当前函数,return 后面的代码不再执行。
易错点:函数里写了 console.log(100),并不代表函数有返回值。没有 return,或 return 右边为空,函数就没有返回值;如果强行取调用表达式的值,浏览器会给 undefined。
形参与实参:调用时按顺序赋值
为什么需要:函数需要接收外部数据,才能根据不同的输入做不同的事。
怎么想:创建函数时写的参数叫形参,形参本质上是“没有赋值的变量”,只能在函数内部使用;调用函数时传入的数据叫实参,实参用于按顺序给形参赋值。
怎么做:
js
function fun(name, age) {
console.log("我叫" + name + ",今年" + age + "岁");
}
fun("高晓乐", 78); // 实参按顺序赋给 name、age实参可以是变量、直接量或表达式,只要有值即可。形参不能写成直接量,因为它需要接收值。
易错点:形参只在函数内部有效,函数外部不能使用;函数外部即使有同名变量,也和函数内部的形参不是同一个变量。
数量不匹配与默认值:多给白给,少给得 undefined
为什么需要:实际开发中可能出现实参数量与形参数量不一致,需要知道 JS 的处理规则。
怎么想:实参按顺序赋值。实参多了,多出的没有对应形参,等于“白给”;实参少了,后面的形参没有被赋值,使用时得到 undefined。
怎么做:
js
function f(name, age, address) {
console.log(name, age, address);
}
f("孙悟空", 800); // address 为 undefined默认值可以用 ES5 方式判断,也可以用 ES6 方式直接写:
js
// ES5:函数体内部判断
function f1(name, age, address) {
if (address === undefined) {
address = "上海";
}
console.log(name, age, address);
}
// ES6:形参默认值
function f2(name, age, address = "上海") {
console.log(name, age, address);
}易错点:有默认值的形参应放在后面。因为实参按顺序赋值,如果默认值参数放前面,后面的实参仍然会按位置覆盖它,默认值就失去意义。
arguments:拿到全部实参
为什么需要:有些函数不规定参数个数,比如求和、push、slice 等,需要接收“调用时传了多少就用多少”。
怎么想:arguments 是系统在函数内部创建的变量,值是一个类数组,包含调用函数时传入的全部实参。它有索引和 length,可以遍历,但它不是真正的数组。
怎么做:
js
function sum() {
let result = 0;
for (let i = 0; i < arguments.length; i++) {
result += arguments[i];
}
return result;
}
console.log(sum(1, 2, 3, 4, 5)); // 15易错点:arguments 只能在函数内部使用,函数外部会报 arguments is not defined;不要把它当成数组,它只是类数组。形参只能接收固定数量,arguments 能拿到全部实参。
作用域:函数内部才有局部变量
为什么需要:变量不是哪里都能用,必须知道它的可作用范围,避免在外部误用函数内部变量。
怎么想:在函数外创建的变量是全局变量,哪里都能用;在函数内创建的变量是局部变量,只在当前函数内可用。形参和 arguments 都属于函数内部使用的变量。
怎么做:
js
var username = "高晓乐"; // 全局变量
function fn() {
var age = 18; // 局部变量
console.log(username); // 可以访问全局变量
console.log(age);
}
fn();
console.log(username); // 可以
// console.log(age); // 报错:age 未定义易错点:for、if 等是语句结构,不是函数;在它们里面用 var 创建的变量,按本节 var 规则仍属于全局变量。不要把 var 和 let 的作用域规则混为一谈,本节讲的是 var 的作用域规则。
课堂画面






代码示例
示例一:函数的调用与返回值(01.html)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>函数的调用与返回值</title>
</head>
<body>
<script>
// 1. 有返回值的函数:return 右边写表达式,表达式的值就是返回值
function fn01() {
return 10 * 5;
}
console.log(fn01()); // 50
// 2. 返回值可以继续作为另一个函数的返回值
function fn02() {
return fn01(); // fn01() 的值是 50,又作为 fn02 的返回值
}
console.log(fn02()); // 50
// 3. 没有 return / return 右边是空的 => 没有返回值
function fn03() {
var sum = 100 * 2 + 56;
return;
}
function fn04() {}
console.log(fn03()); // undefined
console.log(fn04()); // undefined
// 4. return 还能结束函数
function fn05() {
console.log('fn05 start');
return; // 函数在这里就结束了
console.log('fn05 end'); // 永远执行不到
}
fn05();
// 5. 输出是输出,返回是返回
function fn06() {
console.log(100); // 这是输出,不是返回值
}
var res1 = fn06; // 不加小括号:没有调用,res1 得到的是函数本身
console.log(res1); // ƒ fn06() { console.log(100) }
var res2 = fn06(); // 加了小括号:调用,res2 得到的是返回值
console.log(res2); // undefined
console.log(fn06()); // 先输出 100(函数体里的),再输出 undefined(返回值)
</script>
</body>
</html>- 关键行:
fn01()才是调用,fn01只是取到这个变量(函数本身)的值。函数名后必须跟小括号,函数体的语句才会执行。 - 关键行:
return 10 * 5;——return 右边是表达式,表达式的值就是函数调用表达式的值,所以fn01()的值是 50。 - 关键行:
var res1 = fn06;与var res2 = fn06();的区别,是整个“调用 vs 赋值”最容易混的地方:不加括号把“函数自己”交出去,加括号把“返回值”交出去。 - 易错点:
return;(右边为空)和压根不写 return,都表示“没有返回值”,并不是返回 0。没有返回值时若非要取函数调用表达式的值,浏览器自动给undefined。 - 易错点:看到函数要先默念“输出是输出,返回是返回”。
console.log(100)只是输出,函数并没有返回值;console.log(fn06())会输出两个东西:先是 100,后是 undefined。 - 易错点:return 后面的语句不会再执行,所以“写在 return 后面的代码”等于废代码;但如果 return 写在 if 分支里,不一定每次都执行到。
示例二:形参、实参与形参默认值(02.html)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>形参和实参</title>
</head>
<body>
<script>
// 形参:创建函数时写的“没有赋值的变量”;实参:调用函数时传的数据
// ES5 方式设置默认值:在函数体里判断 undefined 再赋值
function func(name, age, address) {
if (address === undefined) {
address = '上海';
}
console.log('我叫' + name + ',年龄' + age + ',住在' + address);
}
func('高小乐', 78, '山洞'); // 三个都传:默认值不生效
func('孙悟空', 897); // 少传 address:走默认值
// 实参数量与形参数量不一致,也不会报错
function func2(name1, name2) {
console.log(name1 + '和' + name2);
}
func2('高小乐', '曹操'); // 正常
func2('贾宝玉', '高小乐', '林黛玉'); // 多出来的实参白给,被忽略
func2('孙悟空'); // 第二个形参没被赋值 => undefined
// ES6 方式设置默认值:直接在形参后面写等号
function func3(name, age = 10, address = '上海') {
console.log('我叫' + name + ',年龄' + age + ',住在' + address);
}
func3('曹操'); // 两个默认值都生效
func3('孙悟空', 800); // age 用实参,address 用默认值
func3('高小乐', 78, '山洞'); // 默认值全不生效
</script>
</body>
</html>- 关键行:
if (address === undefined) { address = '上海'; }——ES5 没有默认值语法,只能靠判断“这个形参是不是 undefined”来临时补值。 - 关键行:
function func3(name, age = 10, address = '上海')——ES6 直接在形参位置写默认值,函数体里啥也不用干。 - 易错点:实参给形参赋值是严格按顺序的。
func2('贾宝玉','高小乐','林黛玉')里,第三个实参根本没有对应形参,白给;func2('孙悟空')只给第一个形参赋值,第二个形参存在但没值,用的时候自动是undefined。 - 易错点:带默认值的形参必须放在形参列表后面。因为顺序赋值,前面的位置没法跳过;把带默认值的放前面,调用时想“跳过”它只能传个空字符串
'',而空字符串是数据,照样会赋上值,默认值就失效了。 - 易错点:形参本质就是变量,但只能在函数内部使用;函数外面即使有个同名的
name(系统自带的),也和函数里的形参毫无关系。 - 易错点:设了默认值的形参也叫“可选参数”——调用时这个实参可给可不给,不给就用默认值。
示例三:arguments 与变量作用域(03.html)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>arguments 与作用域</title>
</head>
<body>
<script>
// arguments:系统在函数内部创建的变量,值是调用时传入的所有实参组成的类数组
function sum() {
var res = 0; // 记录和
for (var i = 0; i < arguments.length; i++) {
res += arguments[i];
}
return res;
}
console.log(sum(1, 2, 3, 4, 5, 6, 7)); // 28
console.log(sum(1, 5)); // 6
// arguments 只能在函数里用,外面用会报 arguments is not defined
// console.log(arguments);
// 全局变量:函数外面创建的变量,哪里都能用
var username = '高小乐';
function fn() {
console.log(username); // 函数内部也能用
}
fn();
console.log(username); // 函数外部也能用
// for、if 是语法结构,不是函数,里面的 var 也是全局变量
for (var i = 0; i <= 10; i++) {
// 空循环
}
console.log(i); // 11
var age;
for (var j = 0; j <= 10; j++) {
age = j + 10;
}
console.log(age); // 20(最后一次循环时 j 是 10)
if (true) {
var address = '上海';
}
console.log(address); // 上海,if 里创建的也是全局变量
// 函数名本身也是一个变量(全局的)
console.log(fn); // 输出的是函数本身
</script>
</body>
</html>- 关键行:
arguments.length和arguments[i]——arguments 是类数组(伪数组),有索引、有 length,可以像数组一样遍历,但它的类型不是 Array。 - 关键行:
res += arguments[i];——写形参只能接到固定个数的实参,多的实参会被丢掉;用 arguments 则无论传多少个都能全部拿到,这就是实现“可变参数数量函数”的方式(push、slice、Array 内部也是这么干的)。 - 易错点:arguments 只能在函数内部使用,在函数外面写
console.log(arguments)会报arguments is not defined;并且不同函数里的 arguments 值各不相同。 - 易错点:判断全局/局部只看一点——有没有写在函数里。for、if、switch 都是语句不是函数,所以
for (var i = ...)里的 i、if里的 address 都是全局变量,出了大括号照样能用。 - 易错点:
console.log(age)得到 20 而不是 11,因为循环体最后一次执行时 j 是 10,执行完循环体之后才 j++ 变成 11。 - 易错点:本节的作用域规则是基于
var讲的,不能直接套到let/const上,它们的作用域判定规则不同,会得出不一样的结果。
常见错误
- 现象:写了
fn却以为函数已执行 → 原因:函数名不加小括号只是取函数值,不是调用 → 修法:确认执行必须写fn();需要传参时写fn(实参)。 - 现象:函数里
console.log了结果,却以为调用表达式有返回值 → 原因:把“输出”当成“返回” → 修法:需要外部拿到结果时用return 表达式,调用处用变量接收或直接参与运算。 - 现象:
return后面的代码“莫名不执行” → 原因:return会结束当前函数 → 修法:把必须执行的语句放到return前;分支中检查return是否提前命中。 - 现象:给形参设默认值后,传参结果和预期错位 → 原因:实参按顺序赋给形参,默认值参数放在前面会破坏可选性 → 修法:把带默认值的形参统一放到参数列表尾部。
- 现象:实参少于形参时,函数内部拿到
undefined却以为会报错 → 原因:JS 不因参数数量不匹配直接报错,未赋值的形参就是无值变量 → 修法:调用时补齐实参,或在函数内/参数列表中设置默认值。 - 现象:对
arguments调push、map等数组方法报错 → 原因:arguments是类数组,不是真数组 → 修法:按索引和length遍历;确需数组方法时先转换为数组。 - 现象:在函数外访问形参或
arguments报未定义 → 原因:它们只在函数内部有效 → 修法:需要外部使用的数据通过return返回,或提升为外部变量再赋值。 - 现象:用
let验证本课var的作用域结论,结果与课堂示例不一致 → 原因:let与var的作用域规则不同 → 修法:复现本课作用域案例时先用var;不要把var结论直接套到let上。
练习
- 阅读代码并写出输出结果,说明每次输出的是“函数本身”“返回值”还是“函数内部输出”:js
function fn() { console.log('A'); } console.log(fn); console.log(fn()); - 改错:要求函数返回两数之和,但当前代码只输出不返回。请修改,使
const r = add(2, 3);后r为5。jsfunction add(a, b) { console.log(a + b); } - 编写
sum():不固定参数个数,使用arguments计算所有实参之和并返回;分别测试传 2 个、5 个、0 个参数的情况。 - 编写
createUser(name, age, address),要求age默认18、address默认'上海';分别用 1 个、2 个、3 个实参调用,并解释为什么默认值参数要放后面。 - 判断下列变量分别在哪些位置可访问,并说明原因:js
var a = 1; function outer() { var b = 2; if (true) { var c = 3; } for (var i = 0; i < 1; i++) {} console.log(a, b, c, i); } console.log(a);
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 函数的调用 | 09:14 |
| 2 | 函数的返回值 | 17:34 |
| 3 | 形参和实参 | 09:19 |
| 4 | 形参和实参的数量问题 | 05:01 |
| 5 | 形参的默认值 | 12:48 |
| 6 | arguments | 12:53 |
| 7 | 全局变量 | 12:52 |
小结
- 是否执行看小括号:
fn是函数值,fn()才是调用。 - 是否拿到结果看
return:输出不等于返回,调用表达式的值来自返回值。 return既设置返回值,也会立即结束当前函数。- 形参是函数内部未赋值的变量;实参按顺序给形参赋值。
- 参数数量不匹配通常不报错:多出的实参不发挥作用,缺少的形参得到
undefined。 - 默认值参数应放在参数列表尾部,否则按顺序传参时默认值容易失去意义。
arguments是函数内可用的类数组,适合实现可变参数数量的函数,但不是真数组。- 作用域以函数为边界;本课
var示例中,for、if等语句块不会单独产生局部作用域。