JS数据类型:函数

概述

函数的声明

JavaScript 有三种声明函数的方法。

function 命令

function 命令声明的代码区块,就是一个函数。function命令后面是函数名,函数名后面是一对圆括号,里面是传入函数的参数。函数体放在大括号里面。

上面的代码命名了一个 print 函数,以后使用 print 这种形式,就可以调用相应的代码。这叫做函数的声明。

函数表达式

可以采取变量赋值的方法命名函数

上面代码是将一个匿名函数赋值给变量。此时,这个匿名函数又称函数表达式,因为赋值语句的右侧只能放表达式。

需要注意的是:

  • function 命令后面不带有函数名;
  • 函数的表达式需要在末尾加上分号,表示语句结束。

Function 构造函数

使用 Function 构造函数,这种声明函数的方式非常不直观,几乎无人使用。

上面代码中,Function 构造函数接受了三个参数,除了最后一个函数是 add 函数的函数体,其他都是 add 函数的参数。

Function 构造函数可以不使用 new 命令,返回结果完全一样。

函数的重复声明

如果同一个函数被重复声明,后面的声明就会覆盖前面的声明。

上面代码中,后一次的函数声明覆盖了前面一次。而且,由于函数名的提升,前一次的声明在任何时候都是无效的

函数名的提升

JavaScript 引擎将函数名视同变量名,所以采用function命令声明函数时,整个函数会像变量声明一样,被提升到代码头部。

上面代码中看起来像在声明之前就调用了函数 f。但是实际上,由于“变量提升”,函数 f 被提升到了代码头部,也就是在调用之前就已经声明了。

函数参数

函数运行的时候,有时需要提供外部数据,不同的外部数据会得到不同的结构,这种外部数据就叫做参数。

参数的省略

上面代码的函数 f 定义了两个参数,但是运行时无论提供多少个参数(或者不提供参数),JavaScript都不会报错。省略的参数的值就变为 undefined 。需要注意的是,函数的 length 属性与实际传入的参数个数无关,只反映函数预期传入的参数个数。

但是,没有办法只省略靠前的参数,而保留靠后的参数,如果一定要省略靠前的参数,只有显示传入 undefined

传递方式

函数参数如果是原始类型的值(数值、字符串、布尔值),传递方式是传值传递。这意味着,在函数体内修改函数值,不会影响到函数外部。

上面代码中,变量 p 是一个原始类型的值,传入函数 f 是传值传递。因此,在函数内部,p 的值是原始值的拷贝,无论怎么修改,都不会影响到原始值。

但是,如果函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址传递。也就是说,传入函数的原始值的地址,因此在函数内部修改参数,将会影响到原始值。

上面代码中,传入函数 f 的是参数对象 obj 的地址。因此,在函数内部修改 obj 的属性 p,会影响到原始值。

注意,如果函数内部修改的不是参数的某个属性,而是替换掉整个参数,此时不会影响到原始值。(这是因为,形参的值是实参的地址,重新对行参赋值导致行参指向另一个地址,保存在原地址上的值当然不受影响)

arguments 对象

由于 JavaScript 允许函数有不定数目的参数,所以需要一种机制,可以在函数体内读取所有参数。这是就是 arguments 对象的由来。

arguments对象包含了函数运行时的所有参数,arguments[0]就是第一个参数,arguments[1]就是第二个参数,以此类推。这个对象只有在函数体内部,才可以使用。

需要注意的是,虽然 arguments 很像数组,但它是一个对象。数组专有的方法(比如 sliceforEach ),不能在 arguments 对象上使用。

闭包

函数内部可以直接读取全局变量。

但是,正常情况下, 函数外部无法读取函数内部的变量。

如果处于种种原因,需要得到函数内部的变量。正常情况下,这是办不到的。只有通过在函数的内部,再定义一个函数。

上面代码中,函数 f2 就在函数 f1 内部,此时 f1 内部的所有局部变量,对 f2 都是可见的。但是反过来则不行。这就是 JavaScript 语言特有的“链式作用域”结构,子对象会一级一级地向上寻找所有父对象的变量。

既然 f2 可以读取 f1 的局部变量,那么只要把 f2 作为返回值,我们便可在 f1 的外部读取它的内部变量了。

上面代码中,函数 f1 的返回值就是函数 f2,由于 f2 可以读取 f1 的内部变量,所有就可以在外部获得 f1 的内部变量了。

闭包就是函数 f2,即能够读取其他函数内部变量的函数。由于在 JavaScript 语言中,只有函数内部的子函数才能读取内部变量,因此可以把闭包简单理解为“定义在一个函数内部的函数”。闭包最大的特点,就是它可以记住诞生的环境,比如 f2 就记住了它诞生的环境 f1,所以从 f2 可以得到 f1 的内部变量。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

闭包的最大用处有两个,一个是可以读取外层函数内部的变量,另一个就是让这些变量始终保存在内存中,即闭包可以使得它诞生的环境一直存在。

上面代码中,start是函数createIncrementor的内部变量。通过闭包,start的状态被保留了,每一次调用都是在上一次调用的基础上进行计算。从中可以看到,闭包inc使得函数createIncrementor的内部环境,一直存在。所以,闭包可以看作是函数内部作用域的一个接口。

注意,外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题。

原创文章,作者:seabert,如若转载,请注明出处:https://bysjb.cn/js-function.html