视频加载失败

JavaScript函数

4896 字
24 分钟
JavaScript函数

本章主要学习JavaScript的函数相关知识,包括递归和闭包,关于递归,其作用最主要就是实现深克隆,但现在structuredClone()可以实现,虽然有些情况不能使用,但是基本满足大多数情况了,这里学习递归是巩固基础知识,关于闭包,其作用最主要就是实现私有变量,现代开发中也有可以代替,#号关键词,但是其作用是在class,总之,这两个属于基础,有学习的必要,但是深入了解对现代开发意义不是很大

什么是函数#

先从一个问题说起:假如我们要分别计算1到10、5到12、14到35的整数和,不用函数的情况下代码是这样的:

for (var i = 1, sum = 0; i <= 10; i++) {
sum += i;
}
console.log(sum);
for (var i = 5, sum = 0; i <= 12; i++) {
sum += i;
}
console.log(sum);

这些代码高度重复,只是变量不同,使用函数就可以解决这个问题;函数可以理解为:一个神奇的机器,送入机器两 个数字,它将产生两个数字之 间所有整数的和。

总结:

  • 函数就是语句的封装,可以让这些代码方便地被复用
  • 函数具有“一次定义,多次调用”的优点
  • 使用函数,可以简化问题,让代码更具有可读性

函数的定义和调用#

这里的函数声明提升,是JavaScript的机制,但是在现代开发中应该避免这种写法,很容易引发bug

函数的定义

和变量类似,函数必须先定义然后才能使用,函数定义有两种,分别是使用function关键字定义和函数表达式匿名函数

//使用function关键字定义函数
function fun() {
// 函数体语句
}
//函数表达式匿名函数
var fun = function () {
// 函数体语句
}

函数的调用

执行函数体中的所有语句,就称为“调用函数”;调用函数非常简单,只需在函数名字后书写圆括号对即可

fun() // 调用函数;圆括号中是实参列表,如果没有实参,也要书写圆括号

语句执行顺序

  1. 函数不调用,内部语句就不会执行
  2. 函数调用后,函数体内所有语句执行完毕, 将语句执行权交还给主程序

函数声明的提升

和变量声明提升类似,函数声明也可以被提升,这里需要注意,函数内部的变量也会提升,在函数内部提升

fun();
function fun() {
alert("函数被执行");
}//在预解析阶段会被提升

函数表达式不能提升

fun(); // 引发错误
var fun = function () {
alert("函数被执行");
}

这个var fun会被当做一个变量提升,但是提升的是变量,不是提升函数,所以会引发错误

函数优先提升

这是一种比较极端的现象,表达式函数和普通函数同时存在 一个相同的函数名,结论是:第一次弹出 B,第二次弹出 A。函数声明先准备好,代码再从上往下执行;函数表达式的赋值要等执行到那一行才生效。 因此,第一次调用得到 B,赋值之后第二次调用得到 A。

//函数优先提升
//变量声明提升,无法覆盖提升的函数
fun();// 弹出B
var fun = function () {
alert('A');
}
function fun() {
alert('B');
}
fun();// 弹出A

函数的参数和返回值#

函数的参数

参数是函数内的一些待定值,在调用函数时,必须传入这些 参数的具体值函数的参数可多可少,函数可以没有参数,也可以有多个参 数,多个参数之间需要用逗号隔开

//圆括号中定义“形式参数”
function add(a, b) {
var sum = a + b;
console.log('两个数字的和是' + sum);
}
add(3, 5);//调用函数时传入“实际参数”

形实结合

  • 在上面例子中,3对应a,5对应b
  • 如果add(3, 5,6);而add(a, b),此时6没有形参接受他,会被直接忽略
  • 如果add(3, 5);而add(a, b,c),此时c没有实参传递他,c值为undefined
  • 以上两点中,被忽略或者undefined,开发中一般都不是我们想看到的,这也是JS松散语法容易导致bug的原因,在typescript中会得到解决

arguments

  • 函数内arguments表示它接收到的实参列表,它是一个类数组对象

  • 类数组对象:所有属性均为从0开始的自然数序列,并且有 length属性,和数组类似可以用方括号书写下标访问对象的某个属性值,但是不能调用数组的方法

    //不管用户传递多少参数,都能计算它们和
    function fun(){
    var sum = 0
    for(var i = 0;i<arguments.length;i++){
    sum += arguments[i]
    }
    console.log('所有参数和' + sum);
    //console.log(arguments);
    //console.log(arguments[0]);
    // console.log(arguments[1]);
    }
    fun(33,44,55,66)

