1. 简述

函数实际上就是对象,每一个函数都是 Function 类型的实例,与其他引用一样具有属性和方法。由于函数是对象,因此,函数名实际上就是一个指向函数对象的指针,不会与某个函数绑定。

2. 创建

  1. // 函数声明语法
  2. function sum(num1, num2) {
  3. return num1 + num2;
  4. }
  5. // 函数表达式
  6. var sum = function (num1, num2) {
  7. return num1 + num2;
  8. }

注意第一种后面不加分号,有函数名,而第二个相当于一条声明变量语句,要加分号,且不需要函数名,通过变量就可以引用函数。

函数声明和函数表达式,在解析上有着很大区别,所以建议使用函数声明

  • 解析器会率先读取函数声明,并使其在执行任何代码之前可用,至于函数表达式,则必须等到解析器执行到所在代码,才会真正被解析执行。

  • 在代码开始执行之前,解析器就已经通过函数声明提升的过程,读取并将函数声明添加到执行环境中,JavaScript 引擎会将函数声明放在源代码顶部,所以在函数声明之前使用函数不会出错

  • 函数表达式,在函数表达式代码前就使用函数,就会导致错误

除了什么时候可以通过变量访问函数的区别外,函数声明和函数表达式是等价的

另外一种使用 Function 构造函数的方法,不使用,只是说明有这种方法

  1. vat sum = new Function("num1", "num2", "return num1 + num2");

接受任意数量参数,前面的参数枚举传入函数的参数,最后一个始终被看做是函数体。

由于这种语法会导致两次解析代码,第一次是解析常规的 ECMAScript 代码,第二次是解析传入构造函数的字符串,从而影响性能。

注意:访问带圆括号的函数名是调用函数,而不带圆括号的函数名是访问函数的指针

3. 没有重载

ECMAScript 函数没有重载,就是因为函数是对象,函数名是函数对象的指针

  1. var addSomeNumber = function (num) {
  2. return num + 100;
  3. }
  4. addSomeNumber = function (num) {
  5. return num + 200;
  6. }

从上例中就很容易理解为什么没有重载。因为函数名是函数对象的指针,也就相当于变量,再次赋值,会覆盖前面的值,指向另一个函数对象地址。所以没有重载。

通过对传入参数的类型和数量进行检测,可以模拟重载

4. 作为值的函数

ECMAScript 中的函数名本身就是变量,所以函数也可以作为值来使用。也就是,不仅可以将函数当做参数传递给另一个函数,而且可以作为另一个函数的结果返回

对数组排序的 sort() 方法的比较函数,需要传递两个参数,就是要比较的两个值。但我们需要一种方式来指明按照哪个属性来排序。

  1. function creatComparisonFunction(propertyName) {
  2. return function (object1, object2) {
  3. var value1 = object1[propertyName];
  4. var value2 = object2[propertyName];
  5. if (value1 < value2) {
  6. return -1;
  7. }
  8. else if (value1 > value2) {
  9. return 1;
  10. }
  11. else {
  12. return 0;
  13. }
  14. }
  15. }
  16. var data = [{name: "Zachary", age: 28}, {name: "Nicholas", age: 29}];
  17. data.sort(creatComparisonFunction("name"));
  18. console.log(data[0].name); // Nicholas
  19. data.sort(creatComparisonFunction("age"));
  20. console.log(data[0].name); // Zachary

定义一个函数,接受一个属性名,然后根据属性名来创建比较函数。

5. 函数内部属性

在函数内部,有两个特殊对象:argumentsthis

arguments 是一个类数组对象,包含传入函数的所有参数,这个对象还有一个 callee 属性,该属性是一个指针,指向拥有这个 arguments 对象的函数。

多使用于递归算法

  1. function factorial(num) {
  2. if (num <= 1) {
  3. return 1;
  4. }
  5. else {
  6. return num * factorial(num - 1);
  7. }
  8. }

由于函数自身调用自身,这个函数的执行就与函数名紧紧耦合在了一起,也就是说,一旦修改函数名,需要修改两处,不利于维护。为了消除这种紧密耦合现象,就可以:

  1. function factorial(num) {
  2. if (num <= 1) {
  3. return 1;
  4. }
  5. else {
  6. return num * arguments.callee(num - 1);
  7. }
  8. }

这样,无论引用函数时使用什么名字,都可以保证正常完成递归调用。

另一个特殊对象 this 引用的是函数据以执行的环境对象。

  1. window.color = "red";
  2. var o = { color: "blue" };
  3. function sayColor() {
  4. console.log(this.color);
  5. }
  6. sayColor(); // "red"
  7. o.sayColor = sayColor;
  8. o.sayColor(); // "blue"

另一个函数对象的属性 caller,保存着调用当前函数的函数的引用。如果是全局作用域中调用当前函数,它的值为 null

  1. function outer() {
  2. inner();
  3. }
  4. function inner() {
  5. alert(inner.caller);
  6. //alert(arguments.callee.caller)
  7. }
  8. outer(); // 显示 outer() 函数的源代码

注意:在严格模式下,访问 argumentscalleecaller 等,都会导致错误。都是为了增强这门语言的安全性,这样第三方代码就不能在相同的环境窥视其他代码。

6. 函数的属性和方法

length 属性表示希望接受的命名参数个数

prototype 属性保存着引用类型的所有实例和方法的真正所在。

每个函数都包含两个非继承而来的方法:apply()call()。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内的 this 对象值。

apply() 方法接受两个参数,一个是在其中运行的函数的作用域,另一个是参数数组。第二个参数可以是 Array 的实例,也可以是 arguments 对象

  1. function sum(num1, num2) {
  2. return num1 + num2;
  3. }
  4. function callSum1(num1, num2) {
  5. return sum.apply(this, arguments);
  6. }
  7. function callSum2(num1, num2) {
  8. return sum.apply(this, [num1, num2]);
  9. }
  10. alert(callSum1(10, 10)); //20
  11. alert(callSum2(10, 10)); //20

call() 方法与 apply() 方法的作用相同,区别仅是接收参数的方式不同。

第一个参数是 this 值,其余参数都是直接传递给函数。也就是说,使用 call() 方法,传递给函数的参数必须逐个列举

  1. function sum(num1, num2) {
  2. return num1 + num2;
  3. }
  4. function callSum(num1, num2) {
  5. return sum.call(this, num1, num2);
  6. }
  7. alert(callSum(10, 10)); //20

这两个方法真正强大的地方是能够扩充函数赖以运行的作用域,最大好处就是对象不需要与方法有任何的耦合。

bind() 方法会创建一个函数的实例,其 this 值会绑定到传入参数的值

  1. window.color = "red";
  2. var o = { color: "blue" };
  3. function sayColor() {
  4. alert(this.color);
  5. }
  6. var objectSayColor = sayColor.bind(o);
  7. objectSayColor(); // blue

toLocaleString()toString()valueOf() 方法会返回函数的源代码,但是源代码的格式因浏览器而异,所以无法根据返回结果来实现任何功能,只能用于调试。