在 JavaScript 中,函数是 First-class Object。
在一些其他的语言中(比如 Scala,一种动态的 JVM 语言),我们也会看到这句话:
函数是 First-class Object。
这句话潜在含义是该语言支持函数式编程范式,并且函数在该语言中拥有与普通变量一样的特性:
- 可以通过字面量创建
- 可以赋值给变量、数组或其他对象的属性
- 可以作为函数的参数
- 可以作为函数的返回值
- 可以拥有动态创建并赋值的属性(这是动态类型的特性)
函数式编程范式是声明式编程范式的一种,而过程式编程范式和面对对象编程范式都属于命令式编程范式的范畴。
函数定义
在 JavaScript 中,函数也是一个对象,所以函数定义的方式也与其他语言有所不同,除了一般的函数声明之外,还可以使用函数对象构造器等方式创建函数对象。
之前,我们就说过,在 JavaScript
是一个支持函数式编程范式的特性,其中一个重要点就是函数可以通过字面量来创建。
函数字面量
对函数字面量的理解和对其他字面量的理解是一致的。
我们首先来看看其他字面量的用法。
|
每一种字面量都有每一种字面量的既定格式。
比如,使用'...'
表示一个字符串字面量,使用[..., ...]
表示一个数组字面量,使用{...: ..., ...: ...}
表示一个对象字面量。
函数字面量也有其既定的格式,一般由以下四部分组成:
function
关键字。- 可选名称,符合 JavaScript 标识符规范。
- 圆括号及可选的逗号分隔的参数列表。
- 大括号及可选的函数体内容。
遵循以上格式要求,我们可以从简至繁地构建以下函数字面量。
|
不难发现,函数字面量与 C 或 Java 语言的函数声明语法非常相似。
在 JavaScript 中,以函数字面量开头的语句就是一个函数声明,也就是说以fucntion
关键字开头的语句会被当作函数声明语句进行处理。
函数字面量拥有与其他字面量一样的特性。
|
在上面的场景中,我们可以看到关键字
function
不再是语句的开头了。
此时,JavaScript 会把函数字面量当成函数表达式处理。
而函数表达式在 JavaScript 中扮演了很重要的角色。
函数声明与函数表达式
函数声明和函数表达式的主要区别有:
- 关键字
function
是否位于语句的开头。 - 绑定函数实例的函数标识符可访问的范围。
- 是否返回函数对象。
第一条是用于区分函数声明和函数表达式,
之后的则是函数声明和函数表达式的差异。
首先,我们来看函数标识符的可用范围。
|
上面的代码示例中已说明了函数表达式会返回函数对象的特性,但不妨再比较一下。
|
函数表达式的这两个特性在 JavaScript 中有着非常广泛的用法。
函数名与函数定义方式的关系
既然说到函数标识符的作用域,那在这里介绍一下函数的 name
属性,该属性值为函数名。
别忘记,函数也是对象哦,既然是对象,自然也可以有属性的。
|
我们可以看到 name
属性值与函数字面量的函数标识符相同,但我们继续往下看。
|
我们发现当使用 fn
引用匿名函数并获取 name
属性值时,得到的并不是 ""
,而是 "fn"
。看上去,函数的 name
属性不仅仅会考虑函数标识符,还会考虑函数引用的标识符。
|
而且,只会考虑第一次引用函数的标识符。
我们不妨再考虑下以下情况:
|
由此我们可以总结一下:
- 具名函数字面量的
name
属性为函数名称。 - 匿名函数字面量的
name
属性一般为""
,但存在以下两种特殊情况:- 当将匿名函数赋值给普通变量时,函数的
name
属性值为普通变量名。 - 当在对象字面量中使用匿名函数字面量时,函数的
name
属性值为对象属性名。
- 当将匿名函数赋值给普通变量时,函数的
作用域
作用域指的是一个声明变量或声明函数可以被访问的范围。
在 JavaScript 中,我们主要讨论两种作用域:函数的作用域和变量的作用域。
在这之前,我们还得介绍一下 JavaScript 的顶级作用域,又称为全局作用域。
一切 JavaScript 代码都是先在全局作用域下执行的。在浏览器环境下,<script>
标签内的 JavaScript 代码以及引用的外部 .js 文件中的代码都是处于同一个全局作用域下。
全局作用域下声明函数会作为全局对象的属性
|
全局作用域下使用 var
声明的变量会作为全局对象的属性
|
⚠️ ES6 中的 let 和 const 声明的变量不具备上述特性。
> let a = 1;> a // 1> window.a // undefined> const b = 2;> b // 2> window.b // undefined>
变量的作用域从声明位置开始
|
函数的作用域从当前函数定义作用域开始
|
也被称为函数提升。
每个函数都会创建一个子作用域
不同作用域下允许同名变量。