【JS基础-Day4】函数与作用域
📺 对应视频:P50-P63 | 🎯 核心目标:理解函数的本质、掌握各种函数定义方式、作用域规则以及IIFE
一、函数基础
1.1 为什么需要函数?
函数是代码复用的基本单元。把重复逻辑封装成函数,减少冗余,提高可维护性。
// 没有函数:重复代码
console.log(1 + 2)
console.log(3 + 4)
console.log(5 + 6)
// 有函数:一次定义,多次使用
function add(a, b) {
return a + b
}
console.log(add(1, 2)) // 3
console.log(add(3, 4)) // 7
console.log(add(5, 6)) // 11
1.2 函数声明(Function Declaration)
// 语法:function 函数名(参数列表) { 函数体 }
function greet(name) {
console.log(`你好,${name}!`)
}
// 调用函数
greet('张三') // 你好,张三!
greet('李四') // 你好,李四!
// ✅ 函数声明会被提升,可以在声明前调用
sayHi() // 正常执行
function sayHi() { console.log('Hi!') }
1.3 参数详解
function func(形参1, 形参2) { ... }
func(实参1, 实参2)
// 参数数量不匹配时
function sum(a, b) {
console.log(a, b)
return a + b
}
sum(1, 2, 3) // a=1, b=2,多余的参数被忽略
sum(1) // a=1, b=undefined,返回 NaN
// ES6 默认参数(解决参数缺失问题)
function sum(a, b = 0) {
return a + b
}
sum(1) // 1(b默认为0)
sum(1, 2) // 3(传了实参,覆盖默认值)
arguments 对象(函数内隐式存在):
function test() {
console.log(arguments) // Arguments [1, 2, 3](类数组)
console.log(arguments.length) // 3
console.log(arguments[0]) // 1
}
test(1, 2, 3)
// 注意:箭头函数没有 arguments!
1.4 返回值
function max(a, b) {
return a > b ? a : b // 用 return 返回结果
}
let result = max(3, 7) // 7
// return 的特点:
// 1. return 后面的代码不执行
// 2. return 不写(或只写 return),函数返回 undefined
// 3. 函数只能 return 一个值(可以 return 对象/数组来绕过)
function getInfo() {
return { name: '张三', age: 18 } // 返回对象
}
function getCoords() {
return [x, y] // 返回数组
}
二、函数的多种写法
2.1 函数表达式(Function Expression)
// 将函数赋值给变量
const greet = function(name) {
return `你好,${name}`
}
greet('张三')
// ⚠️ 函数表达式不会提升!
sayBye() // ❌ TypeError,sayBye 此时是 undefined
const sayBye = function() { console.log('Bye') }
2.2 匿名函数
// 函数没有名字,常用于:
// 1. 赋值给变量(函数表达式)
const fn = function() { ... }
// 2. 作为回调函数传入
setTimeout(function() {
console.log('1秒后执行')
}, 1000)
[1, 2, 3].forEach(function(item) {
console.log(item)
})
2.3 箭头函数(Arrow Function,ES6)
// 基本语法
const add = (a, b) => a + b
// 等价于:
const add = function(a, b) { return a + b }
// 语法简化规则:
// 1. 只有一个参数,可省略括号
const double = x => x * 2
// 2. 函数体只有一行 return,可省略大括号和 return
const add = (a, b) => a + b
// 3. 返回对象字面量,需加括号(避免{}歧义)
const getObj = () => ({ name: '张三', age: 18 })
// 4. 多行函数体,正常写法
const greet = (name) => {
const msg = `你好,${name}`
return msg
}
箭头函数的重要特性(Day1进阶会详解):
- 没有自己的
this,继承外层this - 没有
arguments对象 - 不能作为构造函数(不能 new)
2.4 IIFE(立即执行函数)
// Immediately Invoked Function Expression
// 定义后立即执行,创建独立作用域
;(function() {
let name = '张三' // 局部变量,外部无法访问
console.log('立即执行!')
})()
// 箭头函数版 IIFE
;(() => {
console.log('也是立即执行!')
})()
// 带参数的 IIFE
;(function(a, b) {
console.log(a + b)
})(1, 2) // 3
IIFE 的用途:
// 1. 避免全局变量污染
;(function() {
let count = 0 // 只在 IIFE 内部存在
// 做一些初始化
})()
// 2. 模块模式(现代开发已用 ES Module 替代)
const counter = (function() {
let _count = 0
return {
increment() { _count++ },
getCount() { return _count }
}
})()
三、作用域
3.1 什么是作用域?
作用域是变量可以被访问的范围,控制变量的可见性和生命周期。
JS 作用域类型
├── 全局作用域(Global Scope)
├── 函数作用域(Function Scope)
└── 块级作用域(Block Scope)← ES6新增(let/const)
3.2 全局作用域
// 在最外层声明的变量,任何地方都能访问
let globalVar = '我是全局变量'
function test() {
console.log(globalVar) // ✅ 可以访问
}
// ⚠️ 不用 var/let/const 声明,会直接成为全局变量(危险!)
function bad() {
oops = '意外的全局变量' // 严格模式下报错
}
3.3 函数作用域(局部作用域)
function outer() {
let localVar = '局部变量'
console.log(localVar) // ✅ 函数内可访问
}
console.log(localVar) // ❌ ReferenceError,外部无法访问
3.4 块级作用域(let/const)
{
let blockVar = '块级变量'
const PI = 3.14
var oldVar = '老方式' // var 无块级作用域!
}
console.log(blockVar) // ❌ ReferenceError
console.log(PI) // ❌ ReferenceError
console.log(oldVar) // ✅ '老方式'(var 泄露出来了)
3.5 作用域链
当访问一个变量时,JS 会从当前作用域开始,逐层向外查找,直到全局作用域。
let x = 'global'
function outer() {
let x = 'outer'
function inner() {
let x = 'inner'
console.log(x) // 'inner'(找到了,停止查找)
}
function inner2() {
// 没有 x 声明
console.log(x) // 'outer'(向外找,找到 outer 的 x)
}
inner()
inner2()
}
outer()
作用域链图示:
inner 作用域 → outer 作用域 → 全局作用域 → 找不到 → ReferenceError
四、变量提升(Hoisting)
// var 声明提升(只提升声明,不提升赋值)
console.log(a) // undefined(不报错!)
var a = 10
// 等价于:
var a
console.log(a) // undefined
a = 10
// let/const 存在"暂时性死区(TDZ)"
console.log(b) // ❌ ReferenceError(死区内不可访问)
let b = 20
// 函数声明整体提升
greet() // ✅ 正常执行
function greet() { console.log('Hi') }
// 函数表达式不提升
sayBye() // ❌ TypeError
var sayBye = function() { console.log('Bye') }
五、逻辑中断与短路在函数中的应用
// 参数校验
function greet(name) {
name = name || '游客' // 旧写法:||
name = name ?? '游客' // 新写法:??(只处理 null/undefined)
console.log(`你好,${name}`)
}
// 更现代的写法
function greet(name = '游客') {
console.log(`你好,${name}`)
}
// 防止错误的链式调用
const user = null
// const name = user.profile.name // ❌ TypeError
const name = user?.profile?.name // ✅ undefined(可选链,ES2020)
六、函数的高级概念(预览)
6.1 纯函数(Pure Function)
// 纯函数:相同输入永远得到相同输出,无副作用
function add(a, b) { return a + b } // ✅ 纯函数
// 非纯函数:有副作用(修改外部状态)
let count = 0
function increment() { count++ } // ❌ 有副作用
6.2 函数作为一等公民
// JS 中函数可以:
// 1. 赋值给变量
const fn = function() {}
// 2. 作为参数传递(回调函数)
setTimeout(fn, 1000)
// 3. 作为返回值(高阶函数)
function makeAdder(x) {
return function(y) { return x + y }
}
const add5 = makeAdder(5)
console.log(add5(3)) // 8
七、知识图谱
函数与作用域
├── 函数
│ ├── 声明:function 关键字(会提升)
│ ├── 表达式:赋值给变量(不会提升)
│ ├── 匿名函数
│ ├── 箭头函数(ES6,无 this/arguments)
│ ├── IIFE(立即执行,独立作用域)
│ ├── 参数:形参、实参、默认参数、arguments
│ └── 返回值:return(只能返回一个值)
└── 作用域
├── 全局作用域
├── 函数作用域(局部)
├── 块级作用域(let/const)
├── 作用域链(从内到外查找变量)
└── 变量提升(var提升声明,let/const暂时性死区)
八、高频面试题
Q1:函数声明和函数表达式有什么区别?
函数声明会被整体提升到作用域顶部,可以在声明前调用;函数表达式不会提升,且变量在赋值前是
undefined(var)或暂时性死区(let/const)。
Q2:箭头函数和普通函数的主要区别?
① 没有自己的
this,继承外层上下文的this;② 没有arguments对象;③ 不能作为构造函数(不能new);④ 没有prototype属性;⑤ 语法更简洁。
Q3:什么是作用域链?
当访问变量时,JS 引擎先在当前作用域查找,找不到就往外层作用域找,一直到全局作用域。这种层层嵌套的查找链路叫作用域链。
Q4:IIFE 有什么用?
① 立即执行并创建独立作用域,防止变量污染全局;② 实现模块模式;③ 历史上用于模拟块级作用域(ES6 之前)。
⬅️ 上一篇:Day3 - 循环与数组 ➡️ 下一篇:Day5 - 对象与内置对象