js的引用类型之Function类型

1. 概念

在js中,函数实际上是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。

1
2
3
4
5
6
7
8
9
10
function sum(num1, num2) {
return num1 + num2;
}
console.log(sum(1, 2));//3

var anotherSum = sum;
console.log(anotherSum(1, 2));//3

sum = null;
console.log(anotherSum(1, 2));//3

每个函数都是Function类型的实例。

2. js函数没有重载

函数名可以想象成指针,在定义一个同名函数相当于把原来的函数名的指向直到新的函数对象

1
2
3
4
5
6
7
8
9
10
11
function person() {
console.log('ok');
var a = 1;
console.log(a);
}
function person() {
console.log('no');
var b = 2;
console.log(b);
}
person();//no

3. 定义函数的三种方式

3.1 function语句形式(函数声明)

一个重要特征就是函数声明提升

1
2
3
4
function text() {
//函数执行部分
}
text();

3.2 函数表达式定义函数(ECMAScript推荐的定义方式)

1
2
3
4
var text = function() {
//函数执行部分
};
text();//不可放函数定义上面,因为是顺序执行的

3.3 通过Function构造函数形式定义函数(不推荐)

可以接收任意数量的参数,但最后一个参数始终都被看成是函数体

1
2
var text = new Function("a","b","return a+b");
//注意!建议双引号

3.4 三者定义函数的区别

function语句 Function构造函数 函数直接量
解析时机 优先解析(即函数声明提升) 顺序解析 顺序解析
作用域 具有函数的作用域 顶级函数(顶级作用域 具有函数作用域
性质 静态 动态 静态
形式 句子 表达式 表达式
1
2
3
4
5
6
7
8
9
var k = 1;
function t1() {
var k =2;
function test1() {return k;}
var test2 = function() {return k;};
var test3 = new Function("return k;");
console.log("test1="+test1()+"。test2="+test2()+"。test3="+test3());
}
t1();//test1=2。test2=2。test3=1

var test3 = new Function("return k;");这句相当于顶级作用域下构造函数。

理解函数提升的关键在于理解函数声明与函数表达式之间的区别。

1
2
3
4
5
6
7
8
9
10
//不推荐的代码
if(condition) {
function sayHi() {
console.log("hi");
}
} else {
function sayHi() {
console.log("good!");
}
}

以上代码,表面上看是表示在condition为true时,使用一个sayHi()的定义;否则将使用另外一个定义。实际上在ECMAScript中这属于无效语法,js引擎会尝试修正错误,但是每个游览器尝试修正错误的做法并不一致。大多数会返回第二个声明,忽略condition;Firefox会在condition为true时返回第一个声明。因此这种代码很危险,别使用。正确的做法时使用函数表达式。

1
2
3
4
5
6
7
8
9
10
11
12
//正确做法
var sayHi;

if(condition) {
sayHi = function () {
console.log("hi");
};
} else {
sayHi = function () {
console.log("good!");
}
}

4. 递归

递归函数是在一个函数通过名字调用自身的情况下构成的。

1
2
3
4
5
6
7
8
//递归阶乘函数
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * factorial(num - 1);
}
}

把上述函数解耦合

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

但是在严格模式下,不能通过脚本访问arguments.callee。采用命名函数表达式来达成相同的结果

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

5. 函数内部属性

5.1 arguments

5.2 this

5.3 caller

ECMAScript5采用,除了Opera早期版本,其他游览器都支持。

这个属性中保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值是null

1
2
3
4
5
6
7
8
9
10
function outer() {
inner();
}
function inner() {
console.log(inner.caller);
}
outer();
//ƒ outer() {
// inner();
//}

outer函数调用了inner,所以inner.caller就指向outer()。为了实现更松散的耦合,可以使用arguments.callee.caller

1
2
3
4
5
6
7
8
9
10
function outer() {
inner();
}
function inner() {
console.log(arguments.callee.caller);
}
outer();
//ƒ outer() {
// inner();
//}

6. 函数的属性和方法

6.1 length:函数形参的个数

1
2
3
4
5
6
function abc(a,b,c,d) {
console.log('ok');
}
console.log(abc.length);//4
abc(1,2);//ok
console.log(abc.length);//4

6.2 prototype

对于ECMAScript中的引用类型而言,prototype是保存它们所有实例方法的真正所在。在ECMAScript5中,prototype属性是不可枚举的,因此使用for-in无法发现。

6.3 apply() call()

每个函数都包含两个非继承而来的方法:apply()call()。用途是在特定的作用域中调用函数,实际上等于设置函数体内的this对象的值
使用这个两个方法来扩充作用域的最大好处,就是对象不需要与方法有任何耦合关系。

6.3.1 apply()

传入两个参数,第一个是在其中运行函数得到作用域;第二个是参数数组,可以是Array的实例,也可以是arguments对象

6.3.2 call()

也是两个参数,第一个上apply()一样;第二个参数是其余参数逐个列表出来。

6.4 bind()

ECMAScript5新定义的方法bind()。这个方法会创建一个函数的实例,其this值会被绑定到传给bind()函数的值。

1
2
3
4
5
6
7
8
window.color = "red";
var o = {color:"blue"};

function sayColor() {
console.log(this.color);
}
var say = sayColor.bind(o);
say();//blue

6.5 toLocaleString() toString() valueOf()

返回函数代码,具体返回代码的格式因游览器而异