大家在刚学 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 中,函数其实也是对象。
只不过相比普通对象:
对象 = 存数据
函数 = 存数据 + 执行能力
因此:
所有函数都是对象
但不是所有对象都是函数