从“会用函数”到“理解函数”: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 中,函数其实也是对象。

只不过相比普通对象:

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

因此:

复制代码
所有函数都是对象
但不是所有对象都是函数
相关推荐
清秋20 分钟前
全网最全 ECMAScript 攻略( 更新至 ES2026)
前端·javascript·编程语言
Hilaku42 分钟前
为什么 AI 写前端时,总是优先选择 React,而不是 Vue?
前端·javascript·程序员
不简说3 小时前
拖拽DIY表格插件来了!sv-print 七月更新:这次真的能自己拼表格了
前端·javascript·前端框架
weixin_471383034 小时前
Next.js - 04 - 深入理解next.js渲染原理
前端·javascript·next.js
ssshooter4 小时前
小程序分包页面报 "has not been registered yet" 的隐蔽根因:一个动态 import 拖垮整个分包
前端·javascript·微信小程序
鹏多多4 小时前
Web浏览器后台离线通知方案实现以及兼容指南
前端·javascript·vue.js
yangmu32034 小时前
Vue3 响应式系统深度解析:从 Proxy 到依赖追踪
前端·javascript·vue.js
Asize4 小时前
Node path 与 fs 模块:从回调地狱到 async await 的异步进化
javascript·node.js
大家的林语冰6 小时前
😭 Electron 麻了,Deno 直接支持二进制应用,无需第三方桌面应用框架!
前端·javascript·node.js
Ivanqhz7 小时前
NFM(神经因子分解机)
开发语言·javascript·人工智能·算法·蓝桥杯