JavaScript 教程

纯干货教学,从零开始学习 JavaScript

JavaScript 函数

函数是 JavaScript 中的核心概念,它允许我们将代码组织成可重用的块。函数可以接受参数,执行特定的任务,并返回结果。

🎯 函数的重要性

函数是 JavaScript 编程的基础,它使代码更加模块化、可维护和可重用。掌握函数的使用方法是成为一名优秀 JavaScript 开发者的关键。

什么是函数?

函数是一段封装了特定功能的代码块,它可以被多次调用,而不需要重复编写相同的代码。

函数的基本结构

JavaScript 函数的基本结构如下:

// 函数声明
function functionName(parameters) {
    // 函数体
    // 执行的代码
    return result; // 可选的返回值
}

// 函数调用
functionName(arguments);

函数声明

函数声明是定义函数的最基本方式,使用 function 关键字:

// 声明一个简单的函数
function sayHello() {
    console.log("Hello, JavaScript!");
}

// 调用函数
sayHello(); // 输出: Hello, JavaScript!

// 声明带参数的函数
function greet(name) {
    console.log(`Hello, ${name}!`);
}

// 调用带参数的函数
greet("张三"); // 输出: Hello, 张三!

// 声明带返回值的函数
function add(a, b) {
    return a + b;
}

// 调用带返回值的函数
let result = add(5, 3);
console.log(result); // 输出: 8

函数表达式

函数表达式是将函数赋值给变量的方式:

// 函数表达式
const sayHello = function() {
    console.log("Hello, JavaScript!");
};

// 调用函数
sayHello(); // 输出: Hello, JavaScript!

// 带参数的函数表达式
const greet = function(name) {
    console.log(`Hello, ${name}!`);
};

// 调用函数
greet("李四"); // 输出: Hello, 李四!

// 带返回值的函数表达式
const add = function(a, b) {
    return a + b;
};

// 调用函数
let result = add(10, 5);
console.log(result); // 输出: 15

箭头函数

箭头函数是 ES6 引入的一种更简洁的函数语法:

// 基本箭头函数
const sayHello = () => {
    console.log("Hello, JavaScript!");
};

// 调用函数
sayHello(); // 输出: Hello, JavaScript!

// 带参数的箭头函数
const greet = (name) => {
    console.log(`Hello, ${name}!`);
};

// 调用函数
greet("王五"); // 输出: Hello, 王五!

// 单个参数可以省略括号
const greetSingle = name => {
    console.log(`Hello, ${name}!`);
};

// 调用函数
greetSingle("赵六"); // 输出: Hello, 赵六!

// 带返回值的箭头函数
const add = (a, b) => {
    return a + b;
};

// 调用函数
let result = add(15, 7);
console.log(result); // 输出: 22

// 简写形式(当函数体只有一条 return 语句时)
const multiply = (a, b) => a * b;

// 调用函数
let product = multiply(4, 6);
console.log(product); // 输出: 24

函数参数

JavaScript 函数可以接受任意数量的参数,包括默认参数:

// 基本参数
function greet(name, message) {
    console.log(`${message}, ${name}!`);
}

// 调用函数
greet("张三", "你好"); // 输出: 你好, 张三!

// 默认参数(ES6+)
function greetWithDefault(name, message = "Hello") {
    console.log(`${message}, ${name}!`);
}

// 调用函数(使用默认参数)
greetWithDefault("李四"); // 输出: Hello, 李四!

// 调用函数(覆盖默认参数)
greetWithDefault("王五", "早上好"); // 输出: 早上好, 王五!

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

// 调用函数
let total = sum(1, 2, 3, 4, 5);
console.log(total); // 输出: 15

函数返回值

函数可以使用 return 语句返回一个值:

// 返回基本类型值
function add(a, b) {
    return a + b;
}

let result = add(5, 3);
console.log(result); // 输出: 8

// 返回对象
function createPerson(name, age) {
    return {
        name: name,
        age: age,
        greet: function() {
            return `你好,我是 ${this.name},今年 ${this.age} 岁。`;
        }
    };
}

