js函数

95 阅读9分钟

1、函数的基础知识

为什么会有函数?

在写代码的时候,有一些常用的代码需要书写很多次,如果直接复制粘贴的话,会造成大量的代码冗余;

函数可以封装一段重复的javascript代码,它只需要声明一次,就可以多次调用;

冗余代码:

  • 冗余:多余的重复或啰嗦内容

  • 缺点:

    • 代码重复,可阅读性差
    • 不易维护,如果代码逻辑变了,所有地方的代码都要跟着改,效率太低了

2、函数的声明与调用

函数声明的语法:

let 是用来声明变量的, 函数是用function来声明的,一个函数一般是用来做一件事情的。

function 函数名 (){
    //函数体
}

函数声明的时候,函数体并不会执行,只要当函数被调用的时候才会执行。

调用函数的语法:

函数名();

函数体只有在调用的时候才会执行,调用需要()进行调用。可以调用多次

示例代码:

// 声明函数
function sayHi (){
    // 函数体
    console.log("Hi!!");
}
// 调用这个函数
sayHi();  // console.log("Hi!!");

// 注意
console.log(sayHi);  // 打印的是整个函数 
// sayHi:指的就是这个函数
// ():指的是调用
// sayHi():这个函数的调用结果

3、声明函数的两种方式

3.1 函数声明(命名函数):

// 声明一个函数并且命名了
function 函数名(){
    函数体;
}
函数名();  // 调用函数

/********示例代码***********/
function fn(){
    console.log("哈哈哈");
}
fn();

3.2 函数表达式(匿名函数):

// 必须先声明才能调用
let 函数名 = function(){
    函数体;
}
函数名(); // 调用函数

/********示例代码***********/
var fn = function(){
    console.log("哈哈哈");
}
fn();

这两种函数的区别:

  • 命名函数可以先调用,再声明,因为预解析
  • 函数表达式必须先声明,再调用(在DOM中注册事件的时候用的非常的多)

匿名函数:

没有名字的函数,叫做匿名函数。匿名函数没有办法直接用,需要赋值给变量或者自调用

自调用函数也叫自执行函数,声明和调用一起

  • 可以防止变量全局污染
  • 匿名函数自调用示例代码:
(function(n1,n2){
    console.log(n1);        // 1
    console.log(n2);        // 2
    let name = "张三"
    let age = 18;
    function sayHello() {
      console.log(age);     // 18
      console.log(name);    // "张三"
    }
    sayHello();
})(1,2)

4、函数的参数

形式参数: 在声明一个函数的时候,为了函数的功能更加灵活,有些值是固定不了的,对于这些固定不了的值。我们可以给函数设置参数。这个参数没有具体的值,仅仅起到一个占位置的作用,我们通常称之为形式参数,也叫形参

实际参数: 如果函数在声明时,设置了行参,那么在函数调用的时候就需要传入对应的参数,我们把传入的参数叫做实际参数,也叫实参

语法:

//带参数的函数声明
function 函数名(形参1, 形参2, 形参...){
  //函数体
}

//带参数的函数调用
函数名(实参1, 实参2, 实参3);

特点:

  • 在函数调用的时候,需要传递对应的参数,把实参的值赋值给形参。
  • 实参如果多于形参的个数:多传的参数就丢弃了
  • 实参如果少于形参的个数:没有传的参数,值就是undefined。(容易出问题)

示例代码:

// 设置两个形参
function getSum(num1,num2){
    console.log(num1+num2);
}
// 调用的时候传两个值进去
getSum(10,20);   // 打印出来就是 30

计算n1-n2之间所有数的乘积:

function getProduct(n1, n2) {
    let product = 1;
    for (var i = n1; i <= n2; i++) {
        product *= i;
    }
    console.log(product);
}
getProduct(1, 5);  // 120

5、函数的返回值

当函数执行完的时候,我们期望函数给我一些反馈(比如计算的结果),这个时候可以让函数返回一些东西。也就是返回值。函数通过return返回一个返回值

返回值语法:

//声明一个带返回值的函数
function 函数名(形参1, 形参2, 形参...){
  //函数体
  return 返回值;
}

//可以通过变量来接收这个返回值
let 变量 = 函数名(实参1, 实参2, 实参3);

函数的调用结果就是返回值,因此我们可以直接对函数调用结果进行操作。

示例代码:

