Appearance
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引擎会按以下顺序查找:
- 当前作用域
- 外层作用域
- 直到全局作用域
- 如果未找到,抛出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