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

只不过相比普通对象:

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

因此:

复制代码
所有函数都是对象
但不是所有对象都是函数
相关推荐
FogLetter8 小时前
当JavaScript学会“分身术”:异步编程的进化简史
前端·javascript·面试
java1234_小锋8 小时前
Vue3+Vite简介以及构建第一个HelloWorld实例
前端·javascript·vue.js·vite
天天鸭10 小时前
5 万处中文的老项目实现国际化,如何用架构思维完成改造?
前端·javascript·架构
月月大王的3D日记11 小时前
Three.js 入门系列(7):让甜甜圈围着我转 —— 3D文字生成
前端·javascript
半兽先生12 小时前
深度对比:Mapbox GL JS vs Maptalks,WebGIS 开发该如何选型?
开发语言·javascript·ecmascript
only-lucky13 小时前
QML深入学习五(Controls模块)
前端·javascript·学习
其美杰布-富贵-李14 小时前
第 5 篇:Object3D、Group 与场景树
javascript·three.js
海带紫菜菠萝汤14 小时前
H.264 宏块划分与码率分配机制:影响压缩效率的关键参数详解
前端·javascript·音视频
mONESY16 小时前
React + TypeScript 入门到进阶:从 Props 类型约束到状态管理与副作用最佳实践
javascript
码哥DFS17 小时前
算法练习day1-备战2027届秋招
前端·javascript·数据结构·算法