let person = createPerson("张三", 25);
console.log(person.name); // 输出: 张三
console.log(person.greet()); // 输出: 你好,我是 张三,今年 25 岁。

// 返回函数(闭包)
function createMultiplier(factor) {
    return function(number) {
        return number * factor;
    };
}

let double = createMultiplier(2);
console.log(double(5)); // 输出: 10

let triple = createMultiplier(3);
console.log(triple(5)); // 输出: 15

// 提前返回
function divide(a, b) {
    if (b === 0) {
        return "除数不能为零";
    }
    return a / b;
}

console.log(divide(10, 2)); // 输出: 5
console.log(divide(10, 0)); // 输出: 除数不能为零

函数作用域

函数作用域是指变量在函数内部声明时,只能在函数内部访问:

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

function testScope() {
    // 局部变量
    let localVar = "局部变量";
    console.log(globalVar); // 可以访问全局变量
    console.log(localVar); // 可以访问局部变量
}

testScope(); // 输出: 全局变量, 局部变量
console.log(globalVar); // 可以访问全局变量
// console.log(localVar); // 错误: localVar is not defined

// 块级作用域(ES6+)
function testBlockScope() {
    if (true) {
        let blockVar = "块级变量";
        var functionVar = "函数变量";
    }
    // console.log(blockVar); // 错误: blockVar is not defined
    console.log(functionVar); // 可以访问,因为 var 是函数作用域
}

testBlockScope(); // 输出: 函数变量

闭包

闭包是指函数能够访问其外部作用域中的变量,即使外部函数已经执行完毕:

function createCounter() {
    let count = 0;
    
    return {
        increment: function() {
            count++;
            return count;
        },
        decrement: function() {
            count--;
            return count;
        },
        getCount: function() {
            return count;
        }
    };
}

let counter = createCounter();
console.log(counter.increment()); // 输出: 1
console.log(counter.increment()); // 输出: 2
console.log(counter.decrement()); // 输出: 1
console.log(counter.getCount()); // 输出: 1

// 另一个闭包示例
function outerFunction(outerVar) {
    return function innerFunction(innerVar) {
        console.log(`外部变量: ${outerVar}`);
        console.log(`内部变量: ${innerVar}`);
    };
}

let innerFunc = outerFunction("外部值");
innerFunc("内部值"); // 输出: 外部变量: 外部值, 内部变量: 内部值

递归函数

递归函数是指函数调用自身的函数:

