函数

函数

语法:

function 函数名(参数列表) {
    函数体;
    return 返回值;
};

示例:

function add(x, y) {
  return x + y;
};

console.log(add) // [Function: add],add就是标识符
console.log(add(3, 5)) // 8

示例:声明提升(实际应用中 还是要先定义后使用)

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

function add(x, y) {
  return x + y;
};

匿名函数

类似于python中的Lambda

语法:

// 标识符还可以使用 let、var 定义
const 标识符 =  function(参数列表) { // 不定义函数名,使用标识符与函数关联
    函数体;
    return 返回值;
};

示例:

const add = function(x, y) {
  return x + y;
};

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

函数表达式

语法:

// 标识符还可以使用 let、var 定义
const 标识符 =  function 函数名(参数列表) { // 该函数名只能用在函数内部
    函数体;
    return 返回值;
};

示例:

const add = function fn(x, y) {
  return x + y;
};

console.log(add(3, 5)) // 8
console.log(fn(3, 5)) // ReferenceError: fn is not defined,只能在函数内部使用

示例:实现递归计算n+n-1…

const sum = function _sum(n) {
  if (n === 1) return n;
  return n + _sum(--n);
};

console.log(sum(3)); // 6,即 3+2+1=6
console.log(sum(100)); // 5050

总结

函数、匿名函数、函数表达式的差异:

  • 函数、匿名函数、函数表达式,本质上都一样,都是函数对象。只不过函数有函数名作为标识符,匿名函数、函数表达式需要借助其他的标识符而已;
  • 函数会声明提升,匿名函数、函数表达式不会。(使用function定义函数,虽然会声明提升,但也要先定义后使用!)

高阶函数

函数作为参数或返回一个函数

示例:实现计数器

  • 返回一个函数
function counter() {
  let c = 0;
  return function () {
    return ++c;
  }
};

let inc = counter();

console.log(inc()); // 1
console.log(inc()); // 2
console.log(inc()); // 3

为什么第二次和第三次调用会依次递增?是因为闭包吗?

是的,第二次和第三次调用 inc() 依次递增是因为闭包的作用。闭包是一个函数,它在定义时捕获了其外部环境的变量,因此即使外部环境的作用域已经结束,闭包中的变量仍然可以保持并在后续调用中使用。

代码分析

在你的代码示例中,counter() 返回了一个函数,这个返回的函数可以访问 counter() 内部定义的变量 c,即使 counter() 已经执行完毕。

  1. 定义闭包 当你调用 counter() 时,函数内部定义了一个变量 c = 0,然后返回一个函数,这个函数可以访问 c。这形成了闭包,因为返回的函数保持了对 c 的引用。

  2. 返回闭包counter() 返回后,返回的函数仍然可以访问 c,因为在闭包中,内部函数保留了对外部作用域变量的引用。这意味着即使 counter() 的执行环境已经结束,c 仍然存在于闭包中。

  3. 闭包保留状态 当你调用 inc() 时,它会递增 c 的值,然后返回递增后的结果。因为闭包保留了 c 的状态,每次调用 inc()c 的值都会在上一次的基础上增加。

执行过程

  1. 首次调用 inc()inc() 被首次调用时,c 的值从 0 增加到 1,然后返回 1

  2. 再次调用 inc() 再次调用 inc() 时,闭包中的 c 仍然是 1,因此它增加到 2,然后返回 2

  3. 第三次调用 inc() 同样,闭包中的 c 现在是 2,因此它增加到 3,然后返回 3

闭包的重要性

闭包在 JavaScript 中非常重要,因为它允许函数保持其外部作用域的状态。通过闭包,您可以创建工厂函数、记忆化函数、封装数据、实现私有状态等。闭包提供了强大的抽象和灵活性,使代码更具可复用性和模块化。

因此,在你的代码示例中,第二次和第三次调用 inc() 会依次递增,是因为闭包保留了 c 的状态,并且每次调用都会对 c 进行递增操作。

示例:实现无限计数器

方法1

let counter = function * () {
    let c = 1;
    while (true) {
        yield c++;  // 产生当前的c,然后将c加1
    }
}

let gen = counter()

console.log(gen.next()) // { value: 1, done: false }
console.log(gen.next()) // { value: 2, done: false }
console.log(gen.next().value) // 3

