从“会用函数”到“理解函数”:JavaScript 中函数为什么也是对象?

大家在刚学 JavaScript 时,都会写这样的代码:

scss 复制代码
function sayHello() {
    console.log("hello");
}

sayHello();

看起来函数似乎只是"一段能执行的代码"。

但当学习深入后,你会发现:

ini 复制代码
function fn(){}

fn.age = 18;

console.log(fn.age);

竟然可以正常输出:

复制代码
18

为什么函数还能存属性?

这背后涉及 JavaScript 一个非常重要的思想:

函数本质上也是对象。


什么是函数?

函数(Function)是一段可重复执行的代码。

例如:

css 复制代码
function add(a, b) {
    return a + b;
}

调用:

scss 复制代码
add(1, 2);

得到结果:

复制代码
3

函数最大的价值就是:

  • 封装逻辑
  • 提高复用性
  • 降低代码重复率

为什么说函数也是对象?

先看普通对象:

ini 复制代码
let obj = {};

obj.name = "Tom";

console.log(obj.name);

函数同样可以这样做:

ini 复制代码
function fn(){}

fn.name2 = "Jerry";

console.log(fn.name2);

说明函数和普通对象一样:

  • 可以保存数据
  • 可以拥有属性
  • 可以被变量引用

因此:

复制代码
函数属于对象的一种

但它比普通对象多了一项特殊能力。


函数和普通对象最大的区别

普通对象:

ini 复制代码
let obj = {};

obj();

运行:

vbnet 复制代码
TypeError: obj is not a function

因为对象不能执行。

而函数:

php 复制代码
function fn(){}

fn();

却能正常运行。

这是因为函数内部拥有一个特殊的隐藏能力:

lua 复制代码
[[Call]]

正是这个能力让函数支持:

php 复制代码
fn()

这样的调用形式。

所以:

复制代码
对象 = 存数据
函数 = 存数据 + 执行代码

函数为什么能赋值给变量?

来看一个经典例子:

javascript 复制代码
function test() {
    console.log("hello");
}

let a = test;

很多初学者会疑惑:

复制代码
这里没有括号,
为什么可以赋值?

因为:

bash 复制代码
test

表示函数对象本身。

而:

scss 复制代码
test()

表示执行函数。

所以:

ini 复制代码
let a = test;

实际上就是:

css 复制代码
让变量 a 指向这个函数对象

调用:

css 复制代码
a();

输出:

复制代码
hello

fn 到底是什么?

很多人在学习回调函数时最容易懵。

例如:

php 复制代码
function run(fn){
    fn();
}

这里的:

php 复制代码
fn

其实只是变量名。

和下面没有任何区别:

javascript 复制代码
let dog = function(){
    console.log("汪汪");
}

dog();

这里:

复制代码
dog

是变量名。

那么:

php 复制代码
fn

也只是变量名。

真正执行的是:

复制代码
变量中保存的函数对象

这也是为什么函数可以作为参数传递。


为什么有些函数没有 return?

例如:

javascript 复制代码
function sayHello(){
    console.log("hello");
}

这里没有写:

kotlin 复制代码
return

因为它只负责执行动作。

并不需要返回结果。

实际上 JavaScript 会自动补充:

javascript 复制代码
function sayHello(){
    console.log("hello");
    return undefined;
}

所以:

arduino 复制代码
console.log(
    sayHello()
);

输出:

javascript 复制代码
hello
undefined

Function.prototype 是什么?

学习原型链时经常会遇到:

javascript 复制代码
Function.prototype

可以简单理解为:

所有函数共享的方法仓库。

例如:

csharp 复制代码
function foo(){}

为什么能使用:

scss 复制代码
foo.call()
foo.apply()
foo.bind()

因为这些方法来自:

javascript 复制代码
Function.prototype

原型链关系:

javascript 复制代码
foo
 ↓
Function.prototype
 ↓
Object.prototype
 ↓
null

验证:

javascript 复制代码
function foo(){}

console.log(
    foo.__proto__ === Function.prototype
);

结果:

arduino 复制代码
true

prototype 和 proto 的区别

这是 JavaScript 面试中的高频问题。

proto

表示:

复制代码
当前对象从谁继承

例如:

csharp 复制代码
function Person(){}

Person.__proto__

指向:

javascript 复制代码
Function.prototype

prototype

表示:

复制代码
未来实例对象从谁继承

例如:

javascript 复制代码
function Person(){}

Person.prototype.sayHi = function(){
    console.log("hi");
};

let p = new Person();

关系:

javascript 复制代码
p
 ↓
Person.prototype
 ↓
Object.prototype
 ↓
null

简单记忆:

属性 作用
proto 当前对象的原型
prototype 构造函数给实例准备的原型对象

学习总结

今天最大的收获是弄清楚了一个以前经常听到但一直没真正理解的概念:

JavaScript 中,函数其实也是对象。

只不过相比普通对象:

复制代码
对象 = 存数据
函数 = 存数据 + 执行能力

因此:

复制代码
所有函数都是对象
但不是所有对象都是函数
相关推荐
默_笙5 小时前
🚋 从流水线到地铁网:为什么复杂 AI 都要拆成多 Agent(上)——LangGraph 基础入门
前端·javascript
ID34610744205 小时前
【课程设计】基于Spring Boot+Vue的游戏账号租赁系统的设计与实现-计算机毕设 附源码50345
javascript·vue.js·spring boot·python·node.js·php·课程设计
柚yuzumi5 小时前
彻底搞懂 JavaScript 类型转换:显式转换、隐式转换与 ToPrimitive
前端·javascript·node.js
AI情绪识别开源7 小时前
检信ALLEMOTION 认知评估分析器 · WebSocket 推送数据文档
javascript
OpsEye7 小时前
公司内部全员使用 AI,如何保证会话合规、行为可审计?
javascript·ai编程
变与不变8067 小时前
JS事件机制精讲
前端·javascript
沉迷...8 小时前
Git skip-worktree 使用笔记(本地忽略配置文件)
javascript·git
志尊宝10 小时前
Vue3 零基础每日笔记(032):Suspense 与异步组件——懒加载与顶层 await 终极方案
前端·javascript·vue.js·笔记·html5
是立不是利10 小时前
前端分页逻辑与边界场景实战解析
前端·javascript