// 计算 n1- n2之间所有数的乘积
function getProduct(n1, n2) {
    let product = 1;
    for (let i = n1; i <= n2; i++) {
        product *= i;
    }
    return product; // 返回计算的值
}
let pro = getProduct(1, 5); // 用变量pro接收一下返回的值
console.log(pro);   // 120

注意:

  • 函数一碰到return,就代表函数结束了。return后面的代码不会执行了。
  • 函数可以没有返回值, 会在最后面返回一个undefined

6、函数三要素

函数三要素包括:

  • 函数名
  • 参数
  • 返回值

7、文档注释

关于文档注释,javascript中还有一种注释叫做文档注释,经常用在函数声明处,用来解释这个函数的作用。

文档注释:  /** 这是文档注释 */

以后写的函数的声明,都应该加上文档注释,方便阅读

示例代码:

/**
 * 求圆的面积
 * @param r {number} 圆的半径
 * @returns {number} 圆的面积
 */
function getArea (r) {
    return Math.PI * r * r;
}

8、函数综合练习

8.1 对任意数组从小到大排序

// 封装一个从小到大冒泡排序的函数
function bubbleSort(arr){
    for(let i = 0; i < arr.length - 1; i++){
        let flag = true;
        for(let j = 0; j < arr.length -1 -i; j++){
            if(arr[j] >arr[j+1]){
                flag =false;
                let temp = arr[j];
                arr[j] = arr[j+1];
                arr[j + 1] = temp;
            }
        }
        if(flag){
            break;
        }
    }
    return arr;
}
console.log(bubbleSort([12, 56, 14, 68, 45, 25, 17, 33]));
console.log(bubbleSort([25, 65, 48, 11, 15, 54, 24, 63]));

8.2 求任意数的阶乘(从1到n的积)

function getProduct (n){
    let product = 1; 
    for(let i = 1; i <= n; i++){
       product *= i; 
    }
    return product;
}
console.log(getProduct(5));  // 120
console.log(getProduct(3));  // 6   

8.3 求任意数组中的最大值与最小值

function getMaxAndMin(arr) {
    let max = arr[0];
    let min = arr[0];
    for (let i = 0; i < arr.length; i++) {
        if (max < arr[i]) {
            max = arr[i];
        }
        if (min > arr[i]) {
            min = arr[i];
        }
    }
    return [max, min]; // 返回一个数组
}
console.log(getMaxAndMin([11, 45, 59, 12, 8, 36, 14, 25]));  // [59 8]

9、函数的作用域

在函数中,只有全局作用域函数作用域,因为在ifwhilefor等语句中定义的变量都是全局变量。

全局变量:  在最外层声明的变量就是全局变量,全局变量在任何地方都能访问的到。

局部变量:  在函数中声明的变量,就是局部变量,局部变量只有在当前函数体内能够访问。

隐式全局变量:  没有使用var定义的变量也是全局变量。

作用域:  变量可以发挥作用的区域

全局作用域:  在script标签内,函数外定义的作用域就是全局作用域。在全局作用域中定义的变量都是全局变量。

函数作用域:  在函数中的区域叫做函数作用域,在函数作用域中定义的变量就是局部变量,只能在当前函数内访问。

10、预解析

js解析器执行js代码的时候,分为两个过程:预解析过程代码执行过程

预解析过程:

  • 把变量的声明提升到当前作用域的最前面,只会提升声明,不会提升赋值。
  • 把函数的声明提升到当前作用域的最前面,只会提升声明,不会提升调用。
  • 先提升var,再提升function

预解析例题:

第一题:

console.log(a);    // 打印a这个函数整体
var a = 1;
function a(){
console.log("呵呵");
}
console.log(a);    // 1

// 预解析后为
/**
var a;
function a(){
    console.log("呵呵");
}
console.log(a);    // 打印a这个函数整体
a = 1;
console.log(a);    // 1
*/

第二题:

var num = 10;
fn1();
function fn1() {
    //在函数调用的时候,这个函数也会做预解析操作。
    console.log(num);   // undefined
    var num = 20;
    console.log(num);   // 20
}
console.log(num);       // 10


// 预解析后为
/**
var num ;
function fn1() {
    var num;
    console.log(num);   // undefined
    num = 20;
    console.log(num);   // 20
}
num = 10;
fn1();
console.log(num);       // 10
*/

第三题:

var a = 18;
var b = 30;
fn();
function fn() {
    var b = 9;
    console.log(a); // undefined
    console.log(b); // 9
    var a = 20;
}


// 预解析后为
/**
var a;
var b;
function fn() {
    var b;
    b = 9;
    var a;
    console.log(a);     // 自己作用域里有的就不要出去找
    console.log(b);     // 9
    a = 20;
}
a = 18;
b = 30;
fn();
*/

第四题:

fn();
var b = 10;
console.log(c); // 9
console.log(b); // 10
console.log(a); // 报错

function fn() {
    var a = 9;
    b = 9;
    c = 9;
    console.log(a); // 9
    console.log(b); // 9 
    console.log(c); // 9
}


// 预解析之后
/**
var b;
function fn() {
    var a;
    a = 9;
    b = 9;
    c = 9;
    console.log(a); // 9
    console.log(b); // 9
    console.log(c); // 9
}
fn();
b = 10;
console.log(c); // 9
console.log(b); // 10
console.log(a); // 报错
*/

第五题:

function fn() { 
    console.log(num1);  // undefined
    console.log(num2);  // undefined
    console.log(num3);  // 30
    var num1 = 10;
    var num2 = 20;
    num3 = 40;
    console.log(num1);  // 10
    console.log(num2);  // 20
    console.log(num3);  // 40
}
var num1 = 20;
var num3 = 30;
fn();
console.log(num1);      // 20
console.log(num3);      // 40
console.log(num2);      // 报错


// 预解析之后
/**
var num1;
var num3;

function fn() {
    var num1;
    var num2;
    console.log(num1); // undefined
    console.log(num2); // undefined
    console.log(num3); // 30
    num1 = 10;
    num2 = 20;
    num3 = 40;
    console.log(num1); // 10
    console.log(num2); // 20
    console.log(num3); // 40
}
num1 = 20;
num3 = 30;
fn();
console.log(num1); // 20
console.log(num3); // 40
console.log(num2); // 报错
*/

11、递归函数

函数直接或者间接调用自己,必须要留出口,不然就调死了

示例代码:

// 用递归求1-100的和
/* 
    之前封装过一个getSum的函数比如getSum(100),就是求的1-100的和
    现在我们可以这样理解:
    1-100的和我们可以看做是 100 + getSum(99)
    getSum(99) 可以看成 99 + getSum(98)。。。
    依次这样推下去,但是要注意,到getSum(1)的时候,要留出口,否则会一直死循环下去
 */
function getSum(n) {
    if (n == 1) {       // 一定要留出口
        return 1;
    }
    return n + getSum(n - 1);
}
console.log(getSum(100));

12、回调函数

回调函数:把函数当成参数来使用,那么这个函数就叫回调函数。函数也是一种数据类型

示例代码:

/*
思考,之前封装了一个bubbleSort排序的函数,但是只能排元素是数字的数组
现在想要判断字符串的长度,或者对象的属性的时候就很麻烦,就需要重新写一个函数
比如字符串长度,就需要是arr[j].length - arr[i+1].length
*/
function bubbleSort(arr, fn) {
    for (let i = 0; i < arr.length; i++) {
        let flag = true;
        for (let j = 0; j < arr.length - 1 - i; j++) {
        
            // 传一个函数进来,并且将arr[j], arr[j + 1]作为两个参数传进去
            if (fn(arr[j], arr[j + 1]) > 0) {
                flag = false;
                let temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
        if (flag) {
            break;
        }
    }
}
// 纯数字数组
let arr = [4, 3, 1, 6, 22, 21, 41, 4];
// 调用的时候,我们需要将fn函数的两个参数也传进去
// 这种把一个函数作为参数传进另一个函数的方式就叫回调函数
bubbleSort(arr, function(a, b) { // a b 就相当于 arr[j] 和 arr[j+1]
    return a - b;
    // 如果是 return b - a 就相当于,上面的 arr[j+1] - arr[j]>0 那么就是从大到小排序 
});
console.log(arr);

// 封装后的排序函数也可以直接根据字符串的长度进行排序了
let arrStr = ["aaa", "bb", "cccc", "d"];
bubbleSort(arrStr, function(a, b) {
    // 因为传进去的是一个函数,arr[j] 和 arr[j+1]是以两个参数的形式传进去的
    // 当数组元素是字符串的时候,就可以进行.length操作了
    return a.length - b.length;
});
console.log(arrStr);