一般来说,一个函数是可以通过外部代码调用的一个子程序(或在递归的情况下由内部函数调用),像程序本身一样,一个函数由称为函数体的一系列语句组成,值可以传递给一个函数,函数也可以返回一个值
函数的定义
在 JavaScript
中,函数是头等(first-class
)对象,因为它们可以像任何其他对象一样具有属性和方法,它们与其他对象的区别在于函数可以被调用,简而言之,它们是 Function
对象,下面是一个简单的函数定义方式
1 | function fn1() { |
在 JavaScript
中函数就是对象,只不过函数是一个非常特殊的对象,是一个 Function
类的实例,其实在内存中存储的操作是通过一个键值对来存储的,函数的名称仅仅是一个键,它的值是指向了内存中的一个对象,这个对象就是 Function
的一个对象,我们又声明一个变量 fn2
,它是通过函数的拷贝来完成赋值的,两个引用并没有指向同一个对象
1 | var fn2 = fn1 |
函数虽然是一个对象,但是却和对象有一些区别,对象是通过引用的指向完成对象的赋值的,而函数确实通过对象的拷贝来完成的,所以 fn1
虽然改变了,但是并不会影响 fn2
,对于对象而言,是通过引用的指向来完成赋值的,此时修改 o1
或者 o2
会将两个值都完成修改
1 | // o1 和 o2 其实指向了同一块空间,当修改 o2 的值的时候,o1 的也会改变 |
函数对象
对象字面量产生的对象连接到 Object.prototype
, 函数对象连接到 Function.prototype
(该原型对象本身连接到 Object.prototype
)
1 | Function.prototype.__proto__ === Object.prototype // true |
每个函数在创建的时候会有两个附加属性,函数上下文和实现函数行为的代码,每个函数对象在创建的时候也会带有一个 constructor
属性,它的值是一个拥有 constructor
属性且值即为该函数的对象
函数调用
调用一个函数将暂停当前函数的执行,传递控制权和参数给新函数,除了声明的时候定义的形式参数,每个函数接收两个附加的参数 this
和 arguments
,参数 this
的值取决于调用的模式,在 JavaScript
中一共有四种调用模式
- 方法调用模式
- 函数调用模式
- 构造调用模式
apply/call
调用模式
当实际参数(arguments
)的个数与形式参数(parameters
)的个数不匹配的时候不会导致程序错误,主要分为以下几种情况
- 如果实际参数过多,超出的参数值将会被忽略
- 如果实际参数过少,缺失的部分将会被替换为
undefined
- 而且需要注意的是,对参数的值不会进行类型检查,即任何类型的值都可以被传递给参数
函数没有重载
比如下面这个例子
1 | function sum(num1, num2) { |
换一种写法,可以看的更清楚些
1 | var sum = function (num1, num2) { |
可以发现,下面一个 sum
方法会将上面的同名方法覆盖掉,所以只会执行下面的方法,特别指出,函数的参数和调用没用关系,如果函数只有一个参数,但是却传入了两个参数,仅仅只会匹配一个,函数是对象,不存在重载,只会存在覆盖,后面定义的会覆盖前面所定义的
1 | /** |
函数的值传递
由于函数是对象,所以可以直接把函数通过参数传递进来
1 | function callFun(fun, arg) { |
作为返回值来传递(也就是所谓的闭包)
1 | function fn1(arg) { |
函数中的 arguments
在函数对象中有一个属性叫做 arguments
,通过这个属性可以获取相应的参数值,这个属性是一个类数组对象,其实就是传递进来的参数的一个集合
1 | function say(num) { |
arguments.callee()
在 arguments
这对象中有一个 callee
的方法,arguments.callee(arg)
可以实现反向的调用(已经不再推荐使用)
1 | function factorial(num) { |
以上是一个求阶乘的函数,递归调用的函数名称和原有函数名耦合在一起,如果将这个函数名称更改以后,递归就会失效
1 | var fn = factorial |
此时由于 fn
函数依然使用 factorial
这个名称来调用,但是 factorial
已经指向 null
了,所以会报错,如上情况就需要使用 arguments.callee
方法来调用
1 | function factorial(num) { |
函数中的 this
当需要创建一个函数对象的时候,设置函数对象的属性和方法需要通过 this
关键字来引用,但是特别注意 this
关键字在调用的时候会根据不同的调用对象而变得不同
1 | var color = 'red' |
使用 c
来调用 showColor
方法,等于调用了 showColor
方法,此时的 this
是 c
,所以是 yellow
,直接调用 showColor
,此时调用的对象等于是 window
,showColor
的 this
就是 window
,所以就会在 window
中寻找 color
函数的返回值
当一个函数被调用的时候,它从第一个语句开始执行,并在遇到关闭函数体的 }
时结束,从而是的函数把控制权交还给调用该函数的程序部分,return
语句可用来使函数提前返回,当 return
被执行的时候,函数立即返回而不再执行余下的语句,一个函数总是有一个返回值,如果没有指定返回值,则返回 unfefined
如果函数在前面加上 new
前缀的方式来调用,且返回值不是一个对象,则返回 this
(该新对象),简单的总结就是,如果一个构造函数不写 return
语句,则系统会自动帮你返回一个对象,但是如果写了 return
语句,则
- 如果
return
的是一个基本类型的值,则会忽略这个return
,该返回什么还是返回什么,但是会阻止构造函数接下来的执行 - 如果
return
了一个引用类型,则原有的return
会被覆盖
一些函数的实例
下面我们来几个比较常见的排序的示例,在平常开发当中也是经常会遇到的
实例一
1 | function sortByNum(a, b) { |
实例二
1 | function Person(name, age) { |
使用如上的方式来处理排序,带来的问题是需要为每一个属性都设置一个函数,显得不是很灵活,所以可以通过函数的返回值来调用,实现函数排序功能
1 | function sortByProperty(propertyName) { |