JavaScript函数

在本章节中,我们将学习JavaScript函数的相关知识,包括函数的定义、调用、类型、属性和方法等内容。

1. 什么是函数?

函数是一段可重复使用的代码块,用于执行特定的任务。函数可以接受输入参数,并返回一个输出结果。

函数的主要作用:

  • 封装代码,提高代码的可重用性
  • 提高代码的可读性和可维护性
  • 实现模块化编程
  • 实现异步编程

2. 函数的定义

在JavaScript中,有多种定义函数的方式:

2.1 函数声明

使用function关键字声明函数。

// 函数声明语法
function functionName(parameters) {
    // 函数体
    return value; // 可选的返回值
}

// 函数声明示例
function add(a, b) {
    return a + b;
}

// 调用函数
let result = add(5, 3); // 8

2.2 函数表达式

将函数赋值给一个变量。

// 匿名函数表达式
let add = function(a, b) {
    return a + b;
};

// 命名函数表达式
let multiply = function multiply(a, b) {
    return a * b;
};

// 调用函数
let result1 = add(5, 3); // 8
let result2 = multiply(5, 3); // 15

2.3 箭头函数

使用箭头=>定义函数,是ES6(2015)引入的新特性。

// 箭头函数语法
let functionName = (parameters) => {
    // 函数体
    return value; // 可选的返回值
};

// 简化语法:当只有一个参数时,可以省略括号
let square = x => x * x;

// 简化语法:当函数体只有一条返回语句时,可以省略大括号和return关键字
let add = (a, b) => a + b;

// 调用函数
let result1 = square(5); // 25
let result2 = add(5, 3); // 8

2.4 Function构造函数

使用Function构造函数创建函数,不推荐使用,因为它会影响性能和安全性。

// Function构造函数示例
let add = new Function('a', 'b', 'return a + b');

// 调用函数
let result = add(5, 3); // 8

3. 函数的调用

函数定义后,需要调用才能执行。函数调用的方式有多种:

3.1 直接调用

直接使用函数名调用函数。

function test() {
    console.log("直接调用函数");
}

test(); // 直接调用函数

3.2 作为方法调用

函数作为对象的方法调用,此时this指向调用该方法的对象。

let person = {
    name: "张三",
    sayHello: function() {
        console.log(`你好,我叫${this.name}。`);
    }
};

person.sayHello(); // 你好,我叫张三。

3.3 作为构造函数调用

使用new关键字调用函数,创建一个新对象,此时this指向新创建的对象。

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.sayHello = function() {
        console.log(`你好,我叫${this.name},今年${this.age}岁。`);
    };
}

let person = new Person("张三", 18);
person.sayHello(); // 你好,我叫张三,今年18岁。

3.4 使用call()和apply()调用

使用call()apply()方法调用函数,可以显式指定this的值。

function sayHello(city) {
    console.log(`你好,我叫${this.name},今年${this.age}岁,来自${city}。`);
}

let person = {
    name: "张三",
    age: 18
};

// 使用call()调用函数,参数逐个传递
sayHello.call(person, "北京"); // 你好,我叫张三,今年18岁,来自北京。

// 使用apply()调用函数,参数以数组形式传递
sayHello.apply(person, ["上海"]); // 你好,我叫张三,今年18岁,来自上海。

3.5 使用bind()调用

使用bind()方法创建一个新函数,绑定this的值和部分参数。

function sayHello(city, country) {
    console.log(`你好,我叫${this.name},今年${this.age}岁,来自${city}${country}。`);
}

let person = {
    name: "张三",
    age: 18
};

// 使用bind()创建一个新函数,绑定this和部分参数
let sayHelloToChina = sayHello.bind(person, "北京");

// 调用新函数
sayHelloToChina("中国"); // 你好,我叫张三,今年18岁,来自北京中国。

4. 函数的参数

函数可以接受零个或多个参数。在JavaScript中,函数参数具有以下特性:

4.1 参数的默认值

可以为函数参数设置默认值,当调用函数时不传递该参数或传递undefined时,使用默认值。

// 参数默认值示例
function greet(name = "陌生人") {
    console.log(`你好,${name}!`);
}

greet(); // 你好,陌生人!
greet("张三"); // 你好,张三!
greet(undefined); // 你好,陌生人!

// 多个参数的默认值
function add(a = 0, b = 0) {
    return a + b;
}

console.log(add()); // 0
console.log(add(5)); // 5
console.log(add(5, 3)); // 8

4.2 剩余参数

使用...语法定义剩余参数,将剩余的参数收集到一个数组中。

// 剩余参数示例
function sum(...numbers) {
    return numbers.reduce((total, number) => total + number, 0);
}

console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15

// 剩余参数必须是最后一个参数
function multiply(multiplier, ...numbers) {
    return numbers.map(number => number * multiplier);
}

console.log(multiply(2, 1, 2, 3)); // [2, 4, 6]

4.3 展开运算符

使用...语法将数组或对象展开为多个参数。