// 计算阶乘
function factorial(n) {
    if (n === 0 || 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

// 递归遍历目录(简化示例)
function traverseDirectory(directory, level = 0) {
    console.log(" ".repeat(level) + directory.name);
    if (directory.children) {
        directory.children.forEach(child => {
            traverseDirectory(child, level + 1);
        });
    }
}

// 测试数据
const rootDir = {
    name: "根目录",
    children: [
        {
            name: "文件夹1",
            children: [
                { name: "文件1.txt" },
                { name: "文件2.txt" }
            ]
        },
        {
            name: "文件夹2",
            children: [
                { name: "文件3.txt" }
            ]
        },
        { name: "文件4.txt" }
    ]
};

traverseDirectory(rootDir);

函数方法

JavaScript 提供了一些内置的函数方法:

// call() 方法:调用函数,指定 this 值和参数
function greet() {
    console.log(`Hello, ${this.name}!`);
}

const person = { name: "张三" };
greet.call(person); // 输出: Hello, 张三!

// apply() 方法:调用函数,指定 this 值和参数数组
function sum(a, b) {
    return a + b;
}

const numbers = [5, 3];
console.log(sum.apply(null, numbers)); // 输出: 8

// bind() 方法:创建一个新函数,绑定 this 值和初始参数
function greet(greeting) {
    console.log(`${greeting}, ${this.name}!`);
}

const person2 = { name: "李四" };
const greetPerson = greet.bind(person2, "你好");
greetPerson(); // 输出: 你好, 李四!

// 箭头函数与 this
const obj = {
    name: "王五",
    regularFunction: function() {
        console.log(this.name); // 指向 obj
    },
    arrowFunction: () => {
        console.log(this.name); // 指向全局对象
    }
};

obj.regularFunction(); // 输出: 王五
obj.arrowFunction(); // 输出: undefined(在浏览器中)

匿名函数

匿名函数是没有名称的函数,通常用于回调函数或立即执行函数表达式:

// 作为回调函数
setTimeout(function() {
    console.log("2 秒后执行");
}, 2000);

// 作为数组方法的回调
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(function(num) {
    return num * 2;
});
console.log(doubled); // 输出: [2, 4, 6, 8, 10]

// 立即执行函数表达式 (IIFE)
(function() {
    console.log("立即执行");
})();

// 带参数的 IIFE
(function(name) {
    console.log(`Hello, ${name}!`);
})("赵六"); // 输出: Hello, 赵六!

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

生成器函数

生成器函数是 ES6 引入的一种特殊函数,可以暂停执行并在需要时恢复:

// 生成器函数
function* generateNumbers() {
    yield 1;
    yield 2;
    yield 3;
    yield 4;
    yield 5;
}

// 创建生成器对象
const generator = generateNumbers();

// 逐步获取值
console.log(generator.next().value); // 输出: 1
console.log(generator.next().value); // 输出: 2
console.log(generator.next().value); // 输出: 3
console.log(generator.next().value); // 输出: 4
console.log(generator.next().value); // 输出: 5
console.log(generator.next().value); // 输出: undefined

// 生成器函数示例:无限序列
function* infiniteSequence() {
    let i = 0;
    while (true) {
        yield i++;
    }
}

const infiniteGen = infiniteSequence();
console.log(infiniteGen.next().value); // 输出: 0
console.log(infiniteGen.next().value); // 输出: 1
console.log(infiniteGen.next().value); // 输出: 2

// 生成器函数示例:遍历对象
function* iterateObject(obj) {
    for (const key in obj) {
        if (obj.hasOwnProperty(key)) {
            yield [key, obj[key]];
        }
    }
}

const person = { name: "张三", age: 25, job: "工程师" };
for (const [key, value] of iterateObject(person)) {
    console.log(`${key}: ${value}`);
}

函数最佳实践

  • 函数名称应该清晰明了:函数名称应该准确描述函数的功能
  • 函数应该单一职责:一个函数应该只做一件事情
  • 函数参数应该合理:避免过多参数,可以使用对象来组织参数
  • 使用默认参数:为可选参数提供默认值
  • 使用箭头函数简化代码:对于简单的函数,使用箭头函数语法
  • 避免全局变量:在函数内部使用局部变量
  • 添加适当的注释:解释函数的功能、参数和返回值
  • 处理错误情况:检查参数有效性,处理可能的错误
  • 使用递归时注意栈溢出:对于大问题,考虑使用迭代替代递归
  • 优化性能:避免在循环中定义函数,使用适当的缓存策略

函数常见问题

  • 问题 1: 函数未定义
  • 解决方案:确保函数在调用前定义,注意函数声明提升和函数表达式的区别。

  • 问题 2: this 指向错误
  • 解决方案:了解 this 的绑定规则,使用 bind()、call() 或 apply() 方法,或使用箭头函数。

  • 问题 3: 参数传递错误
  • 解决方案:检查参数数量和类型,使用默认参数和剩余参数。

  • 问题 4: 递归栈溢出
  • 解决方案:设置递归终止条件,对于大问题使用迭代替代递归。

  • 问题 5: 闭包内存泄漏
  • 解决方案:避免在循环中创建闭包,及时释放不再需要的引用。

📝 学习检查

通过本章节的学习,你应该了解:

  • 函数的基本概念和定义方式
  • 函数参数和返回值的使用
  • 函数表达式和箭头函数的语法
  • 函数作用域和闭包的概念
  • 递归函数和生成器函数的使用
  • 函数的最佳实践和常见问题