主题
函数与Object 对象(Day7 2/3)
本页对应 Day07_JavaScript,共 6 段视频,约 1 小时 40 分。
学习目标
- 能区分函数声明与匿名函数(函数直接量),并编写匿名函数作为变量值或直接作为参数的代码。
- 能写出立即调用函数表达式(IIFE),并说明其通过局部作用域避免全局变量污染的作用。
- 能依据“自己定义、没有直接调用、最终执行”判断回调函数,并说明
forEach中回调函数的调用过程与参数含义。 - 能识别高阶函数的两种形式(参数为函数、返回值为函数),并分析简单高阶函数的执行结果。
- 能使用
sort的回调函数实现数组升序和降序,并解释返回值正负与元素交换规则之间的关系。
知识点
先保留本节回顾清单
text
1. 函数的概念
2. 函数的组成 函数名 函数体 参数 返回值
3. 函数的调用和返回值
4. 参数
形参和实参、形参的默认值、arguments
5. 变量的作用域
作用域 全局变量 局部变量
作用域链
6. 变量提升
变量提升
函数提升函数的概念
为什么需要:函数用来封装一段可重复使用的代码,避免同样逻辑写很多遍。
怎么想:先把函数理解成一种“数据类型”,和数字、字符串、数组一样,也可以有直接量形式;转写稿里说的“匿名函数”,本质就是“函数的直接量”。
怎么做:声明函数、函数表达式、匿名函数都可以创建函数;真正执行函数要看后面有没有小括号。
易错点:不要以为有 function 就执行了;没有调用,函数体不会运行。转写稿里的“直接量”要写成“直接量/字面量”,“逆名函数”应为“匿名函数”。
函数的组成
为什么需要:知道函数由哪些部分组成,才能读懂和写对函数。
怎么想:函数名是代号;函数体是执行内容;参数是外部传进来的数据;返回值是函数执行后交出去的结果。
怎么做:写函数时先明确“要不要参数、要不要返回值”,再写函数体。
易错点:函数名只是引用,不是函数体本身;return 后面的代码不会继续执行。
函数的调用和返回值
为什么需要:函数只有被调用才执行,返回值决定调用表达式的结果。
怎么想:转写稿反复强调:判断有没有调用,不要猜,看函数后面有没有小括号。
怎么做:函数名() 是调用;return 值 把结果返回给调用处。
易错点:连续写立即调用函数表达式时,如果不写分号,浏览器可能把下一行的小括号接到上一行返回值上,报 is not a function。转写稿里的“立即调用的函数”规范叫法是 IIFE,即立即调用函数表达式。
参数
为什么需要:参数让函数能处理不同数据,提高复用性。
怎么想:形参是定义函数时写的变量,实参是调用时真正传进去的值;默认值用于实参没传的情况;arguments 可在普通函数中拿到实参集合。
怎么做:定义时写形参,调用时传实参;需要默认值时在形参后写默认值。转写稿补充:函数也可以当参数传,例如 forEach 接收一个回调函数;回调函数满足“我定义的、我没有直接调用、最终执行了”。
易错点:把回调函数传进去时不要加小括号,加了就变成“立即调用它的返回值”,常导致 undefined is not a function。转写稿里的“回掉函数”应写“回调函数”,“高阶函数”是参数或返回值可为函数的函数。
变量的作用域
为什么需要:作用域决定变量在哪里能用,避免变量互相覆盖。
怎么想:全局变量哪里都能用,局部变量只在函数内能用;作用域链是内层找不到就往外层找。转写稿强调:函数的作用域看“创建位置”,不是看“调用位置”。
怎么做:把模块代码放进函数或 IIFE 中,产生局部作用域,避免全局变量污染。
易错点:不要因为函数在某处调用,就以为它能拿到调用处的局部变量;它仍按创建时所在作用域查找。转写稿里的“作用欲”应为“作用域”。
变量提升
为什么需要:理解提升才能解释“为什么代码没执行到,变量却能访问到但值是 undefined”。
怎么想:var 声明会提升,函数声明也会提升;提升的是声明,不是赋值。
怎么做:把声明写在前面,赋值按逻辑写;函数声明和函数表达式要区分。
易错点:函数表达式赋给 var 时,提升的是变量名,不是整个函数体;调用过早会得到 undefined 而不是函数。转写稿里的“函数提升”要结合“声明提升、赋值不提升”来理解。
小结
这节回顾的主线是:函数是数据,调用看小括号,参数分形参实参,作用域看创建位置,提升只提升声明。把转写稿中的匿名函数、IIFE、回调函数、高阶函数放回这条主线里,能更稳地读懂后续代码。
匿名函数:函数也有“直接量”
为什么需要:函数和 number、string、数组一样都是数据类型,因此也可以不先赋给变量,直接写出来使用。匿名函数就是没有名字的函数,本质是“函数直接量”。
怎么想:数字 45 是直接量,数组 [12,13] 是直接量;把函数直接写出来、不命名,就是匿名函数。函数名只是代号,函数体本身才是“本尊”。
怎么做:
js
let n = 45;
console.log(n * 1); // 变量和直接量都能参与运算
let arr = [12, 13, 14];
console.log(arr[1]); // 变量取索引
console.log([12, 13, 14][1]); // 数组直接量也能取索引
let fn = function () {
console.log('匿名函数赋给变量');
};
fn();匿名函数也可以直接加括号调用,但作为语句开头时容易被浏览器解析失败,通常要先用小括号包起来:
js
(function () {
console.log('匿名函数直接调用');
})();易错点:不要把“匿名”理解成“不能执行”。匿名函数仍可赋给变量、当参数、立即调用;直接写 function(){}() 容易语法报错,因为浏览器会把函数体后的括号解析成别的结构。
立即调用函数 IIFE:用函数造一个局部作用域
为什么需要:普通函数若想起名复用,就要有函数名;但有时我们只想让一段代码立即执行,并且里面的变量不要污染全局。
怎么想:IIFE 是“立即调用函数表达式”。它创建完就调用,重要目的不是复用,而是产生局部作用域,避免全局变量重名覆盖。
怎么做:
js
(function () {
var a = 1;
console.log(a); // 1
})();
console.log(a); // 报错:a is not defined连续写两个 IIFE 时,如果不写分号,浏览器可能把下一行的小括号接到上一行返回值上:
js
(function () { console.log(100); })()
(function () { console.log(200); })() // 可能报错解决方式:加分号,或在前面加一元运算符:
js
;(function () { console.log(100); })()
!(function () { console.log(200); })()易错点:IIFE 前面的小括号不是“调用”,只是把函数包成整体;真正调用靠后面的 ()。连续 IIFE 不写分号时,!、+、- 等一元运算符能让整体断开,但最稳妥仍是统一加分号。
回调函数:我定义、我没直接调、最终执行了
为什么需要:有些方法(如 forEach)内部需要“按规则执行一段代码”,但具体做什么由使用者决定。把函数当参数传进去,就能把逻辑交给方法内部调用。
怎么想:回调函数满足三点:函数是我定义的;我没有在它后面直接加 () 调用;它最终被执行了。forEach 内部会调用传入的函数。
怎么做:
js
let names = ['欧阳姥姥', '司马姥姥', '令狐姥姥'];
names.forEach(function (item, index) {
console.log(item, index);
});不传函数会报错,因为 forEach 需要函数参数:
js
names.forEach(); // 报错,提示不是函数有名字的函数也能当回调,但传名字时不要加括号:
js
function show(item, index) {
console.log(item, index);
}
names.forEach(show); // 正确
// names.forEach(show()); // 错:这是先调用 show,再把返回值传进去易错点:回调函数传的是“函数本身”,不是调用结果;加 () 就变成立即调用。作用域看函数创建位置,不看调用位置:
js
let name = 10;
function fn01(cb) {
let name = 20;
cb();
}
function fn02() {
console.log(name);
}
fn01(fn02); // 10高阶函数:参数或返回值是函数
为什么需要:把函数当数据传递,可以封装“流程”,把可变逻辑交给调用者。
怎么想:满足一条即可:参数要求是函数,或返回值是函数。forEach 属于参数是函数的高阶函数。
怎么做:
js
function func01(cb) {
cb(100, 200);
}
func01(function (a, b) {
console.log(a, b); // 100 200
});
function func() {
return function (x) {
return x * 2;
};
}
console.log(func()(3)); // 6易错点:func() 只是拿到返回的函数,不会执行内部逻辑;要再写一个括号 func()(3)。连续调用时若前面没有返回值,就会报“不是函数”。
sort 排序:用回调告诉它何时交换
为什么需要:sort 默认会把元素转成字符串,按位比较,所以 101 可能排到 23 前面,不符合数字自然大小排序。
怎么想:sort 可接收回调函数,内部把相邻两个元素传进来。转写稿中强调:第一个参数是后一个元素,第二个是前一个元素,因此用 next、prev 起名避免搞混;回调返回负数时交换两个元素。
怎么做:
js
let arr = [23, 101, 12, 45, 89, 56];
arr.sort(); // 默认按字符串规则,101 可能靠前
arr.sort(function (next, prev) {
return next - prev; // 升序:后小前大时返回负数,交换
});
console.log(arr); // [12, 23, 45, 56, 89, 101]
arr.sort(function (next, prev) {
return prev - next; // 降序
});
console.log(arr); // [101, 89, 56, 45, 23, 12]易错点:不要凭直觉认为第一个参数一定是“前一个元素”;这里按转写稿记成 next 在后、prev 在前。sort 会直接修改原数组;默认排序不是按数字大小,必须传比较回调。
先厘清:本节说的“对象”有两层含义
为什么需要:初学 JS 时最容易把“对象”混成一个词:一会儿说“数组也是对象”,一会儿又说“Object 是一种数据类型”。如果不先分层,后面看到 Array、Function、Object 就会不知道它们在比什么。
怎么想:先把“对象”拆成广义和狭义。广义是理解数据统一性的视角;狭义是语法分类里的 Object 数据类型。两者不是互相否定,而是讨论层面不同。
怎么做:笔记里已经给了两条判断:
一切皆对象,数组、函数都是对象的一种。Object 数据类型,是对象类型中的一种,与Array、Function 是平级的。第一句是广义理解:数组、函数都可以被当作“对象的一种”来理解。第二句是狭义理解:当我们在类型分类里说 Object 时,它和 Array、Function 是平级的类型名称,不是说“所有东西都叫 Object 这个类型”。
易错点:不要用广义理解去解释狭义报错,也不要用狭义分类去否定“函数也是对象”的广义视角。先问自己:这句话是在讲“数据统一性”,还是在讲“类型名称”?
广义对象:为什么说“数组、函数都是对象的一种”
为什么需要:转写稿里反复强调“函数也别太嘚瑟,它也是一种类型的数据”,目的是建立“数据可以统一看待”的意识。否则学完数字、字符串、布尔、数组后,会误以为函数是特殊物种。
怎么想:转写稿用“直接量”和“变量”作对比:数字可以直接写 45,也可以先给变量;数组可以直接写 [1,2,3],也可以给变量;函数也可以写成函数直接量,也就是匿名函数,再赋给变量。变量只是代号,直接量才是数据本身。这个推导说明:函数和数字、数组一样,都是数据的一种表现形式。
怎么做:可以写一个匿名函数赋给变量:
js
var fn = function () {
console.log('匿名函数');
};
fn();这里的 function () { ... } 是函数直接量,fn 是变量名。转写稿说“变量只是代号,直接量才是本尊”,就是提醒我们:函数本身可以作为数据被传递、被赋值、被调用。
易错点:不要把“函数是对象的一种”理解成“函数名就是对象”。函数名只是变量名或引用名;真正被赋值、被传递的是函数这个数据。另一个易错点是连续写立即调用函数时漏分号,浏览器会把下一行开头的 ( 解析成对上一行返回值的调用,于是报 is not a function。转写稿里的调试结论是:加分号,或在前面加一元运算符,都是让解析器知道这是一个整体。
狭义对象:Object 与 Array、Function 是平级的类型名称
为什么需要:当笔记说“Object 数据类型,是对象类型中的一种,与 Array、Function 是平级的”,是在帮我们建立类型分类表。否则容易把 Object 当成“所有对象的祖宗类型”而到处乱套。
怎么想:这里的“平级”要放在“数据类型名称”的语境里理解:Object 是对象类型中的一种,Array 是数组类型,Function 是函数类型。它们都是 JS 里可讨论的类型名称。广义上数组、函数可以被看作对象的一种;狭义上在类型分类里,Object、Array、Function 是并列出现的名称。两个层面同时成立,并不矛盾。
怎么做:读代码时先判断当前语境。看到“一切皆对象”,是在讲广义视角;看到“Object 数据类型”,是在讲狭义类型。笔记里的代码要原样保留:
Object 数据类型,是对象类型中的一种,与Array、Function 是平级的。易错点:最常见的错误是把“平级”误解成“完全一样”。平级是说它们都是类型名称,不是说 Object 和 Array 的用法、能力完全相同。另一个错误是看到“函数也是对象”后,就忘了狭义上 Function 仍然是一个独立的类型名称。学习时要把“广义视角”和“狭义分类”分开记。
用转写稿的调试过程补一条判断链
为什么需要:转写稿里讲匿名函数、立即调用函数、回调函数、高阶函数、forEach、sort,表面看是函数专题,但它们共同服务于一个判断:函数也是数据,函数也可以被赋值、被传递、被调用。这正是理解“广义对象”的实践材料。
怎么想:遇到一个函数写法,不要先猜,按转写稿的规则看代码:函数后面有没有小括号?有才是直接调用;没有小括号,只是把函数当值传给别人,那可能是回调。转写稿里判断回调函数的三条是:我定义的、我没有直接调用、最终执行了。这个判断链能防止把“传函数”误说成“调用函数”。
怎么做:例如 forEach 的回调:
js
var nameList = ['欧阳姥姥', '司马姥姥', '令狐姥姥'];
nameList.forEach(function (item, index) {
console.log(item, index);
});这里 function (item, index) {} 是匿名函数,作为实参传给 forEach;它没有被我们直接加小括号调用,而是由 forEach 内部按数组元素个数调用。转写稿的调试结论是:数组有几个元素,回调就调用几次;每次传入当前元素和索引。sort 的回调则用来告诉排序算法“什么时候交换两个元素”:返回小于 0 的数就交换,否则不交换。转写稿里用 next - prev 实现升序,用 prev - next 实现降序,都是在补“函数作为数据参与规则”的推导。
易错点:把“函数当参数”写成“函数加小括号再当参数”,那就变成先调用、再把返回值传进去,常见报错是 is not a function。另外,转写稿里的同音字要纠正:是“直接量”不是“直接亮”,是“作用域”不是“作用欲”,是“形参/实参”不是“形态/食材”,是 forEach 不是“foish/forish”,是 sort 不是“salt”。这些术语一旦写错,后面读文档会对不上。
回到本节:先分层,再判断
为什么需要:本节笔记很短,但它是后面对象、数组、函数学习的入口。先把“广义对象”和“狭义 Object”分开,后面才不会把“数组也是对象”和“Object 是数据类型”混成一句话。
怎么想:看到“对象”先停一秒,问:这里是在讲广义的数据统一性,还是在讲狭义的 Object 类型?广义用于理解“数组、函数都是对象的一种”;狭义用于记住“Object 与 Array、Function 是平级的类型名称”。
怎么做:读笔记时保留两条原文,不把一条覆盖另一条。写代码时用转写稿的调试方法验证:函数有没有小括号、有没有被直接调用、返回值是什么。这样既能理解“一切皆对象”的广义视角,也能在类型分类时不把 Object、Array、Function 混为一谈。
易错点:不要因为“一切皆对象”就把所有值都当成狭义 Object;也不要因为“Object 是数据类型”就否定“函数也是对象的一种”的广义理解。两个层面各司其职,才是本节要建立的对象观。
课堂画面






代码示例
示例1:匿名函数与立即调用函数(IIFE)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>匿名函数与IIFE</title>
</head>
<body>
<script>
// 匿名函数:函数直接量,用小括号包成整体后立即调用
(function () {
var localName = '局部变量';
console.log('我是匿名的立即调用的函数', localName);
})();
// 连续写IIFE时,前面加分号,避免下一行小括号被当成上一行返回值的调用
;(function () {
console.log(100);
})();
;(function () {
console.log(200);
})();
</script>
</body>
</html>要点解释:
(function () { ... })();外层小括号把函数包成一个整体,末尾()表示立即调用。- IIFE 会产生局部作用域,里面的变量外部访问不到,可避免全局变量污染。
- 连续写多个 IIFE 时要注意分号;不加分号时,下一行开头的
(可能被解析成对上一行返回值的调用,从而报is not a function。 - 易错点:
function () {}()直接写会语法错误,必须先用小括号包起来;IIFE 内变量在外部不能使用。
示例2:forEach 回调函数与高阶函数
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>forEach与回调函数</title>
</head>
<body>
<script>
var nameList = ['欧阳姥姥', '司马姥姥', '令狐姥姥', '独孤姥姥', '西门姥姥', '东方姥姥', '随便姥姥'];
// forEach 接收一个函数作为参数:这个函数就是回调函数
nameList.forEach(function (item, index) {
console.log('元素:', item, '索引:', index);
});
// 高阶函数:参数要求是函数;内部调用传进来的回调
function func01(cb) {
cb(100, 200);
}
func01(function (a, b) {
console.log('回调接收到:', a, b);
});
</script>
</body>
</html>要点解释:
forEach是高阶函数,因为它要求参数是一个函数;传入的这个函数就是回调函数。- 回调函数由
forEach内部调用,数组有几个元素就调用几次。 - 回调参数
item是当前元素,index是当前索引;第三个参数是数组本身,不需要可以不写。 - 易错点:传入回调时不能加小括号,写成
func01(cb())会先自己调用并把返回值传进去;nameList.forEach不加括号不会执行。
示例3:sort 自定义排序规则
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>sort自定义排序</title>
</head>
<body>
<script>
var arr = [23, 101, 12, 45, 89, 56];
// 默认 sort:元素转字符串后按字符串规则逐位比较
console.log('默认排序:', arr.slice().sort());
// 升序:第一个参数是后边元素 next,第二个参数是前边元素 prev
var asc = arr.slice().sort(function (next, prev) {
return next - prev;
});
console.log('升序:', asc);
// 降序
var desc = arr.slice().sort(function (next, prev) {
return prev - next;
});
console.log('降序:', desc);
</script>
</body>
</html>要点解释:
- 默认
sort()会把元素转成字符串,再按字符串逐位比较,所以101可能排在前面,不符合数字自然大小排序。 - 自定义排序时,回调返回负数就交换当前两个元素;返回
0或正数则不变。 - 回调参数顺序容易写反:第一个参数是后边元素
next,第二个参数是前边元素prev。 - 升序写
next - prev,降序写prev - next。 - 易错点:
sort()会直接修改调用它的数组;示例中用slice()复制后再排序,避免影响原数组。
常见错误
现象:写
function(){ console.log(1) }()直接报语法错误。
原因:浏览器把开头的function按语句解析,函数体后的小括号无法按预期调用。
修法:用(function(){ console.log(1) })(),或!function(){ console.log(1) }()等一元运算符包裹。现象:连续写两个 IIFE 且不写分号,第二个报
... is not a function。
原因:第二个 IIFE 前的小括号被解析成调用上一个函数的返回值。
修法:每条语句加分号;或在第二个 IIFE 前加;、!等分隔。现象:
arr.forEach(fn())或arr.sort(cb())报错/立即执行。
原因:fn()、cb()是调用表达式,传给高阶函数的是返回值,不是函数本身。
修法:传函数名fn、cb,或直接写匿名函数function(){},不要加调用小括号。现象:
sort回调写完后升序降序反了,或结果不符合预期。
原因:sort回调参数顺序和“交换规则”理解错,且返回负数才交换。
修法:用next、prev明确命名;升序可return next - prev,降序可return prev - next。现象:在回调函数里等不到执行,或误以为自己没有调用却执行了。
原因:回调函数由高阶函数/API 内部调用,不是定义时直接调用。
修法:区分“定义函数”和“调用函数”;检查是否把函数作为参数传入,而不是加括号调用。现象:用 IIFE 包了代码,变量仍然污染全局。
原因:内部变量漏写var/let/const,或显式挂到window上。
修法:IIFE 内部使用局部声明,避免把模块变量挂到全局对象。现象:判断回调函数里的变量值时,按“调用位置”找作用域,结果判断错。
原因:作用域链由函数创建/定义位置决定,不由调用位置决定。
修法:找变量时回到函数定义处,沿定义处的外层作用域分析。
练习
改错:
jsfunction(){ console.log('A') }() (function(){ console.log('B') })()要求说明两处问题及至少两种修法。
分析输出:
js['a','b','c'].forEach(function(item, index){ console.log(item, index); });回调函数被调用几次?每次
item、index分别是什么?写代码:给数组
[23,101,12,45,89,56]使用sort,分别实现升序和降序,并解释回调返回值正负对应的交换行为。判断并说明:
jsfunction run(cb){ cb(100, 200); } run(function(a, b){ console.log(a, b); });谁是回调函数?谁是高阶函数?输出什么?
分析输出并说明原因:
jslet name = '全局'; function fn1(cb){ let name = '局部'; cb(); } function fn2(){ console.log(name); } fn1(fn2);
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 匿名函数 | 09:29 |
| 2 | 立即调用的函数 | 16:48 |
| 3 | 回调函数 | 22:14 |
| 4 | 回调函数的应用场景 | 02:53 |
| 5 | 高阶函数 | 34:21 |
| 6 | 数组排序算法sort | 14:18 |
小结
- 匿名函数本质是函数直接量,可以赋值给变量,也可以直接作为参数或返回值使用。
- IIFE 的核心价值是立即执行并产生局部作用域,常用于隔离模块变量、避免全局污染。
- IIFE 的语法坑主要来自解析连续性和分号省略,括号、分号、一元运算符都是常见修法。
- 回调函数的关键判断是:我定义、我没有直接调用、最终被执行;有名字函数同样可作回调。
- 回调函数最常见形式是作为其他函数的参数,高阶函数则表现为“参数为函数”或“返回值为函数”。
forEach的回调参数常用item、index,调用次数与数组元素个数一致。sort回调只负责告诉内部算法“何时交换”,返回负数交换,返回 0 或正数不交换。- 判断回调函数中的作用域时,看函数定义位置,不看它被调用的位置。