// 展开运算符示例
let numbers = [1, 2, 3, 4, 5];

// 将数组展开为多个参数
console.log(Math.max(...numbers)); // 5

// 合并数组
let moreNumbers = [6, 7, 8];
let allNumbers = [...numbers, ...moreNumbers]; // [1, 2, 3, 4, 5, 6, 7, 8]

// 合并对象
let person1 = { name: "张三", age: 18 };
let person2 = { city: "北京", ...person1 };
console.log(person2); // { city: "北京", name: "张三", age: 18 }

5. 函数的返回值

函数可以使用return语句返回一个值,return语句会终止函数的执行。

// 返回一个值
function add(a, b) {
    return a + b;
}

let result = add(5, 3); // 8

// 返回多个值(通过数组或对象)
function getPerson() {
    return {
        name: "张三",
        age: 18,
        city: "北京"
    };
}

let person = getPerson();
console.log(person.name); // 张三
console.log(person.age); // 18
console.log(person.city); // 北京

// 提前返回
function isEven(number) {
    if (typeof number !== "number") {
        return false;
    }
    return number % 2 === 0;
}

console.log(isEven(4)); // true
console.log(isEven(5)); // false
console.log(isEven("4")); // false

6. 函数的作用域

函数内部可以访问其所在作用域中的变量,以及全局作用域中的变量。

// 全局变量
let globalVar = "全局变量";

function outer() {
    // 外部函数变量
    let outerVar = "外部函数变量";
    
    function inner() {
        // 内部函数变量
        let innerVar = "内部函数变量";
        
        // 可以访问所有作用域中的变量
        console.log(innerVar); // 内部函数变量
        console.log(outerVar); // 外部函数变量
        console.log(globalVar); // 全局变量
    }
    
    inner();
    
    // 无法访问内部函数变量
    console.log(innerVar); // 报错:innerVar is not defined
}

outer();

7. 函数的属性和方法

函数是对象,具有属性和方法。

7.1 name属性

name属性返回函数的名称。

// 函数名称示例
function add(a, b) {
    return a + b;
}

console.log(add.name); // "add"

// 匿名函数表达式的名称
let multiply = function(a, b) {
    return a * b;
};

console.log(multiply.name); // "multiply"

// 箭头函数的名称
let square = x => x * x;

console.log(square.name); // "square"

7.2 length属性

length属性返回函数参数的数量。

// 函数length属性示例
function add(a, b) {
    return a + b;
}

console.log(add.length); // 2

// 带默认值的参数
function greet(name = "陌生人") {
    console.log(`你好,${name}!`);
}

console.log(greet.length); // 0

// 剩余参数
function sum(...numbers) {
    return numbers.reduce((total, number) => total + number, 0);
}

console.log(sum.length); // 0

7.3 call()方法

call()方法调用函数,显式指定this的值和参数。

// call()方法示例
function sayHello(city) {
    console.log(`你好,我叫${this.name},来自${city}。`);
}

let person = {
    name: "张三"
};

sayHello.call(person, "北京"); // 你好,我叫张三,来自北京。

7.4 apply()方法

apply()方法调用函数,显式指定this的值,参数以数组形式传递。

// apply()方法示例
function sayHello(city, country) {
    console.log(`你好,我叫${this.name},来自${city}${country}。`);
}

let person = {
    name: "张三"
};

sayHello.apply(person, ["北京", "中国"]); // 你好,我叫张三,来自北京中国。

7.5 bind()方法

bind()方法创建一个新函数,绑定this的值和部分参数。

// bind()方法示例
function sayHello(city) {
    console.log(`你好,我叫${this.name},来自${city}。`);
}

let person = {
    name: "张三"
};

let sayHelloToBeijing = sayHello.bind(person, "北京");
sayHelloToBeijing(); // 你好,我叫张三,来自北京。

8. 函数的类型

8.1 普通函数

使用函数声明或函数表达式定义的函数。

function add(a, b) {
    return a + b;
}

let multiply = function(a, b) {
    return a * b;
};

8.2 箭头函数

使用箭头=>定义的函数,是ES6(2015)引入的新特性。

let add = (a, b) => a + b;
let square = x => x * x;
let greet = () => console.log("你好!");

8.3 匿名函数

没有名称的函数。

// 匿名函数作为参数
setTimeout(function() {
    console.log("1秒后执行");
}, 1000);

// 匿名函数作为事件处理程序
document.getElementById("btn").addEventListener("click", function() {
    console.log("按钮被点击了");
});

8.4 递归函数

调用自身的函数。

// 递归函数示例:计算阶乘
function factorial(n) {
    if (n <= 1) {
        return 1;
    }
    return n * factorial(n - 1);
}

console.log(factorial(5)); // 120

// 递归函数示例:计算斐波那契数列
function fibonacci(n) {
    if (n <= 1) {
        return n;
    }
    return fibonacci(n - 1) + fibonacci(n - 2);
}

console.log(fibonacci(6)); // 8

8.5 高阶函数