函数的返回值

  • 函数体内可以使用return关键字表示“函数的返回值”

    function sum(a, b) {
    return a + b;
    }
    var result = sum(3, 5);
  • 调用一个有返回值的函数,可以被当做一个普通值,从而可以出现在任何可以书写值的地方

    function sum(a, b) {
    return a + b;
    }
    var result = sum(3, sum(4, 5));

遇见return即退出函数

  • 调用函数时,一旦遇见return语句则会立即退出函数,将执行权交还给调用者

    function fun() {
    console.log('A');
    return 'B';
    console.log('C');
    }
    console.log(1);
    var char = fun();
    console.log(char);
    console.log(2);
  • 结合if语句的时候,往往不需要写else分支了

  • 比如题目:请编写一个函数,判断一个数字是否是偶数

    function checkEven(n){
    if(n % 2 == 0){
    return true
    }
    return false
    }
    var result = checkEven(7);
    console.log(result);

函数算法题#

寻找喇叭花数

  • 题目:喇叭花数是这样的三位数:其每一位数字的阶乘之和 恰好等于它本身。即abc = a! + b!+ c!,其中abc表示一 个三位数。试寻找所有喇叭花数。

  • 思路:将计算某个数字的阶乘封装成函数,这样可以让问题 简化。

    // 求阶乘
    //5! = 5 × 4 × 3 × 2 × 1 = 120
    //3! = 3 × 2 × 1 = 6
    //1! = 1
    //0! = 1
    function factorial(n) {
    var result = 1;
    for (var i = 1; i <= n; i++) {
    result *= i;//result=1*1;result=1*2;result=2*3;result=6*4 等于1*2*3*4=24
    }
    return result;
    }
    // 寻找喇叭花数
    for (var num = 100; num <= 999; num++) {
    var a = parseInt(num / 100, 10); // 百位
    var b = parseInt(num / 10, 10) % 10; // 十位
    var c = num % 10; // 个位
    var sum = factorial(a) + factorial(b) + factorial(c);
    if (sum === num) {
    console.log(num);
    }
    }

JavaScript内置sort()方法

  • 数组排序可以使用sort()方法,这个方法的参数又是一个函 数。

    var arr = [33, 22, 55, 11];
    arr.sort(function (a, b) {
    });
  • 这个函数中的a、b分别表示数组中靠前和靠后的项,如果 需要将它们交换位置,则返回任意正数;否则就返回负数。

    var arr = [33, 22, 55, 11];
    //排序
    arr.sort(function (a, b) {
    if (a > b) {
    return 1;
    } else {
    return -1;
    }
    });
    console.log(arr);
    // 方式2
    var arr = [33, 22, 55, 11];
    arr.sort(function (a, b) {
    return a-b;
    });
    console.log(arr);

递归#

什么是递归#

  • 函数的内部语句可以调用这个函数自身,从而发起对函数的一次迭 代。在新的迭代中,又会执行调用函数自身的语句,从而又产生一 次迭代。当函数执行到某一次时,不再进行新的迭代,函数被一层 一层返回,函数被递归。
  • 递归是一种较为高级的编程技巧,它把一个大型复杂的问题层层转 化为一个与原问题相似的规模较小的问题来求解。

用求4的阶乘举例

  • 最开始求4的阶乘,只需知道3的阶乘 4;3的阶乘等于2的阶乘 3;2的阶乘等于1的阶乘 * 2;1的阶乘等于1;

  • 1的阶乘等于1;那2的阶乘等于1 * 2就是2;3的阶乘等于3 * 2就是6;4的阶乘等于4 * 6就是24,最终等于24

    function factorial(n) {
    // 递归出口
    if (n === 0) return 1;
    // 递归调用
    return n * factorial(n - 1);
    }
    console.log(factorial(4));
  • 递归的要素:边界条件:确定递归到何时终止,也称为递归出口;递归模式:大问题是如何分解为小问题的,也称为递归体

递归常见算法题#

