Skip to content
On this page

JavaScript 函数与作用域

函数是JavaScript中的一等公民,是组织和复用代码的基本单元。理解函数的各个方面对于掌握JavaScript至关重要。

函数定义方式

JavaScript提供了多种定义函数的方式,每种方式都有其特点和适用场景。

函数声明(Function Declaration)

  • 存在函数提升(hoisting)
  • 在执行上下文的创建阶段就被定义
javascript
function greet(name) {
  return `Hello, ${name}!`;
}

// 函数声明可以在声明之前调用
console.log(greet('John')); // 'Hello, John!'

函数表达式(Function Expression)

  • 不会被提升,只能在定义后调用
  • 可以是匿名函数或命名函数
javascript
// 匿名函数表达式
const greet = function(name) {
  return `Hello, ${name}!`;
};

// 命名函数表达式(主要用于递归和调试)
const factorial = function fact(n) {
  if (n <= 1) return 1;
  return n * fact(n - 1); // 使用函数名进行递归
};

箭头函数(Arrow Function,ES6)

  • 没有自己this、arguments、super、new.target
  • 不能用作构造函数
  • 不能用作Generator函数
javascript
// 基本语法
const greet = (name) => `Hello, ${name}!`;

// 单个参数可省略括号
const square = x => x * x;

// 无参数需空括号
const getRandom = () => Math.random();

// 多行函数体需大括号和显式return
const add = (a, b) => {
  const result = a + b;
  return result;
};

// 返回对象字面量需用括号包裹
const createUser = (name, age) => ({ name, age });

函数参数

参数默认值(ES6)

javascript
function greet(name = 'World', punctuation = '!') {
  return `Hello, ${name}${punctuation}`;
}

console.log(greet()); // 'Hello, World!'
console.log(greet('John')); // 'Hello, John!'
console.log(greet('John', '?')); // 'Hello, John?'

剩余参数(Rest Parameters,ES6)

javascript
function sum(...numbers) {
  return numbers.reduce((total, num) => total + num, 0);
}

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

参数解构

javascript
function createUser({ name, age, email = 'unknown@example.com' }) {
  return { name, age, email };
}

const user = createUser({ name: 'John', age: 30 });
// { name: 'John', age: 30, email: 'unknown@example.com' }

作用域

作用域决定了变量和函数的可访问范围。

全局作用域

  • 在所有函数之外定义的变量
  • 在浏览器中,全局作用域的变量成为window对象的属性
javascript
var globalVar = 'I am global';
function checkScope() {
  console.log(globalVar); // 'I am global'
}

函数作用域

  • 在函数内部声明的变量只在该函数内可访问
javascript
function outer() {
  var functionScoped = 'I am function scoped';
  
  function inner() {
    console.log(functionScoped); // 'I am function scoped'
  }
  
  inner();
}

块级作用域(ES6)

  • 用let和const声明的变量具有块级作用域
  • 由{}包围的代码块(如if、for、while等)
javascript
if (true) {
  let blockScoped = 'I am block scoped';
  const constant = 'I am constant';
  
  console.log(blockScoped); // 'I am block scoped'
}
// console.log(blockScoped); // ReferenceError: blockScoped is not defined

词法作用域(静态作用域)

  • 函数在定义时确定作用域,而非调用时
javascript
var x = 1;

function outer() {
  var x = 2;
  
  function inner() {
    console.log(x); // 2,而非1,因为函数在定义时确定作用域
  }
  
  return inner;
}

var innerFunc = outer();
innerFunc(); // 2

作用域链

当访问一个变量时,JavaScript引擎会按以下顺序查找:

  1. 当前作用域
  2. 外层作用域
  3. 直到全局作用域
  4. 如果未找到,抛出ReferenceError
javascript
var a = 'global a';

