JavaScript 函数总览
JS 函数的三层身份(可调用代码块、对象、作用域边界)、定义方式、参数系统、this、闭包、高阶函数、特殊函数。
[!info] related notes
JavaScript 函数总览
函数的三层身份
JS 函数不是”特殊语法糖”,它同时是:
- 可执行的代码块 — 能被调用
- 一种对象 — 能赋值、传参、返回、挂属性
- 一种作用域边界 — 形成局部变量、闭包、词法环境
function fn(a, b) { return a + b }
typeof fn // "function"
fn(1, 2) // 3
fn.x = 100 // 可以挂属性
fn.x // 100
一、定义方式
函数声明
function add(x, y) { return x + y }
- 有函数名
- 会提升(hoist):声明前可调用
typeof add === "function"
函数表达式
const add = function(x, y) { return x + y }
- 变量受
var/let/const规则控制 - 函数本身不整体提升到可调用状态
- 可匿名,也可具名
匿名函数表达式
const f = function() {}
具名函数表达式
const c = function d() {
console.log(typeof d) // "function"(内部可见)
}
console.log(typeof c) // "function"
console.log(typeof d) // "undefined"(外部不可见)
用途:
- 递归更稳定(内部名不依赖外部变量)
- 调试堆栈信息更清楚
箭头函数
const add = (a, b) => a + b
const square = x => x * x
const getObj = () => ({ x: 1 })
详见下方「箭头函数」章节。
构造器方式
const add = new Function('x', 'y', 'return x + y')
- 运行时动态拼代码
- 性能和安全性差,默认全局作用域
- 工程中极少使用
二、提升(Hoisting)
| 定义方式 | 提升行为 |
|---|---|
| 函数声明 | 整体提升,声明前可调用 |
var 函数表达式 | 只提升变量声明(var foo),值为 undefined |
let / const 函数表达式 | 暂时性死区(TDZ),声明前访问报 ReferenceError |
箭头函数(const) | 同 let / const,有 TDZ |
foo() // ok
function foo() {}
// bar() // TypeError: bar is not a function
var bar = function() {}
// baz() // ReferenceError: Cannot access 'baz' before initialization
const baz = function() {}
三、参数系统
详见 arguments-object。
命名参数
function test(a, b) { console.log(a, b) }
test(1) // 1, undefined(少传)
test(1, 2, 3) // 1, 2(多传忽略)
JS 不强制参数个数严格一致。
默认参数
function greet(name = 'world') { return 'hello ' + name }
greet() // "hello world"
greet(undefined) // "hello world"(undefined 触发默认值)
greet(null) // "hello null"(null 不触发)
只有 undefined 触发默认值,不是所有假值。
剩余参数 rest
function sum(...nums) { return nums.reduce((a, b) => a + b, 0) }
...nums 收集剩余实参成为真正的数组。
arguments
普通函数内自动可用的类数组对象。箭头函数没有 arguments。
function log(a, b, c, d) {
console.log(a, b, c, d); // 1, 2, 3, undefined
arguments[0] = 'bfe';
arguments[3] = 'dev';
console.log(a, b, c, d); // "bfe", 2, 3, undefined
}
log(1, 2, 3);
关键行为:非严格模式下 arguments[n] 和命名参数双向同步——但仅限实际传递的参数。超出范围的赋值只作用于 arguments 对象本身。
四、this
详见 this 关键字。
普通函数的
this不是定义时决定的,而是调用时决定的。
调用方式与 this
| 调用方式 | this 指向 | 示例 |
|---|---|---|
普通调用 fn() | 非严格:window / global;严格:undefined | fn() |
方法调用 obj.fn() | 调用它的对象 | obj.fn() |
构造调用 new Fn() | 新创建的对象 | new Person() |
| 显式绑定 | 指定的 thisArg | fn.call(obj), fn.apply(obj) |
call / apply / bind
详见 call-apply-bind。
function foo(a, b) { console.log(this.x, a, b) }
const obj = { x: 100 }
foo.call(obj, 1, 2) // 立即调用,参数逐个传
foo.apply(obj, [1, 2]) // 立即调用,参数数组传
const bar = foo.bind(obj, 1) // 不立即调用,返回新函数
bar(2)
容易踩坑:方法被拿出来调用
const obj = {
x: 10,
foo() { console.log(this.x) }
}
const f = obj.foo
f() // undefined(已经不是 obj.foo(),是普通函数调用)
五、箭头函数
与普通函数的区别
| 特性 | 普通函数 | 箭头函数 |
|---|---|---|
this | 调用时决定 | 继承外层作用域,不可被 call/apply/bind 改变 |
arguments | 有自己的 | 没有,继承外层 |
new 构造 | 可以 | 不可以(TypeError) |
prototype | 有 | undefined |
| 写法 | function() {} | () => {} |
箭头函数 this 的经典用法
对象方法不要用箭头函数(this 不指向对象):
const obj = {
x: 10,
foo: () => { console.log(this.x) } // 不会输出 10
}
回调函数常用箭头函数保留外层 this:
function Timer() {
this.count = 0
setInterval(() => {
this.count++ // 继承 Timer 的 this
console.log(this.count)
}, 1000)
}
六、构造函数与 new
详见 ECMAScript 原型。
new 做了什么
const p = new Person('Tom')
等价于:
const obj = {}
obj.__proto__ = Person.prototype
Person.call(obj, 'Tom')
return obj // 如果 Person 没返回对象的话
构造函数返回值规则
function A() { this.x = 1; return 100 } // 返回基本类型 → 忽略,返回新对象
function B() { this.x = 1; return { y: 2 } } // 返回对象 → 替代默认新对象
new A().x // 1
new B().x // undefined
new B().y // 2
七、闭包
函数能够记住并访问它定义时所在词法作用域中的变量,即使这个函数在外部执行。
function outer() {
let count = 0
return function() {
count++
console.log(count)
}
}
const fn = outer()
fn() // 1
fn() // 2
私有变量
function createCounter() {
let count = 0
return {
inc() { count++ },
get() { return count }
}
}
经典坑:循环闭包
for (var i = 0; i < 3; i++) {
setTimeout(function() { console.log(i) }, 0)
}
// 输出 3, 3, 3(共享同一个 i)
for (let i = 0; i < 3; i++) {
setTimeout(function() { console.log(i) }, 0)
}
// 输出 0, 1, 2(let 每轮创建新绑定)
八、作用域
JS 采用词法作用域:变量查找由函数定义的位置决定,不是调用的位置。
let x = 1
function outer() {
let x = 2
function inner() { console.log(x) }
return inner
}
const f = outer()
f() // 2(inner 定义时在 outer 内部,查找 outer 的 x)
九、高阶函数
接收函数作为参数,或返回函数。
// 接收函数
function map(arr, fn) {
const res = []
for (const item of arr) res.push(fn(item))
return res
}
// 返回函数
function multiply(x) {
return function(y) { return x * y }
}
const double = multiply(2)
double(5) // 10
常见高阶函数:map、filter、reduce、sort、forEach。
详见 array-prototype-map、higher-order-function-callback-mismatch。
十、柯里化
详见 currying。
把多参数函数变成一连串单参数函数,依赖闭包:
function add(a) {
return function(b) {
return function(c) {
return a + b + c
}
}
}
add(1)(2)(3) // 6
十一、IIFE(立即执行函数)
(function() {
console.log('run')
})()
用途:创建独立作用域、避免污染全局变量、旧时代模拟模块。
十二、递归
function factorial(n) {
if (n <= 1) return 1
return n * factorial(n - 1)
}
用具名函数表达式更稳定:
const factorial = function fact(n) {
if (n <= 1) return 1
return n * fact(n - 1) // 不依赖外部变量名
}
十三、特殊函数类型
getter / setter
const obj = {
_x: 1,
get x() { return this._x },
set x(v) { this._x = v }
}
本质也是函数,通过属性访问语法触发。
generator
function* gen() {
yield 1
yield 2
}
const it = gen()
it.next() // { value: 1, done: false }
调用后不直接执行,返回迭代器。详见 ecmascript-iterators-and-generators。
async
async function foo() {
const x = await Promise.resolve(10)
return x
}
foo().then(console.log) // 10
返回 Promise,await 等待 Promise 解析。详见 async / await。
严格模式下的函数行为
'use strict'
function foo() { console.log(this) }
foo() // undefined(非严格模式是 window)
十四、函数对象的成员
| 属性 | 含义 |
|---|---|
name | 函数名(调试用,具名表达式返回内部名) |
length | 形参数量(不含 rest) |
prototype | 构造函数实例共享方法的落点,箭头函数无 |
caller | 历史属性,现代代码尽量不用 |
详见 function-length。
十五、最小必会清单
| 题型 | 核心考点 |
|---|---|
| 函数声明 vs 表达式 | 提升差异 |
| 具名函数表达式 | 内部名可见性 |
| 闭包 | 词法作用域捕获 |
| 循环闭包 | var 共享 vs let 隔离 |
| this | 调用时决定 |
| 箭头函数 this | 继承外层,不可绑定 |
| new 做了什么 | 创建对象 + 原型绑定 + this 绑定 + 返回 |
| call / apply / bind | 立即调用 vs 返回新函数 |
| 高阶函数回调参数 | 参数错位陷阱 |
学习顺序建议
- 函数声明 / 表达式 / 箭头函数
- 作用域 / 词法环境
- 闭包
- this
- call / apply / bind
- new / prototype / 原型链
- Promise / async(本质也离不开函数)
学习检查
不看正文回答:
- JavaScript 函数为什么同时可以被理解成“可调用代码、对象、作用域边界”?
- 函数声明、函数表达式和箭头函数在提升与调用语义上有哪些关键差异?
- 普通函数的
this为什么由调用方式决定,而箭头函数不是? - 闭包为什么依赖词法作用域,而不是调用位置?
call/apply/bind与普通方法调用分别怎样影响this?- 构造调用
new Fn()至少会发生哪些关键步骤? - 高阶函数、柯里化、IIFE、generator、async function 分别是在函数这一基础对象上增加了什么使用模式?
- 给一段陌生函数代码,能否预测它的作用域、
this、闭包和返回行为,而不是只认 API?
掌握验收
- 能解释函数声明 / 表达式 / 箭头函数的语义边界;
- 能独立分析
this、闭包、new 与高阶函数代码; - 能把具体函数问题路由到对应原子笔记,而不是在总览里继续堆第二份说明。