主题
函数(Day5 3/3)
本页对应 Day05_JavaScript,共 7 段视频,约 1 小时 15 分。
学习目标
- 能够识别多维数组,并通过多层索引读取和修改嵌套数组中的元素。
- 能够利用字符串的
length属性和索引读取字符、遍历字符串,并说明字符串只具备部分数组特性,不能用索引修改字符或修改length。 - 能够说明函数的概念与作用,区分系统函数与对象方法,并验证函数是对象类型数据且
typeof返回function。 - 能够指出函数的组成:函数名、函数体、参数、返回值,区分“返回”和“输出”,并解释函数调用表达式的值。
- 能够使用
function关键字方式和表达式方式创建含参数、含返回值的函数并正确调用,同时说明构造函数方式创建函数的语法及不推荐使用的原因。
知识点
函数是什么:有特定功能的代码块
为什么需要:写代码时会反复做同一件事,比如取整、弹框、转类型。如果每次都重写一遍,代码会冗余且难维护。函数就是把“完成某件事的代码”打包起来,起个名字,以后通过名字调用。
怎么想:先别把函数神秘化。系统已经给了很多函数:Number、String、Boolean、parseInt、parseFloat、isNaN、isFinite、alert、prompt、console.log、document.write,数组的 push、pop、shift、unshift 等本质上也是函数。它们都有特定功能,只是内部代码我们看不到。
怎么做:记住定义:函数是具有特定功能的代码块。调用函数,就是让函数体里的代码执行。调用一次执行一次,调用两次执行两次。
易错点:console.log 是输出,不是“返回值”;看到函数名不等于调用,必须加小括号才算调用。
函数名就是变量:JS 的特殊设定
为什么需要:理解这一点,才能理解后面“函数可以被赋值、可以被覆盖”的现象。
怎么想:在 JS 中,函数是一种数据类型,属于对象类型;用 typeof 判断会得到 "function"。函数名本质上是一个变量,只不过这个变量的值是函数类型的数据。系统函数也是系统创建好的变量,值也是函数。
怎么做:
js
console.log(typeof Number); // function
console.log(typeof String); // function
console.log(typeof parseInt); // function也可以验证“函数名是变量”:
js
function fun01() {
console.log('hello,我是fun01');
}
fun01(); // 调用,输出
fun01 = 100;
fun01(); // 报错:fun01 is not a function易错点:不要把其他语言里“函数名只是函数名”的经验直接搬过来。JS 里函数名是变量,值被改成数字后就不能再调用。类似地,把 Number 改成 100 再写 Number(),也会报“不是一个函数”。
函数体、参数与返回值:一台机器的输入与产出
为什么需要:函数要能接收不同数据、产出计算结果,否则只能写死,复用性差。
怎么想:把函数比作机器:参数是原材料,函数体是加工过程,return 是产出。输出则是另外一件事,需要自己写 console.log、alert 等。
怎么做:函数由函数名、函数体、参数、返回值组成。创建时小括号里写形参,调用时小括号里写实参。
js
function fun02(name, age) {
console.log('我是' + name + ',今年' + age + '岁');
}
fun02('高启强', 48);
function fun03(a, b) {
return a * b;
}
console.log(fun03(100, 200)); // 20000
var r = fun03(10, 67);
console.log(r);fun03(100, 200) 是函数调用表达式,它整体有值,这个值就是返回值。
易错点:return 不是输出。只写 return a * b; 不会在页面或控制台显示;要显示必须自己 console.log(fun03(100, 200))。另外,不写 return 的函数,调用表达式的值不是“没有输出”,而是返回值通常为 undefined。
第一种创建方式:function 关键字
为什么需要:这是最常见、最接近其他编程语言的形式,适合声明一个有名字的函数。
怎么想:关键字 function + 函数名 + 小括号参数列表 + 大括号函数体。
怎么做:
js
function fun01() {
console.log('hello,我是fun01');
}
fun01();
function fun02(name, age) {
console.log('我是' + name + ',今年' + age + '岁');
}
fun02('高启强', 48);
function fun03(a, b) {
return a * b;
}
console.log(fun03(100, 200));易错点:只声明不调用,函数体不会执行;函数体里可以写多条语句、循环、分支,不限制条数。函数名命名规范与变量一致:字母、数字、下划线、$,不能以数字开头,不能是关键字。
第二种创建方式:函数表达式
为什么需要:它更能体现“函数是数据、函数名是变量”。可以把一个函数直接量赋给变量。
怎么想:对比数组直接量:var arr = [1,2,3];;函数直接量就是 function(){...},再赋给变量。
怎么做:
js
var fun01 = function () {
console.log('hello,我是fun01');
};
fun01();
var fun02 = function (name, age) {
console.log('我是' + name + ',今年' + age + '岁');
};
fun02('高启强', 48);
var fun03 = function (a, b) {
return a * b;
};
console.log(fun03(100, 200));易错点:这种写法等号右边是函数直接量,函数名在等号左边;调用方式与第一种没有区别。表达式末尾建议写分号。
第三、四种方式:Function 构造函数(了解即可)
为什么需要:它和数组的构造函数方式对应,帮助理解“函数也是对象/数据类型”,但实际开发很少用。
怎么想:Array 能创建数组,Number 能创建数字,Function 能创建函数。参数和函数体都要写成字符串。
怎么做:
js
var fun01 = new Function('console.log("hello,我是fun01")');
fun01();
var fun02 = new Function('name', 'console.log("我叫" + name)');
fun02('曹操');new Function(...) 和 Function(...) 结果基本一样,只是形式不同。
易错点:参数、函数体都写在字符串里,函数体多时很难读,不能像普通代码那样自由换行;不推荐使用。了解即可,不要把它当成常规写法。
易错点收束
函数名是变量,值被覆盖后不能再调用;不加小括号不叫调用;return 是返回,不是输出;参数是向函数内部传数据,实参在调用时写;函数调用表达式整体有值,值就是返回值。把这几条记住,再写函数时先问自己:我要不要传参?要不要返回?要不要输出?
作业清单(原笔记)
1. 循环强化练习 6 7 8 9 10
2. 翻转字符串
尝试创建一个能翻转字符串的函数
3. 选做题
var nums = [34, 78, 12, 104, 79, 50, 36, 29, 19.9, 250, 120];
实现数组排序 从小到大先明确这次作业在练什么
为什么需要:这次作业不是孤立的三道题,而是把前面学过的循环、数组读写、字符串的“数组特性”、函数创建与调用串起来。转写稿里反复强调:字符串有 length,也能用索引读取某个字符,但它只具备“部分数组特性”,不能用索引修改单个字符;数组是集合,可以改其中某个元素。这个差别直接决定翻转字符串和数组排序的写法。
怎么想:先把作业分成三类能力:循环控制、字符串遍历、数组比较交换。每道题都先写“输入是什么、输出是什么、用哪个循环、在哪里返回或打印”。
怎么做:按下面三块逐项完成,不要一上来堆代码。易错点:函数名是变量,调用要加小括号;return 是返回,不是输出;字符串不能原地改字符。
循环强化练习 6 7 8 9 10
为什么需要:循环题练的是三要素——初始值、结束条件、更新方式。后面翻转字符串和排序都依赖循环,如果这里索引边界不清,后面一定出错。
怎么想:每道题先写三行草稿:i 从几开始?条件写到 i < n 还是 i <= n?每次 i++ 还是 i--?如果是累加,先想累加变量初始值应该是 0;如果是找最大值,初始值要考虑数组第一个元素或足够小的值。
怎么做:按布置的 6、7、8、9、10 顺序完成,不要跳题。每题写完后在循环里临时打印 console.log(i),确认索引是否从正确位置开始、是否在正确位置结束,再删掉调试语句。易错点:i <= arr.length 会多取一位,通常应写 i < arr.length;倒序循环容易漏掉 i >= 0;累加变量写在循环里会被反复重置。
翻转字符串:用字符串的数组特性,但不要原地改
为什么需要:转写稿里已经验证过:字符串可以用 msg[0] 读取字符,也可以用 msg.length 得到字符个数,所以可以像遍历数组一样遍历字符串。但 msg[0] = 'h' 不会报错,也不会真正改掉原字符串;msg.length -= 5 也不管用。因此翻转字符串不能靠“交换原字符串里的字符”,而要新建一个字符串,把字符从后往前拼进去。
怎么想:假设字符串是 hello 高小乐666。,最后一个字符索引是 str.length - 1。循环从最后一位开始,每次取 str[i],拼到结果字符串后面,直到 i >= 0。函数只负责返回结果,输出交给外面。
怎么做:
js
function reverseStr(str) {
var result = '';
for (var i = str.length - 1; i >= 0; i--) {
result += str[i];
}
return result;
}
console.log(reverseStr('hello 高小乐666。'));调试时可以在循环里打印 i 和 str[i],确认取字符顺序是从句号、6、6、6、乐、小、高、空格、o、l、l、e、h。易错点:i > 0 会漏掉第一个字符;把 return result 写进循环会只返回一次;以为 str[i] = ... 能修改原字符串;忘记函数调用要写 reverseStr('...'),只写函数名不会执行。
选做题:数组从小到大排序
为什么需要:排序题练的是双重循环、数组索引读写和交换变量。数组和字符串不同,数组可以通过 nums[i] = nums[j] 修改元素,所以可以真正交换位置。
怎么想:用最直观的双重循环比较交换:外层 i 表示当前要确定的位置,内层 j 从 i + 1 开始找后面更小的数;如果 nums[i] > nums[j],就交换。这样每轮把较小的数换到前面。
怎么做:
js
var nums = [34, 78, 12, 104, 79, 50, 36, 29, 19.9, 250, 120];
for (var i = 0; i < nums.length - 1; i++) {
for (var j = i + 1; j < nums.length; j++) {
if (nums[i] > nums[j]) {
var temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
}
console.log(nums);易错点:内层 j 写成 j = 0 会重复比较;交换时不用临时变量会把一个值覆盖掉;外层条件写成 i < nums.length 虽然不一定马上错,但会多跑一轮;排序函数如果要封装,记得把数组作为参数传入,并 return 排序后的数组,再由外面 console.log 输出。
交作业前自检
为什么需要:转写稿里特别提醒“返回不是输出”。翻转字符串和排序如果封装成函数,函数内部用 return 交出结果,外面要用 console.log 才能看到。怎么做:逐项检查——循环题是否打印了预期结果;reverseStr 是否返回新字符串;排序后 nums 是否从小到大;函数调用是否带小括号;是否误把字符串当数组原地修改。易错点:只写函数声明不调用;把 return 当 console.log;排序后没有输出就以为没结果。
课堂画面






代码示例
1. 多维数组 & 字符串的数组特性
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>多维数组与字符串的数组特性</title>
</head>
<body>
<script>
/* ===== 一、多维数组:数组的元素还是数组 ===== */
var cityList = [
['南京', '苏州'],
['杭州', '嘉兴', '宁波'],
['济南', '青岛', '烟台', '潍坊']
];
console.log(cityList[2][3]); // 潍坊:先取大数组的第 3 个元素,再取它的第 4 个元素
console.log(cityList[1][1]); // 嘉兴
console.log(cityList.length); // 3,只看最外层
/* ===== 二、字符串的数组特性 ===== */
var msg = 'hello 高小乐666。';
console.log(msg.length); // 13:空格、标点、中文都算一个字符
console.log(msg[0]); // h
console.log(msg[6]); // 高(从 0 开始数)
console.log(msg[msg.length - 1]);// 。取最后一个字符
// 可以当数组一样遍历
for (var i = 0; i < msg.length; i++) {
console.log(msg[i]);
}
/* ===== 三、只是“部分”数组特性:只能读,不能改 ===== */
msg[0] = 'H'; // 不报错,但不生效
console.log(msg); // hello 高小乐666。
msg.length = 5; // 不报错,也不生效
console.log(msg); // hello 高小乐666。
// 字符串是“类数组/伪数组”,push、pop 等数组方法一律不能用
// msg.push('x'); // 报错:msg.push is not a function
</script>
</body>
</html>关键行与易错点
cityList[2][3]:多维数组取值就是“多写几层中括号”,先定位外层元素,再定位里层元素,别把两层索引写成一层。msg.length得到的是字符个数:中文、空格、标点、数字都算 1 个字符,不是字节数。msg[msg.length - 1]是取最后一个字符的固定套路,因为索引从 0 开始。- 易错点:
msg[0] = 'H'、msg.length = 5都不报错但也不生效——字符串是原始类型,是一个整体,只能读取其中某个字符,不能通过索引修改单个字符,也不能改 length。 - 易错点:字符串只有
length和“索引读取”这两条像数组,push / pop / shift / unshift这些数组方法它一个都没有。
2. function 关键字方式创建函数(函数名就是变量)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>函数的组成与 function 关键字创建函数</title>
</head>
<body>
<script>
/* ===== 函数是数据类型:typeof 得到 function ===== */
console.log(typeof Number); // function
console.log(typeof String); // function
console.log(typeof Array); // function
// 注意:函数名后面没写小括号,就不是调用,只是打印这个变量里的值
/* ===== 1. 无参函数 ===== */
function fn01() {
console.log('hello,我是fn01');
}
fn01(); // 调用一次,函数体执行一次
fn01(); // 调用两次,函数体执行两次
/* ===== 2. 有参函数:小括号里的 name、age 是形参,本质是两个变量 ===== */
function fn02(name, age) {
console.log('我是' + name + ',今年' + age + '岁');
}
fn02('高小乐', 78); // 实参依次赋值给形参
fn02('曹操', 17); // 每次调用可以传不同的值
/* ===== 3. 有返回值函数:return 是返回,不是输出 ===== */
function fn03(num01, num02) {
return num01 * num02;
}
fn03(100, 200); // 没有任何输出!返回值没人接收
console.log(fn03(100, 200)); // 20000,自己写输出才有输出
var res = fn03(10, 67); // 函数调用表达式的值就是返回值
console.log(res); // 670
/* ===== 4. 函数名就是变量,可以被重新赋值 ===== */
// fn01 = 100;
// fn01(); // 报错:fn01 is not a function
</script>
</body>
</html>关键行与易错点
typeof Number得到function而不是object,这是 JS 的特殊之处:函数是一种数据类型,typeof能判断出function。- 函数名后面不加小括号不叫调用,只是使用这个变量本身;
fn01()才叫函数调用表达式。 - 形参就是函数体内部可以直接使用的两个变量,调用时实参按顺序依次赋值给形参;没有传参就是
undefined。 - 最易错点:返回 ≠ 输出。
return只是把计算结果交出去,fn03(100, 200)单独写一行不会有任何打印;想看到结果必须自己写console.log或赋值给变量再输出。 - 函数名就是一个变量,把它改成
100之后再调用就会报fn01 is not a function——调用的不是名字,而是变量里存的那个函数。
3. 表达式方式创建函数(含 Function 构造函数,了解即可)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>表达式方式创建函数</title>
</head>
<body>
<script>
/* ===== 表达式方式:把一个“函数直接量”赋值给变量 ===== */
var fn01 = function () {
console.log('hello,我是fn01');
};
fn01();
fn01();
var fn02 = function (name, age) {
console.log('我是' + name + ',今年' + age + '岁');
};
fn02('高启强', 48);
var fn03 = function (num01, num02) {
return num01 * num02;
};
console.log(fn03(100, 56)); // 5600
/* ===== 和数字直接量、数组直接量对比,结构完全一样 ===== */
var n = 100; // 数字直接量给变量
var arr = [10, 20, 30]; // 数组直接量给变量
var fn = function () { console.log('函数直接量'); }; // 函数直接量给变量
console.log(typeof n, typeof arr, typeof fn); // number object function
/* ===== 第三、四种:Function 构造函数,了解即可,不要用 ===== */
var fn04 = new Function('console.log("hello,我是fn04")');
fn04();
var fn05 = new Function('name', 'console.log("我叫" + name)');
fn05('高小乐');
</script>
</body>
</html>关键行与易错点
var fn01 = function () {...};这条语句和var n = 100;、var arr = [10,20,30];结构完全相同,只是把“函数直接量”赋给了变量,这最能说明函数名就是变量名。- 创建方式变了,调用方式不变:仍然是函数名加小括号,仍然调用一次执行一次。
- 易错点:表达式方式的函数整体是一个赋值语句,结尾的
};分号别漏;函数体里的语句写在{}内部,顺序和关键字方式一致(函数名、参数列表、函数体、返回值)。 - 易错点:
new Function('参数', '函数体语句')里所有内容都必须写成字符串,换行、缩进都不方便,语法极易写错,实际开发中不用,只作为“函数也是对象类型、也有构造函数”的印证,和数组的new Array()是一个道理。 - 无论哪种方式创建,
return的作用都只是返回结果,不是输出。
常见错误
- 现象:
msg[0] = 'H'不报错,但msg没变。→ 原因:字符串是原始类型,只具备部分类数组特性,索引只读。→ 修法:不要改原字符,用拼接、切片或转数组生成新字符串。 - 现象:调用有
return的函数后页面/控制台没输出。→ 原因:把“返回”当成“输出”,return只把值交给调用表达式。→ 修法:用console.log(fn())输出,或const res = fn()接收后再处理。 - 现象:多维数组取值得到
undefined。→ 原因:维度层级或索引写错,且索引从0开始。→ 修法:逐层打印中间值,按arr[外层][内层]拆开取。 - 现象:函数表达式在赋值前调用报
fn is not a function/undefined is not a function。→ 原因:表达式方式不会像函数声明那样整体提升。→ 修法:先定义再调用;需要提前调用就用function fn(){}声明。 - 现象:
fn = 100; fn()报fn is not a function。→ 原因:函数名本质是变量,值被覆盖后不再是函数。→ 修法:不要给函数名重新赋值;需要别名时另起变量保存。 - 现象:对字符串调用
msg.push('!')报错。→ 原因:字符串不是数组,只有部分类数组特性,数组方法不适用。→ 修法:用字符串方法,或先split('')转数组处理后再join('')。
练习
- 给定二维数组
cityList,取出“潍坊”,再遍历并扁平输出所有城市;要求写出索引路径和循环。 - 编写
reverseString(str)翻转字符串,要求不直接修改原字符串中的字符,并说明原因。 - 编写
sumArray(nums)返回数组元素和;再编写getMaxMin(nums)返回最大值和最小值。调用后输出结果,注意区分return与console.log。 - 分别用函数声明和函数表达式编写
createGreeting(name, age),返回“我是X,今年Y岁”;比较两种写法在调用位置上的差异。 - 选做:编写
sortAsc(nums)将数组从小到大排序并返回新数组;说明为什么不能把字符串当数组用push处理。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 多维数组 | 04:58 |
| 2 | 字符串的数组特性 | 13:21 |
| 3 | 函数的概念 | 12:33 |
| 4 | 函数的组成 | 15:14 |
| 5 | 第一种创建函数的方式 | 14:23 |
| 6 | 第二种创建函数的方式 | 05:52 |
| 7 | 第三种第四种创建函数的方式 | 08:40 |
小结
- 字符串索引可读不可改,任何“修改字符”的需求都要生成新字符串。
- 多维数组不要凭感觉写索引,先打印中间层再逐层取值。
- JS 中函数名就是变量名,覆盖函数名的值会直接破坏调用能力。
return负责返回值,输出由调用方决定,二者不能混为一谈。- 函数声明与函数表达式调用时机不同,表达式方式要先定义后调用。
- 字符串只是类数组,数组方法不能直接用于字符串。
Function构造器写法只作了解,业务代码优先使用声明或表达式。