function outer() {
  var b = 'outer b';
  
  function inner() {
    var c = 'inner c';
    console.log(a); // 'global a' - 从全局作用域获取
    console.log(b); // 'outer b' - 从外层作用域获取
    console.log(c); // 'inner c' - 从当前作用域获取
  }
  
  inner();
}

闭包

闭包是指函数能够访问其外部作用域中的变量,即使在外部函数执行完毕后依然可以访问。

闭包的创建

javascript
function outer(x) {
  return function inner(y) {
    return x + y; // inner函数"闭包"了x变量
  };
}

const add5 = outer(5);
console.log(add5(3)); // 8 - 即使outer执行完毕,x仍然可访问

闭包的常见用途

1. 数据封装和私有变量

javascript
function createCounter() {
  let count = 0; // 私有变量
  
  return {
    increment: function() {
      count++;
      return count;
    },
    decrement: function() {
      count--;
      return count;
    },
    getCount: function() {
      return count;
    }
  };
}

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

2. 函数工厂

javascript
function createMultiplier(multiplier) {
  return function(number) {
    return number * multiplier;
  };
}

const double = createMultiplier(2);
const triple = createMultiplier(3);

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

3. 循环中的闭包问题

javascript
// 常见错误
for (var i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i); // 输出 3, 3, 3
  }, 100);
}

// 解决方案1:使用let
for (let i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i); // 输出 0, 1, 2
  }, 100);
}

// 解决方案2:使用IIFE(立即执行函数表达式)
for (var i = 0; i < 3; i++) {
  (function(index) {
    setTimeout(() => {
      console.log(index); // 输出 0, 1, 2
    }, 100);
  })(i);
}

高阶函数

高阶函数是指接受函数作为参数或返回函数的函数。

接受函数作为参数

javascript
// 数组方法是高阶函数的典型例子
const numbers = [1, 2, 3, 4, 5];

// map - 转换数组元素
const doubled = numbers.map(n => n * 2); // [2, 4, 6, 8, 10]

// filter - 过滤数组元素
const evens = numbers.filter(n => n % 2 === 0); // [2, 4]

// reduce - 归约操作
const sum = numbers.reduce((acc, curr) => acc + curr, 0); // 15

// 自定义高阶函数
function executeWithLogging(fn, ...args) {
  console.log('Function called with:', args);
  const result = fn(...args);
  console.log('Function result:', result);
  return result;
}

返回函数

javascript
// 柯里化(Currying)
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      return function (...args2) {
        return curried.apply(this, args.concat(args2));
      };
    }
  };
}

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

const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6

this 绑定

this的值取决于函数的调用方式:

默认绑定

javascript
function foo() {
  console.log(this); // 在浏览器中是window,在严格模式下是undefined
}

隐式绑定

javascript
const obj = {
  name: 'Alice',
  greet: function() {
    console.log(`Hello, ${this.name}`); // 'Hello, Alice'
  }
};

obj.greet(); // this指向obj

显式绑定

javascript
function greet() {
  console.log(`Hello, ${this.name}`);
}

const person = { name: 'Bob' };
greet.call(person);  // 'Hello, Bob'
greet.apply(person); // 'Hello, Bob'
greet.bind(person)(); // 'Hello, Bob'

new 绑定

javascript
function Person(name) {
  this.name = name;
}

const john = new Person('John');
console.log(john.name); // 'John'

箭头函数的this

javascript
const obj = {
  name: 'Alice',
  regularFunction: function() {
    console.log(this.name); // 'Alice'
    
    const arrowFunction = () => {
      console.log(this.name); // 'Alice',继承外层this
    };
    
    arrowFunction();
  }
};

函数属性和方法

函数也是对象,具有以下属性和方法:

javascript
function example(a, b) {
  return a + b;
}

console.log(example.length); // 2 - 形参个数
console.log(example.name);   // 'example' - 函数名

// toString() - 返回函数源码
console.log(example.toString());

// apply, call, bind - 改变this指向
const obj = { x: 10 };
example.call(obj, 5, 3); // 8