接受一个或多个函数作为参数,或者返回一个函数的函数。

// 高阶函数示例:接受一个函数作为参数
function map(arr, callback) {
    let result = [];
    for (let i = 0; i < arr.length; i++) {
        result.push(callback(arr[i]));
    }
    return result;
}

let numbers = [1, 2, 3, 4, 5];
let squares = map(numbers, x => x * x); // [1, 4, 9, 16, 25]

// 高阶函数示例:返回一个函数
function multiplyBy(multiplier) {
    return function(number) {
        return number * multiplier;
    };
}

let double = multiplyBy(2);
let triple = multiplyBy(3);

console.log(double(5)); // 10
console.log(triple(5)); // 15

8.6 立即执行函数表达式(IIFE)

定义后立即执行的函数表达式。

// IIFE语法
(function() {
    // 函数体
})();

// IIFE示例
(function() {
    console.log("立即执行函数");
})();

// 带参数的IIFE
(function(name) {
    console.log(`你好,${name}!`);
})("张三"); // 你好,张三!

// 箭头函数的IIFE
(() => {
    console.log("箭头函数的IIFE");
})();

9. 箭头函数

箭头函数是ES6(2015)引入的新特性,它提供了一种更简洁的函数定义方式。

9.1 箭头函数的特点

  • 语法简洁
  • 没有自己的this,继承父级作用域的this
  • 不能作为构造函数使用
  • 没有arguments对象
  • 没有prototype属性

9.2 箭头函数示例

// 基本语法
let add = (a, b) => a + b;

// 只有一个参数时,可以省略括号
let square = x => x * x;

// 没有参数时,必须使用括号
let greet = () => console.log("你好!");

// 函数体有多条语句时,必须使用大括号和return关键字
let multiply = (a, b) => {
    let result = a * b;
    return result;
};

// 返回对象时,必须使用括号包裹对象
let createPerson = (name, age) => ({ name: name, age: age });

9.3 箭头函数的this

箭头函数没有自己的this,它继承父级作用域的this

// 普通函数的this
let person = {
    name: "张三",
    age: 18,
    sayHello: function() {
        console.log(`你好,我叫${this.name}。`); // this指向person对象
        
        // 普通函数的this指向全局对象或undefined(严格模式)
        setTimeout(function() {
            console.log(`我今年${this.age}岁。`); // this指向全局对象,age为undefined
        }, 1000);
    }
};

person.sayHello();

// 箭头函数的this
let person2 = {
    name: "李四",
    age: 20,
    sayHello: function() {
        console.log(`你好,我叫${this.name}。`); // this指向person2对象
        
        // 箭头函数的this继承父级作用域的this,即person2对象
        setTimeout(() => {
            console.log(`我今年${this.age}岁。`); // this指向person2对象,age为20
        }, 1000);
    }
};

person2.sayHello();

10. 常见问题解答

Q: 函数声明和函数表达式有什么区别?

A: 函数声明会被提升到作用域顶部,可以在声明之前调用;函数表达式不会被提升,只能在定义之后调用。

Q: 箭头函数和普通函数有什么区别?

A: 箭头函数语法简洁,没有自己的this,不能作为构造函数使用,没有arguments对象,没有prototype属性。

Q: 什么是高阶函数?

A: 高阶函数是指接受一个或多个函数作为参数,或者返回一个函数的函数。

Q: 什么是递归函数?

A: 递归函数是指调用自身的函数,用于解决可以分解为相同子问题的问题。

Q: 什么是IIFE?

A: IIFE(立即执行函数表达式)是指定义后立即执行的函数表达式,用于创建独立的作用域,避免变量污染。

Q: 如何显式指定函数的this值?

A: 可以使用call()、apply()和bind()方法显式指定函数的this值。

11. 练习项目

  1. 创建一个HTML文件,包含以下内容:

    • 一个输入框,用于输入数字
    • 一个按钮,用于计算阶乘
    • 一个显示结果的区域
  2. 使用JavaScript实现以下功能:

    • 定义一个递归函数,计算一个数的阶乘
    • 点击按钮时,获取输入框的值
    • 调用阶乘函数计算结果
    • 在结果区域显示计算结果
    • 处理可能的错误,如输入非数字或负数
  3. 测试不同的输入值,确保计算结果正确

12. 小结

  • 函数是一段可重复使用的代码块,用于执行特定的任务
  • JavaScript中有多种定义函数的方式:函数声明、函数表达式、箭头函数、Function构造函数
  • 函数可以接受参数,并返回一个值
  • 函数是对象,具有属性和方法
  • 箭头函数是ES6引入的新特性,语法简洁,没有自己的this
  • 高阶函数是指接受函数作为参数或返回函数的函数
  • 递归函数是指调用自身的函数
  • IIFE是指定义后立即执行的函数表达式
  • 函数的this值取决于调用方式

在下一章节中,我们将学习JavaScript函数参数的相关知识。

« 上一篇 JavaScript变量提升 下一篇 » JavaScript函数参数