在JavaScript中,function *中的星号(*)代表这是一个生成器函数(generator function)。生成器函数是一种特殊类型的函数,它可以在执行过程中暂停,然后可以在以后某个时间点继续执行。这是通过yield关键字实现的。

生成器函数的特征:

  • 暂停与恢复:生成器函数可以使用yield关键字暂停执行,并通过生成器对象的next()方法恢复。
  • 生成器对象:调用生成器函数会返回一个生成器对象,这个对象可以用next()方法来控制函数的执行。
  • Lazy Evaluation:生成器函数不会立即执行,而是在调用next()时执行,这种懒计算特性在处理大量数据时非常有用。

yield的作用:

  • yield可以把函数暂停并返回一个值给调用者。
  • 当生成器对象的next()方法被调用时,生成器函数继续从上次yield的位置开始执行。

在你的代码示例中,counter是一个生成器函数,它会一直生成递增的数字。你可以不断调用gen.next()来获取下一个值,并且不会有终止的明确点,因为循环中使用了while (true),它永远不会停止。

示例:实现map函数

不需要返回惰性,立刻返回一个新的数组(不要修改原有数组)

方法一

let arr = [1, 2, 3, 4, 5, 6]

function map(fn, iter) {
    let new_arr = [];
    for (let v of iter) {
        new_arr.push(fn(v))
    }

    return new_arr
}


function square(x) {
    return x * x
}

console.log(map(square, arr)) // [ 1, 4, 9, 16, 25, 36 ]

补充

高阶函数(Higher-Order Functions)是指可以接收函数作为参数或返回一个函数的函数。在 JavaScript 中,高阶函数是一个非常重要的概念,它可以实现强大的抽象、代码复用和函数式编程模式。让我们探讨高阶函数的两种主要形式:函数作为参数,以及函数作为返回值。

1. 函数作为参数

在这种情况下,高阶函数接收其他函数作为参数,并可能在执行过程中调用这些函数。常见的场景包括数组操作、事件处理、异步操作等。

示例:数组方法

JavaScript 提供了许多高阶函数用于数组操作,例如 mapfilterreduceforEach 等,这些方法接受一个函数作为参数。

const numbers = [1, 2, 3, 4, 5];

// 使用高阶函数 map,将每个元素平方
const squares = numbers.map(num => num * num);
console.log(squares); // [1, 4, 9, 16, 25]

// 使用高阶函数 filter,筛选偶数
const evens = numbers.filter(num => num % 2 === 0);
console.log(evens); // [2, 4]

在这些示例中,传入的函数用于定义数组操作的行为。

示例:事件处理

在事件驱动的编程中,高阶函数常用于处理事件。

document.getElementById('myButton').addEventListener('click', () => {
  console.log('Button clicked!');
});

这里,回调函数作为参数传递给 addEventListener,在事件发生时调用。

2. 函数作为返回值

高阶函数也可以返回一个函数,这种情况通常用于创建闭包、工厂函数、函数柯里化等。

示例:闭包

闭包是指内部函数可以访问其外部作用域的变量。通过返回一个函数,可以创建闭包。

