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

只不过相比普通对象:

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

因此:

复制代码
所有函数都是对象
但不是所有对象都是函数
相关推荐
zzqssliu2 小时前
taocarts 跨境独立站 SEO 优化实践(多语言 + 反向海淘场景)
java·javascript·php
前端Hardy2 小时前
CSS 动画真的比 JS 快?Josh Comeau 做了组实验,结果跟直觉不一样
前端·javascript·后端
前端Hardy2 小时前
前端日历组件,要变天了?Schedule-X v4.6 彻底杀疯了
前端·javascript·后端
如此风景2 小时前
UniCloud学习真经
javascript
ZC跨境爬虫2 小时前
跟着 MDN 学CSS day_36:(float、clear与BFC深度解析)
前端·javascript·css·ui·交互
糯米团子7493 小时前
javascript高频知识点
开发语言·前端·javascript
无风听海3 小时前
Bearer Token 权威指南:从原理到生产级安全实践
前端·javascript·安全
riuphan4 小时前
JavaScript 类型判断完全指南
前端·javascript
Hilaku4 小时前
前端工程师最终会变成 AI工程师?
前端·javascript·程序员