第二章: 函数与作用域:代码组织与执行上下文
第二章: 函数与作用域:代码组织与执行上下文
Prorise第二章: 函数与作用域:代码组织与执行上下文
摘要: 在第一章,我们掌握了 JavaScript 的基本构件。现在,我们将进入一个更核心的领域:函数与作用域。函数是组织可复用代码的基石,而作用域则是控制这些代码中变量访问权限的规则体系。本章将深入探讨函数声明与表达式的区别、闭包的强大威力、this
关键字的精髓,以及如何通过 call
, apply
, bind
精准控制函数的执行上下文。理解这些概念,是您从“会写代码”迈向“写好代码”的关键一步。
在本章中,我们将层层递进,揭开函数与作用域的神秘面纱:
- 首先,我们将从 函数基础 出发,辨析两种核心的函数定义方式及其差异。
- 接着,我们将深入 作用域与闭包,理解 JavaScript 是如何管理变量以及函数为何能“记住”其创建时的环境。
- 然后,我们将攻克 JS 中最重要也最易混淆的概念之一 ——
this
关键字,掌握其指向的四大核心规则。 - 紧接着,我们将学习
call
,apply
,bind
这三个强大的工具,学会如何随心所欲地改变this
的指向。 - 最后,我们将探讨几种 特殊的函数形式,尤其是改变了
this
规则的箭头函数。
2.1. 函数基础:代码复用的核心
您已经了解函数的基本概念,但 JavaScript 中定义函数的方式存在一个不易察觉的但至关重要的区别:函数声明与函数表达式。
定义方式
使用 function
关键字开头,后跟函数名。
1 | console.log(add(5, 10)); // 15 |
核心特性
- 提升:整个函数定义在代码执行前被提升到作用域顶部,可在声明前调用。
- 适用场景:全局/模块级工具函数、无需动态生成时首选。
定义方式
创建匿名函数并赋值给变量(常用 const
/let
)。
1 | try { |
核心特性
- 变量声明提升,赋值不提升:调用发生在赋值前会触发暂时性死区 (TDZ)。
- 适用场景:回调、按需动态定义、需要闭包或箭头函数时更灵活。
函数的参数
JavaScript 在函数参数处理上非常灵活,ES6 更是引入了便捷的默认值和 Rest 参数。
参数默认值: 为参数提供默认值,当调用函数时未传递该参数或传递了
undefined
时,该默认值会被使用。1
2
3
4
5function greet(name = "Guest", message = "Welcome") {
console.log(`${message}, ${name}!`);
}
greet("Prorise"); // Welcome, Prorise!
greet(); // Welcome, Guest!Rest 参数 (Rest Parameters): 使用
...
语法,可以将一个不定数量的参数表示为一个数组。这在需要处理可变数量参数的场景中非常有用。1
2
3
4
5
6function sum(...numbers) {
// 'numbers' 是一个包含了所有传入参数的真实数组
return numbers.reduce((total, current) => total + current, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(10, 20, 30, 40)); // 100Rest 参数必须是函数参数列表的最后一个参数。
2.2. 作用域与闭包:理解变量的生命周期与“记忆”
承上启下: 我们已经知道如何创建函数,但函数内部的变量是如何被访问和管理的?这就引出了 JavaScript 中两个最核心的概念:作用域和闭包。
作用域 (Scope)
作用域 是指在程序中定义变量的区域,它决定了变量的可见性和生命周期。在现代 JavaScript 中,主要有三种作用域:- 全局作用域 (Global Scope): 在所有函数和代码块之外定义的变量,拥有全局作用域,在代码的任何地方都可以访问。
- 函数作用域 (Function Scope): 在函数内部定义的变量,只能在该函数内部访问。
- 块级作用域 (Block Scope): (ES6 新增) 在
{}
代码块(如if
,for
,while
语句)内由let
和const
声明的变量,只在该代码块内部有效。
1 | // 1. 全局作用域 (Global Scope) |
闭包 (Closure)
痛点背景: 按照作用域规则,一个函数执行完毕后,其内部的局部变量应该被销毁和回收。但如果我们希望一个函数能“记住”它创建时所在的环境,即使它在其他地方被执行,应该怎么办?
1 | function createGreeter() { |
1
Hello, Prorise
解决方案: 闭包 就是解决这个问题的答案。闭包能使函数执行完后,其内部局部变量因被引用而不被销毁,从而记住创建时的环境。比如在一个函数内部返回另一个函数,内部函数就形成了闭包,可访问外部函数的局部变量。在 JavaScript 中,当一个函数返回另一个函数时,就创建了一个闭包。
1 | function createGreeter() { |
1
2
Hello, Prorise
Hello, Prorise
闭包的核心价值:
- 数据封装与私有变量: 闭包可以创建出只能通过特定函数访问的“私有”变量,是实现模块化和封装的基础。
- 状态保持: 让函数能够跨多次调用保持状态,例如计数器、缓存等。
2.3. this
关键字:解密上下文的指向
this
是 JavaScript 中最令人困惑的概念之一,但也是最重要的。与许多其他语言不同,JavaScript 中 this
的值并不取决于它在哪里被定义,而是取决于它在何处、以及如何被调用。this
指向的是函数的 执行上下文。
掌握 this
的关键在于理解以下四种绑定规则:
1. 默认绑定
当函数作为独立函数直接调用时(没有通过对象调用),this
会被绑定到全局对象。在浏览器中是 window
,在严格模式 ('use strict'
) 下是 undefined
。
1 | function showThis() { |
2. 隐式绑定
当函数作为对象的一个方法被调用时,this
会被绑定到调用该方法的那个对象。
1 | const user = { |
1
Hello, my name is Prorise
陷阱: 如果将方法赋给另一个变量再调用,隐式绑定会丢失,退化为默认绑定。
1 | const user = { |
3. new
绑定
当函数通过 new
关键字调用(作为构造函数)时,this
会被绑定到一个新创建的空对象上。
1 | function User(name) { |
1
Prorise
4. 显式绑定
我们可以通过 call()
, apply()
, 或 bind()
方法,强制指定函数执行时的 this
值。我们将在下一节详细探讨。
2.4. 核心原理:call
、apply
与 bind
的应用
上一节我们提到,隐式绑定可能会丢失,导致 this
指向非预期的对象。为了解决这个问题,JavaScript 提供了三种方法来显式地、强制地设置函数的 this
上下文。
作用
立即调用函数,并把 this
绑定到指定对象;其余参数 按逗号逐个传递。
记忆法call
→ C omma(逗号分隔参数)
1 | const person = { name: "Prorise" }; |
作用
与 call
相同,但参数以 数组(或类数组) 一次性传入。
记忆法apply
→ A rray(数组传参)
1 | const person = { name: "Prorise" }; |
作用
不立即调用,而是返回一个 永久绑定 this 的新函数;可预设部分参数(柯里化)。
记忆法bind
→ B ind and return(绑定并返回新函数)
1 | const person = { name: "Prorise" }; |
2.5. 特殊函数:IIFE, 箭头函数
除了常规函数,JavaScript 还有一些特殊的函数形式,它们在特定场景下非常有用。
IIFE (立即执行函数表达式)
IIFE (Immediately Invoked Function Expression) 是一个在定义时就立即执行的函数表达式。痛点背景: 在 ES6 出现块级作用域之前,为了避免在 for
循环等场景中创建的变量污染全局作用域,开发者们发明了 IIFE 来创建一个临时的函数作用域。
1 | (function() { |
1
函数内部作用域
虽然 let
和 const
的块级作用域让 IIFE 用于创建作用域的需求大大降低,但它在一些需要初始化且只执行一次的模块化代码中仍有应用。
箭头函数
ES6 引入的箭头函数提供了一种更简洁的函数写法,但它最重要的特性是 它没有自己的 this
绑定。
核心特性: 箭头函数会捕获其定义时所在上下文(作用域)的 this
值,并将其作为自己的 this
。这彻底解决了传统函数在回调中 this
指向丢失的问题。
痛点背景: 看一个传统的回调 this
丢失问题。
1 | const team = { |
1
2
Alice is on team undefined
Bob is on team undefined
解决方案: 使用箭头函数,this
会被自动绑定到 showMembers
方法的 this
,也就是 team
对象。
1 | const team = { |
1
2
Alice is on team Prorise Devs
Bob is on team Prorise Devs
2.6. 本章核心速查
核心速查总结
分类 | 关键项 | 核心描述 |
---|---|---|
函数定义 | 函数声明 | function name(){} ,存在函数提升,可在声明前调用。 |
函数表达式 | const name = function(){} ,不提升,更灵活。 | |
核心概念 | 闭包 (Closure) | 函数能“记住”并访问其定义时的作用域,用于封装和状态保持。 |
this 绑定 | 隐式绑定 | obj.method() ,this 指向 obj 。 |
显式绑定 | 使用 call , apply , bind 强制指定 this 。 | |
箭头函数 | 没有自己的 this ,继承外层作用域的 this 。 | |
this 控制 | call() / apply() | 立即执行 函数,区别在于参数是 逗号分隔 还是 数组。 |
bind() | 返回一个新函数,其 this 被永久绑定。 |