斐波那契数列

  • 斐波那契数列是这样的数列:1、1、2、3、5、8、13、21

  • 规律:数列下标为0和1的项的值都是1,从下标为2的项开始,每项等于 前面两项的和

    //编写一个函数,返回斐波那契数列中下标为n的那项的值
    function fibonacci(n) {
    // 递归出口
    if(n == 0 || n ==1) return 1;
    // 递归调用
    return fibonacci(n - 1) + fibonacci(n - 2);
    }
    // console.log(fibonacci(6));
    function fun(n){
    for(var i = 0; i < n; i++){
    console.log(fibonacci(i));
    }
    }
    //计算前15项斐波那契数列
    fun(15)

递归实现深克隆#

复习引用类型值的相关知识

类型举例当 var a = b 变量传值时当用 == 比较时
基本类型值数字型、字符串型、布尔型、undefined 型内存中产生新的副本比较值是否相等
引用类型值对象、数组内存中不产生新的副本,而是让新变量指向同一个对象比较内存地址是否相同,即比较是否是同一个对象

复习浅克隆

  • 使用var arr2 = arr1这样的语句不能实现数组的克隆

  • 浅克隆:准备一个空的结果数组,然后使用for循环遍历原 数组,将遍历到的项都推入结果数组

    var arr1 = [33,44,55,66,77,88,99,100];
    //var arr2 = arr1;
    //arr1.push(111);
    // arr2 也会被改变
    //console.log(arr2);
    // arr1 和 arr2 是同一个数组
    // console.log(arr1 === arr2);
    //准备一个结果数组
    var result = [];
    for(var i = 0; i < arr1.length; i++){
    result.push(arr1[i]);
    }
    arr1.push(222);
    // result 不会被改变
    console.log(result);
    console.log(arr1);
    // arr1 和 result 是不同的数组
    console.log(arr1 === result);
  • 浅克隆只克隆数组的一层,如果数组是多维数组,则克隆的 项会“藕断丝连”。

    var arr1 = [33,44,55,66,[77,88]];
    var result = [];
    for(var i = 0; i < arr1.length; i++){
    result.push(arr1[i]);
    }
    arr1[4].push(222);
    console.log(result);//[33,44,55,66,[77,88,222]] 浅克隆

实现深克隆

  • 使用递归思想,整体思路和浅克隆类似,但稍微进行一些改 动:如果遍历到项是基本类型值,则直接推入结果数组;如 果遍历到的项是又是数组,则重复执行浅克隆的操作。

    var arr1 = [33,44,55,66,[77,88]];
    //实现深度拷贝
    function deepCopy(arr){
    var result = [];
    for(var i = 0; i < arr.length; i++){
    // 递归调用
    // 递归调用的条件:当前元素是数组
    if(Array.isArray(arr[i])){
    result.push(deepCopy(arr[i]));
    }else{
    // 非数组元素,直接添加到结果数组
    result.push(arr[i]);
    }
    }
    return result;
    }
    arr2 = deepCopy(arr1)
    console.log('arr2的数组:',arr2);//[33,44,55,66,[77,88]]
    console.log( arr1 === arr2);//false
    arr1[4].push(222);
    console.log('arr2的数组:',arr2);//[33,44,55,66,[77,88]]
    console.log('arr1的数组:',arr1);//[33,44,55,66,[77,88,222]]

全局变量和局部变量#

变量作用域

  • JavaScript是函数级作用域编程语言:变量只在其定义时所 在的function内部有意义。

    function fun() {
    var a = 10;
    }
    fun();
    console.log(a); // 报错
    //变量a是在fun函数中被定义的,所以变量a只在fun函数内部有定义,fun函数就是a的“作用域”,变量a被称为局部变量。

全局变量

  • 如果不将变量定义在任何函数的内部,此时这个变量就是全 局变量,它在任何函数内都可以被访问和更改。

    var a = 10;
    function fun() {
    a++;
    console.log(a); // 输出11
    }
    fun();
    console.log(a); // 输出11变量a没有定义在任何函数内部,它是“全局变量”
    var a = 10;
    function fun() {
    var a = 5;
    a++;
    console.log(a); // 输出6
    }
    fun();
    console.log(a); // 输出10

