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

只不过相比普通对象:

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

因此:

复制代码
所有函数都是对象
但不是所有对象都是函数
相关推荐
可乐鸡翅yeah_1 小时前
video.js 集成 hls.js 开发 M3U8 播放器,新手高频踩坑
开发语言·前端·javascript·后端·ecmascript·m3u8·音视频在线播放
用户117953654782 小时前
TRAE vs Claude Code 性能对比:从代码生成、多文件重构到底层吞吐,谁更适合你的日常开发?
javascript
kill5223 小时前
useRef
前端·javascript·react.js
米多科技4 小时前
我做了一个 3D 虚拟世界基底,在上面再搭 3D 世界或元宇宙,事半功倍
javascript·人工智能·github
霍营_托尼4 小时前
纯前端播 40GB 本地视频?我把浏览器改造成了「磁盘流式」播放器
前端·javascript·音视频开发
西柚小萌新5 小时前
【LLM&&AI应用开发 八股文】--4.2.Agent智能体(中)
前端·javascript·react.js
百度一下吧5 小时前
断网后 WebView 加载单页面应用:页面跳转 JS 加载失败的处理方案
开发语言·javascript·ecmascript
程序员Sunday6 小时前
Promise.all、allSettled、race、any 怎么选,失败后其他请求会怎样
开发语言·前端·javascript
szial7 小时前
JavaScript 的 call、apply 和 bind:如何控制函数调用
开发语言·前端·javascript
。小二7 小时前
Go 泛型并发利刃:async 库深度评测——370K ops/s、零依赖、生产就绪
开发语言·javascript·golang