引用类型之Function类型

对于面向对象编程的语言来说,对象是很重要的一个概念,而对于函数来说,每个函数实际上都是对象,每个函数都是Function类型的实例,而且与其他引用类型一样具有属性和方法。

函数声明与函数表达式

通常有三种声明函数的方法

  1. 使用函数声明语法
    1
    2
    3
    function sum(num1, num2) {
    return num1 + num2;
    }

这也是最常用的方法

  1. 使用函数表达式定义函数
    1
    2
    3
    var sum = function (num1, num2) {
    return num1 + num2;
    };

可能会注意到function关键字后面没有函数名,这是因为使用函数表达式定义函数时,通过变量sum就可以引用函数

  1. 使用Function构造函数
    1
    var sum = new Function("num1", "num2", "return num1 + num2");

但是非常不推荐这种写法,因为这种语法会导致解析两次代码(第一次解析ECMAScript代码,第二次解析传入构造函数的参数)从而影响性能

关于变量提升

对于1,2两种声明函数的方法有什么不同呢?看下面这个例子

1
2
3
4
5
6
7
8
9
10
11
// 使用函数声明创建函数
console.log(sum(10,10)); //会打印出20
function sum(num1, num2) {
return num1 + num2;
};
// 使用函数表达式创建函数
console.log(add(10,10)); //会报错
var add = function(num1, num2) {
return num1 + num2;
};

实际上,解析器在执行环境中加载数据时,解析器会率先读取函数声明,并使其在执行任何代码之前可用,而至于函数表达式,则必须等到解析器执行到它所在的代码行才会真正被解释执行。

函数内部属性

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

arguments是一个类数组对象,包含着传入函数的所有参数,之前在基础算法的时候已经讲过。但是arguments还有一个名叫callee属性,该属性是一个指针,指向拥有arguments对象的函数。具体用法之前写过了,看这里

另一个比较特殊的对象就是this了,关于this知乎上@方方老师这篇文章讲的特别好

函数的属性和方法

因为在ECMAScript中函数是对象,因此函数也有属性和方法。

每个函数包含两个属性:lengthprototype

length表示函数接受的参数的个数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function sayName(name) {
console.log(name);
}
function sum(sum1, sum2) {
return num1 + num2;
}
function sayHi() {
console.log("Hi!");
}
console.log(sayName.length); //1
console.log(sum.length); //2
console.log(sayHi.length); //0

另一个属性prototype是保存引用类型所有实例方法的真正所在,比如toString()valueOf()等方法都保存在prototype名下,prototype在实现继承时非常重要,下一篇我们再讲。

每个函数都包含两个非继承来的方法:apply()call()

apply()call()都是在特定的作用域中调用函数,等于设置函数体内this的值,他们的区别在于:
apply()接受两个参数:一个是在其中运行函数的作用域,另一个是参数数组,既可以是Array实例,也可以是arguements对象。
call()第一个参数和apply()一样,但是传递参数的时候,必须逐个例举出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function sum(num1, num2) {
return num1 + num2;
}
function callSum1(num1, num2) {
return sum.apply(this, arguments); //传入arguments字符串
}
function callSum2(num1, num2) {
return sum.apply(this, [num1,num2]); //传入数组
}
function callSum3(num1, num2) {
return sum.call(this, num1, num2); //使用call必须把参数列举出来
}
console.log(callSum1(10,10)); //20
console.log(callSum2(10,10)); //20
console.log(callSum3(10,10)); //20

既然apply()call()的参数有作用域,那么是用来干什么的呢?看下面的例子:

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

是不是this的值又懵了?如果懵了,就翻上去看方方老师那篇文章。
没错,apply()call()的作用就是扩充函数的作用域

大概就是这么多了,书上这一章的内容我反复看了好几遍才理解,所以学习哪有什么捷径,无他,唯手熟尔
那么,聪明的你,看完之后懂了没有?
下一篇准备讲原型

努力<br><br>希望能成为一名前端工程师<br>加油