遮蔽效应

  • 如果不将变量定义在任何函数的内部,此时这个变量就是全 局变量,它在任何函数内都可以被访问和更改

    var a = 10;//变量a没有定义在任何函数内部,它是“全局变量
    function fun() {
    a++;//这是全局变量a
    console.log(a); // 输出11
    }
    fun();
    console.log(a); // 输出11
  • 如果函数中也定义了和全局同名的变量,则函数内的变量会 将全局的变量“遮蔽”

    var a = 10;
    function fun() {
    var a = 5;//局部变量a将全局变量a“遮蔽”了
    a++;//改变的是局部变量a的值,不影响全局变量
    console.log(a); // 输出6
    }
    fun();
    console.log(a); // 输出10

注意考虑变量声明提升的情况

var a = 10;
function fun() {
//变量提升 var a
a++; //局部变量a被自增1,a此时是undefined,自增1结果是NaN
var a = 5;//重新将a赋值为5
console.log(a);// 输出5
}
fun();
console.log(a);// 输出10

形参也是局部变量

var a = 10;
function fun(a) {//形参a也是函数内部的局部变量
a++;//8
console.log(a);
}
fun(7);
console.log(a);//10

作用域链

  • 先来认识函数的嵌套:一个函数内部也可以定义一个函数。 和局部变量类似,定义在一个函数内部的函数是局部函数。

    function fun() {
    function inner() {
    console.log('你好');
    }
    inner();// 调用内部函数
    }
    fun();// 调用外部函数
  • 在函数嵌套中,变量会从内到外逐层寻找它的定义。

    var a = 10;
    var b = 20;
    function fun() {
    var c = 30;
    function inner() {
    var a = 40;
    var d = 50;
    console.log(a, b, c, d);//使用变量时,JS会从当前层开始,逐层向上寻找定义 [40 20 30 50]
    }
    inner();
    }
    fun();

不加var将定义全局变量

  • 在初次给变量赋值时,如果没有加var,则将定义全局变量。

    function fun() {
    a = 3;
    }
    fun();
    console.log(a); // 3
    function fun() {
    var a = 3;
    }
    fun();
    console.log(a);//报错 a is not defined

闭包#

什么是闭包#

从一个题目开始

这个程序的运行结果是什么呢?

function fun() {
var name = '慕课网';
function innerFun() {
alert(name);
}
return innerFun;//返回了内部函数
}
var inn = fun();//内部函数被移动到了外部执行
inn();
  • fun函数里面定义变量name是慕课网,并且定义一个函数innerFun,innerFun弹出name,也就是慕课网,fun函数返回innerFun函数,执行fun函数,inn变量接受,inn接受的是fun返回的innerFun函数,innerFun执行,弹出name,也就是慕课网

什么是闭包

  • JavaScript中函数会产生闭包(closure)。闭包是函数本身和该函数声明时所处的环境状态的组合。
  • 函数能够“记忆住”其定义时所处的环境,即使函数不在其 定义的环境中被调用,也能访问定义时所处环境的变量

观察闭包现象

  • 在JavaScript中,每次创建函数时都会创建闭包。
  • 但是,闭包特性往往需要将函数“换一个地方”执行,才能 被观察出来。

闭包非常实用

  • 闭包很有用,因为它允许我们将数据与操作该数据的函数关联 起来。这与“面向对象编程”有少许相似之处。
  • 闭包的功能:记忆性、模拟私有变量

闭包的记忆性-案例#

  • 当闭包产生时,函数所处环境的状态会始终保持在内存中,不会在外层函数调用后被自动清除。这就是闭包的记忆性。

闭包的记忆性举例

  • 创建体温检测函数checkTemp(n),可以检查体温n是否正常 ,函数会返回布尔值。

  • 但是,不同的小区有不同的体温检测标准,比如A小区体温合 格线是37.1℃,而B小区体温合格线是37.3℃,应该怎么编程 呢?

    function createCheckTemp(standardTemp){
    function checkTemp(temp){
    if(temp <= standardTemp){
    console.log('你的体温正常');
    }else{
    console.log('你的体温异常');
    }
    }
    return checkTemp;
    }
    var checkTemp = createCheckTemp(37.1);//创建一个检查体温的函数,标准体温为37.1度
    var checkTemp1 = createCheckTemp(37.3);//创建一个检查体温的函数,标准体温为37.3度
    checkTemp(36.5);
    checkTemp(37.5);
    checkTemp1(37.2);

ES6版本实现记忆性:

class CheckTemp {
#standardTemp; // 私有字段:标准体温
constructor(standardTemp) {
this.#standardTemp = standardTemp;
}
checkTemp(temp) {
if (temp <= this.#standardTemp) {
console.log('你的体温正常');
} else {
console.log('你的体温异常');
}
}
}
// 创建两个不同标准的体温检测实例
const checkTemp = new CheckTemp(37.1);
const checkTemp1 = new CheckTemp(37.3);
// 检测体温
checkTemp.checkTemp(36.5); // 你的体温正常
checkTemp.checkTemp(37.5); // 你的体温异常
checkTemp1.checkTemp(37.2); // 你的体温正常
checkTemp1.checkTemp(37.5); // 你的体温异常

闭包模拟私有变量#

  • 题目:请定义一个变量a,要求是能保证这个a只能被进行指 定操作(如加1、乘2),而不能进行其他操作,应该怎么编 程呢?

  • 在Java、C++等语言中,有私有属性的概念,但是JavaScript 中只能用闭包来模拟。

    function fun(){
    var a = 0;
    return {
    getA: function(){
    console.log(a);
    },
    add:function(){
    a++;
    },
    pow:function(){
    a *=2;
    }
    }
    }
    var obj = fun();
    //如果想在fun函数中访问a变量,唯一的方法是通过getA方法访问a变量
    obj.getA();
    obj.add();
    obj.getA();
    obj.pow();
    obj.getA();

使用闭包的注意点

  • 不能滥用闭包,否则会造成网页的性能问题,严重时可能导致 内存泄露。所谓内存泄漏是指程序中己动态分配的内存由于某 种原因未释放或无法释放。
  • 所谓内存泄漏是指程序中己动态分配的内存由于某种原因未释 放或无法释放。

闭包的一道面试题

function addCount() {
var count = 0;
return function () {
count = count + 1;//count++也可以;1
console.log(count);//1
};
}
var fun1 = addCount();
var fun2 = addCount();
fun1();//1
fun2();//1
fun2();//2
fun1();//2
  • 闭包具有记忆性

立即执行函数IIFE#

什么是IIFE

IIFE(Immediately Invoked Function Expression,立即 调用函数表达式)是一种特殊的JavaScript函数写法,一旦 被定义,就立即被调用。

(function () {
statements
})();

形成IIFE的方法

函数不能直接加圆括号被调用

function() {
alert(1)
}();//错误

函数必须转为“函数表达式”才能被调用。

(function() {
alert(1)
})();
+function() {
alert(1)
}();
-function() {
alert(1)
}();
写法作用最终表达式的值推荐程度
(function(){})()括号转换为表达式并立即调用函数的返回值推荐
+function(){}()一元加号转换为表达式并立即调用返回值经过数值转换不推荐
-function(){}()一元减号转换为表达式并立即调用返回值数值转换后取负不推荐

IIFE的作用1 - 为变量赋值

为变量赋值:当给变量赋值需要一些较为复杂的计算时(如 if语句),使用IIFE显得语法更紧凑。

var age = 12;
var sex = '男';
var title = (function () {
if (age < 18) {
return '小朋友' } else {
if (sex == '男') {
return '先生';
} else {
return '女士';
}
}
})();

IIFE的作用2

var arr = [];
for (var i = 0; i < 5; i++) {
arr.push(function () {
console.log(i);//变量i是全局变量,所有函数都共享内存中的同一个变量i
});
}
arr[1]();//5

IIFE的作用 - 将全局变量变为局部变量

IIFE可以在一些场合(如for循环中)将全局变量变为局部变 量,语法显得紧凑

var arr = [];
for (var i = 0; i < 5; i++) {
(function (i) {
arr.push(function () {
console.log(i);
});
})(i);//这是立即执行函数,i是当前循环的变量
}
arr[2](); // 弹出2

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

JavaScript函数
https://f.youzi177.cn/posts/2026102001javascript-han-shu/
作者
小封鸭
发布于
2026-10-19
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
小封鸭
小楼一夜听春雨,深巷明朝卖杏花
公告
欢迎来到我的博客。这里主要记录前端全栈和日常内容
分类
标签
最新动态
站点统计
文章
60
分类
3
标签
12
总字数
107,734
运行时长
0 天
最后活动
0 天前
站点信息
构建平台
EdgeOne Pages
博客版本
Firefly v6.16.8
文章许可
CC BY-NC-SA 4.0
文章目录