function createCounter() {
  let count = 0;

  return function() {
    count++;
    return count;
  };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2

这里,createCounter 返回的函数是一个闭包,它能够访问 count 变量,即使在 createCounter 执行结束后仍然可以保留其状态。

示例:工厂函数

高阶函数可以用来创建特定行为的函数,通常用于工厂函数。

function createGreeter(greeting) {
  return function(name) {
    console.log(`${greeting}, ${name}!`);
  };
}

const sayHello = createGreeter('Hello');
sayHello('John'); // "Hello, John!"

在这个例子中,createGreeter 返回一个函数,并将 greeting 保持在闭包中。生成的函数可用于不同的场景。

高阶函数的应用

  • 函数式编程:高阶函数是函数式编程的重要组成部分,可以实现更高的抽象和代码复用。
  • 组件化和模块化:高阶函数在构建可重用组件时非常有用,尤其在现代前端框架中。
  • 异步编程:高阶函数常用于处理异步代码,例如回调、Promise、异步事件等。

总结

高阶函数是 JavaScript 的核心概念之一,它们可以接收函数作为参数,或者返回一个函数。这种特性使 JavaScript 能够实现高度抽象和代码复用,支持各种编程模式和应用场景。通过理解高阶函数的工作原理和应用场景,可以编写更强大、灵活和可维护的代码。

箭头函数

箭头函数是 JavaScript 中一种简洁的函数表示法,它在 ES6 中引入。与传统的函数表达式相比,箭头函数具有以下主要特点:

  1. 简洁的语法:箭头函数使用 => 作为函数的标志,比传统的函数表达式更简洁。

    // 传统函数表达式
    const add = function(a, b) {
      return a + b;
    };
    
    // 箭头函数
    const add = (a, b) => a + b;
  2. 隐式的 return:当箭头函数只有一个表达式时,返回值是自动隐式返回的。

    const square = x => x * x; // 隐式返回 x * x
    
  3. 省略括号:当箭头函数只有一个参数时,参数括号可以省略。

    const double = x => x * 2;
  4. 没有自己的 this:箭头函数不绑定 this,它会继承外部上下文的 this。这在处理回调函数时非常有用。

    const obj = {
      value: 42,
      getValue: function() {
        setTimeout(() => {
          console.log(this.value); // 42
        }, 1000);
      }
    };
    
    obj.getValue();

    传统函数表达式的 this 会根据调用环境的不同而变化,而箭头函数的 this 始终继承自它所在的外部上下文。

  5. 没有 arguments 对象:箭头函数不会生成 arguments 对象。如果需要访问参数列表,可以使用展开语法 (...)。

    const sum = (...args) => args.reduce((total, num) => total + num, 0);
    
    console.log(sum(1, 2, 3, 4)); // 10
    

以上是箭头函数的一些主要特点。因为它没有自己的 thisarguments,在某些场景下可能需要特别注意。如果需要使用传统的 this 行为或 arguments 对象,则应使用传统的函数表达式或函数声明。

补充说明

箭头函数参数

  • 没有参数,使用()
  • 只有一个参数,参数列表可省略()
  • 多个参数,不能省略小括号,多个参数间使用逗号间隔

箭头函数返回值

  • 如果函数体部分有多行,就需要使用{},如果有返回值需要使用return
  • 如果函数体部分只有一行,可同时省略{}return(如果有return语句,就不能省略大括号)
  • 如果只有一条非return语句,加上大括号,函数就成无返回值了(返回undefined

示例1

let arr = [1, 2, 3, 4, 5, 6]

function map(fn, iter) {
    let new_arr = [];
    for (let v of iter) {
        new_arr.push(fn(v))
    }

    return new_arr
}

console.log(map(function (x) {return x * x}, arr)) // [ 1, 4, 9, 16, 25, 36 ]
// 箭头函数,以下三行等价
console.log(map((x) => {return x * x}, arr)) // [ 1, 4, 9, 16, 25, 36 ]
// 当箭头函数只有一个参数时,参数括号可以省略。
console.log(map(x => {return x * x}, arr)) // [ 1, 4, 9, 16, 25, 36 ]
// 返回值只有return时,通常可以省略{},同时也要去掉return关键字
console.log(map(x => x * x, arr)) // [ 1, 4, 9, 16, 25, 36 ]

函数参数

JavaScript函数参数是指在定义和调用函数时传递的值。

普通参数

在 JavaScript 中,普通参数是指在函数定义中明确指定的参数。普通参数是基础,也是最常见的参数类型。它们具有以下特点:

  1. 定义与使用: 普通参数是在函数定义时使用的参数列表中的变量。当你调用函数时,你可以将具体的值传递给这些参数。

    function add(a, b) {
        return a + b;
    }
    console.log(add(2, 3)); // 输出: 5
    
  2. 顺序性: 在函数定义中,普通参数的顺序很重要。调用函数时,传递的实参需要按照参数的定义顺序传递。

    function divide(a, b) {
        return a / b;
    }
    console.log(divide(10, 2)); // 输出: 5
    console.log(divide(2, 10)); // 输出: 0.2
    
  3. 可选参数: 在 JavaScript 中,你可以在定义函数时不给参数赋默认值。这些参数在调用时可以选择传递或不传递。

    function greet(name) {
        console.log(`Hello, ${name || 'Guest'}`);
    }
    greet(); // 输出: Hello, Guest
    greet('Alice'); // 输出: Hello, Alice
    
  4. 默认值: 普通参数可以设置默认值,如果调用时没有传递对应的实参,这个默认值就会被使用。

    function multiply(a, b = 1) {
        return a * b;
    }
    console.log(multiply(5)); // 输出: 5
    console.log(multiply(5, 3)); // 输出: 15
    
  5. 传递表达式: 在调用函数时,可以将表达式或变量作为实参传递,而不仅仅是固定的值。

    const x = 3;
    const y = 2 + 1;
    console.log(add(x, y)); // 输出: 6
    

普通参数在定义函数时非常直观,它们在函数体内代表传递给函数的值,可以根据需求和设计选择性地传递或者赋予默认值。通过理解这些概念,可以灵活运用函数参数来编写更加灵活、可读性更强的代码。

可变参数

在 JavaScript 中,可变参数是指一个函数可以接受不固定数量的参数。这种功能可以通过“剩余参数(rest parameters)”语法实现。在函数定义时,你可以使用 ... 语法表示可变参数,从而允许一个函数接收任意数量的实参。可变参数的应用场景包括处理不确定数量的输入、实现累加功能、组合参数等。

以下是关于可变参数的详细介绍:

  1. 使用剩余参数: 当你想要创建一个函数,允许它接收不定数量的参数时,可以使用剩余参数语法。

    function sum(...numbers) {
        return numbers.reduce((acc, num) => acc + num, 0);
    }
    
    console.log(sum(1, 2, 3)); // 输出: 6
    console.log(sum(10, 20, 30, 40)); // 输出: 100
    
  2. 参数解构: 剩余参数会变成一个数组,因此你可以使用数组的方法来操作它。

    function joinStrings(separator, ...strings) {
        return strings.join(separator);
    }
    
    console.log(joinStrings('-', 'one', 'two', 'three')); // 输出: "one-two-three"
    
  3. 可变参数的顺序: 剩余参数必须在函数参数列表的末尾。如果你想要传递固定参数和可变参数,可以确保剩余参数是最后一个。

    function logWithTimestamp(timestamp, ...messages) {
        console.log(`[${timestamp}]`, ...messages);
    }
    
    logWithTimestamp('2024-01-01', 'Error:', 'Something went wrong'); // 输出: [2024-01-01] Error: Something went wrong
    
  4. 使用 arguments 对象(不推荐使用了): 在 ES6 之前,JavaScript 使用 arguments 对象来获取所有传递给函数的参数。它是一个类数组对象,但不完全是数组(例如,没有数组的很多方法)。

    function oldSum() {
        let total = 0;
        for (let i = 0; i < arguments.length; i++) {
            total += arguments[i];
        }
        return total;
    }
    
    console.log(oldSum(1, 2, 3, 4)); // 输出: 10
    

虽然 arguments 对象仍然可以使用,但在现代 JavaScript 中,剩余参数语法更加灵活和直观,因为它会返回一个真正的数组,并且可以与数组方法结合使用。

通过理解可变参数的概念,你可以在 JavaScript 中实现更加灵活和通用的函数,从而更好地处理不定数量的输入数据。

示例1

const sum = (...numbers) => {
    let x = 0;
    for (let n of numbers) {
        x += n;
    }
    return x;
}

console.log(sum(1,2,3)) // 6

参数解构

参数解构是指在函数参数中使用解构赋值,从对象或数组中提取特定的值。这种技巧可以简化函数的输入,并使函数的定义更加简洁和易于理解。在 JavaScript 中,参数解构可以应用于函数参数的定义和传递。

对象解构

对象解构允许你在函数参数中提取对象的属性,并且可以提供默认值。

function displayUserInfo({ name, age, city }) {
    console.log(`Name: ${name}, Age: ${age}, City: ${city}`);
}

const user = { name: 'Alice', age: 30, city: 'New York' };
displayUserInfo(user); // 输出: Name: Alice, Age: 30, City: New York

使用默认值

在对象解构中,可以为某些属性提供默认值,这样即使没有传递这些属性,函数也不会出错。

function displayUserInfo({ name, age, city = 'Unknown' }) {
    console.log(`Name: ${name}, Age: ${age}, City: ${city}`);
}

const user = { name: 'Alice', age: 30 };
displayUserInfo(user); // 输出: Name: Alice, Age: 30, City: Unknown

数组解构

数组解构允许你在函数参数中提取数组中的特定元素,并使用这些值。

function logCoordinates([x, y]) {
    console.log(`X: ${x}, Y: ${y}`);
}

const point = [10, 20];
logCoordinates(point); // 输出: X: 10, Y: 20

使用剩余参数

在数组解构中,你可以使用剩余参数来接收其余的元素。

function logFirstTwo([first, second, ...rest]) {
    console.log(`First: ${first}, Second: ${second}`);
    console.log(`Rest: ${rest}`);
}

const numbers = [1, 2, 3, 4, 5];
logFirstTwo(numbers); 
// 输出:
// First: 1, Second: 2
// Rest: 3, 4, 5

其他解构方式

const sum = (...numbers) => {
    let x = 0;
    for (let n of numbers) {
        x += n;
    }
    return x;
}

console.log(sum(...[10,20,30])) // 60,解构

这种方式使用了两个不同的特性:剩余参数展开语法。让我们详细解释一下它们的作用以及这种用法的含义。

  1. 剩余参数 (...numbers): 剩余参数允许函数接受可变数量的参数。在这个例子中,sum 函数的参数是 numbers,它将接收到的所有参数汇集成一个数组。这样你就可以在函数内使用 for-of 循环或其他数组操作来处理这些参数。

    const sum = (...numbers) => {
        let x = 0;
        for (let n of numbers) {
            x += n;
        }
        return x;
    }

    这个函数可以接收任意数量的参数,并返回它们的总和。例如:

    console.log(sum(1, 2, 3)); // 输出: 6
    console.log(sum(10, 20)); // 输出: 30
    
  2. 展开语法 (...[10, 20, 30]): 展开语法用于将数组或对象“展开”为独立的元素。在这个例子中,...[10, 20, 30] 将数组 [10, 20, 30] 展开成独立的参数,传递给 sum 函数。

    console.log(sum(...[10, 20, 30])); // 输出: 60
    

    展开语法非常有用,因为它允许你在函数调用中将数组作为一组独立的参数传递,而不需要手动展开。你可以使用展开语法来传递现有数组、合并数组、复制数组等。

结合剩余参数和展开语法

通过结合这两个特性,你可以实现灵活且动态的参数传递。例如,你可以创建一个包含一系列数字的数组,然后使用展开语法将它们作为参数传递给 sum 函数。

const numbers = [10, 20, 30];
console.log(sum(...numbers)); // 输出: 60

这种方式的好处是可以在函数参数数量不确定时提供灵活性,并且可以方便地操作数组或合并参数。它是 JavaScript 中非常有用的技巧,适用于各种场景。

解构的好处

  1. 清晰性:解构可以提高代码的可读性,因为它清楚地表明了函数期望的参数是什么。
  2. 默认值:可以为缺失的属性提供默认值,从而使代码更具健壮性。
  3. 灵活性:解构允许函数接受更复杂的参数结构,同时保持代码的简洁。

通过使用参数解构,你可以创建更加易读和灵活的函数定义,尤其是当函数需要从复杂的数据结构中提取值时。

注意事项

  • 与python不同,调用 Js 中的函数时,没有关键字传参,都是按位置传参,与变量名无关

    • const add1= (x, y=5) => x + y;
      console.log(add1(y=6)) // 11
      console.log(add1(a=3, b=4)) // 7
      console.log(add1()) // NaN
      console.log(add1(z=10, y=20, x=30)) // 30
      
      console.log('-----------')
      
      const add2 = (x=4, y) => x + y;
      console.log(add2(y=6)) // NaN
      console.log(add2(a=3, b=4)) // 7
      console.log(add2()) // NaN
      console.log(add2(z=10, y=20, x=30)) // 30
      

JavaScript函数参数是指在定义和调用函数时传递的值。以下是关于JavaScript函数参数的详细信息:

  1. 参数的定义: 在定义函数时,你可以在括号内列出参数,这些参数可以在函数体内被使用。可以定义任意数量的参数。

    function greet(name) {
        console.log(`Hello, ${name}!`);
    }
  2. 函数调用时传递参数: 当你调用函数时,可以传递实际的值(实参)给这些参数。

    greet("Alice"); // 输出: Hello, Alice!
    
  3. 可选参数和默认值: JavaScript允许给参数设置默认值。如果在调用时没有传递对应的实参,参数会使用默认值。

    function greet(name = "Guest") {
        console.log(`Hello, ${name}!`);
    }
    greet(); // 输出: Hello, Guest!
    
  4. 参数数量可变: 使用...rest语法可以接受任意数量的参数。这在处理不确定数量的参数时很有用。

    function sum(...numbers) {
        return numbers.reduce((total, n) => total + n, 0);
    }
    console.log(sum(1, 2, 3, 4)); // 输出: 10
    
  5. 函数参数是按值传递的: 在JavaScript中,原始类型(如字符串、数字、布尔值等)是按值传递的,而对象和数组是按引用传递的。这意味着对原始类型的修改不会影响原始值,而对对象和数组的修改会影响原始对象/数组。

    function changeValue(x) {
        x = 10;
    }
    let a = 5;
    changeValue(a);
    console.log(a); // 输出: 5
    
    function changeObject(obj) {
        obj.name = "Changed";
    }
    let user = { name: "Alice" };
    changeObject(user);
    console.log(user.name); // 输出: Changed
    
  6. 参数解构: 可以通过解构语法直接在函数参数中提取对象或数组的属性。

    function getFullName({ firstName, lastName }) {
        return `${firstName} ${lastName}`;
    }
    const user = { firstName: "John", lastName: "Doe" };
    console.log(getFullName(user)); // 输出: John Doe
    

通过理解这些JavaScript函数参数的概念和特性,你可以在开发中灵活运用这些技巧,从而提高代码的可读性和可维护性。

函数返回值

在 JavaScript 中,函数的返回值是函数执行后返回给调用者的结果。返回值可以是任何数据类型,如数字、字符串、布尔值、数组、对象、甚至是函数或其他复杂结构。理解函数返回值的概念对编写可维护和模块化的代码至关重要。

返回值的基本用法

要指定函数的返回值,使用 return 关键字。函数在执行到 return 语句时会立即返回指定的值,并停止进一步执行。

function add(a, b) {
    return a + b; // 返回 a 和 b 的和
}

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

多样化的返回值

函数的返回值可以是任何 JavaScript 类型,这使得它们在多种场景中非常灵活。

  • 返回原始值:可以返回数字、字符串、布尔值等。

    function isEven(number) {
        return number % 2 === 0;
    }
    
    console.log(isEven(4)); // 输出: true
    console.log(isEven(5)); // 输出: false
    
  • 返回数组:可以返回一个数组,允许函数返回多个值。

    function getCoordinates() {
        return [10, 20]; // 返回数组
    }
    
    const [x, y] = getCoordinates();
    console.log(`x: ${x}, y: ${y}`); // 输出: x: 10, y: 20
    
  • 返回对象:可以返回对象,这对于需要返回多个值或具有命名的值非常有用。

    function getUser() {
        return {
            name: 'Alice',
            age: 30
        };
    }
    
    const user = getUser();
    console.log(`Name: ${user.name}, Age: ${user.age}`); // 输出: Name: Alice, Age: 30
    
  • 返回函数:可以返回函数,支持更复杂的功能和逻辑。

    function createGreeter(name) {
        return function() {
            console.log(`Hello, ${name}!`);
        };
    }
    
    const greetAlice = createGreeter('Alice');
    greetAlice(); // 输出: Hello, Alice!
    

注意事项

  • 没有返回值的函数:如果函数没有 return 语句,或者 return 后面没有指定任何值,则函数返回 undefined

    function doNothing() {
        // 没有 return
    }
    
    console.log(doNothing()); // 输出: undefined
    
  • 函数可以随时返回:一旦函数执行到 return 语句,它就会立即返回,即使在函数中有更多代码。

    function checkNumber(number) {
        if (number < 0) {
            return 'Negative';
        }
        return 'Positive or Zero';
    }
    
    console.log(checkNumber(-1)); // 输出: Negative
    console.log(checkNumber(1)); // 输出: Positive or Zero
    

总结

理解函数的返回值对编写高质量代码至关重要。返回值允许你在函数间传递数据、处理复杂逻辑,并构建模块化的程序。通过合理使用返回值,你可以实现更加灵活和可维护的代码设计。

示例:返回多个值

const add = (x, y) => {return x, y}

console.log(add(3, 100)) // 100

之所以在你的代码中只返回了 y,是因为 return 后面的表达式包含逗号运算符,而该运算符返回最后一个表达式的值。

示例:分析返回值

a = (x = 5, y = 6, true);
console.log(a) // true,逗号运算符返回最后一个表达式的值。

b = (123, true, z = 'test');
console.log(b); // test,逗号运算符返回最后一个表达式的值。(表达式的值!值!)

function c() {
    return x = 5, y = 6, true, 'ok';
}

console.log(c()) // ok

所以,Js的函数返回值依然是单值

作用域

函数中变量的作用域

function test() {
    a = 100
    var b = 200 // var可以声明提升,但不可以突破函数作用域
    let c = 300 // let不可以声明提升,也不可以突破函数作用域,推荐使用!
}

test()

console.log(a)
console.log(b) // 全局作用域中不可见
console.log(c) // 全局作用域中不可见

块作用域中的变量

if (true) {
  a = 100
  var b = 200
  let c = 300
}

console.log(a)
console.log(b)
console.log(c) // 全局作用域中不可见

函数作用域是指在函数内部定义的变量、参数和其他资源的可访问范围。在 JavaScript 中,函数作用域对于理解变量的生命周期和可见性非常重要。下面是函数作用域的详细解释,包括作用域的定义、闭包、作用域链和变量提升等概念。

作用域的定义

作用域决定了在特定范围内可以访问哪些变量。函数作用域是指在函数内部定义的变量只能在该函数内访问,不能在函数外部访问。

function greet() {
    let message = 'Hello';
    console.log(message); // 在函数内部可以访问
}

greet(); // 输出: Hello
console.log(message); // 报错: message is not defined

闭包

闭包是指一个函数在其定义的作用域外被调用时,仍然可以访问其定义时的作用域中的变量。闭包在 JavaScript 中非常重要,特别是在异步编程和回调中。

function makeGreeter(name) {
    return function() {
        console.log(`Hello, ${name}`);
    };
}

const greetAlice = makeGreeter('Alice');
greetAlice(); // 输出: Hello, Alice

在这个例子中,makeGreeter 返回了一个内部函数,这个函数保持了对外部函数 makeGreeter 中变量 name 的引用,即使 makeGreeter 已经执行完毕。这个特性就是闭包。

作用域链

作用域链是指在访问变量时,JavaScript 会沿着作用域链向外查找,直到找到该变量为止。如果没有找到,最终会在全局作用域中查找。

let globalMessage = 'Global';

function outer() {
    let outerMessage = 'Outer';
    
    function inner() {
        let innerMessage = 'Inner';
        console.log(globalMessage); // 输出: Global
        console.log(outerMessage);  // 输出: Outer
        console.log(innerMessage);  // 输出: Inner
    }
    
    inner();
}

outer();

在这个例子中,inner 函数可以访问其外部作用域中的变量,包括 outerMessageglobalMessage,这是因为 JavaScript 在查找变量时会沿着作用域链向外查找。

变量提升

变量提升是指 JavaScript 中函数和变量的声明会被提升到作用域的顶部。这意味着你可以在函数体内提前使用变量,而不必担心顺序问题,但要注意使用 varletconst 的区别。

function hoistedExample() {
    console.log(hoistedVar); // 输出: undefined(因为变量已被提升,但未赋值)
    var hoistedVar = 'Hoisted';
    console.log(hoistedVar); // 输出: Hoisted
}

在这个例子中,var 声明的变量会被提升到函数作用域的顶部,但赋值在原始位置进行。这在函数作用域中需要特别小心,以避免变量的未定义和其他问题。

总结

函数作用域是 JavaScript 中变量可见性和生命周期的关键概念。了解作用域的运作方式、闭包、作用域链以及变量提升可以帮助你编写更稳健和可维护的代码。在函数设计和变量管理时,理解这些概念对于避免错误和实现正确的行为至关重要。