JavaScript读书笔记07

Function类型

函数实际上也是对象,也就是说每个函数其实都是Function类型的实例,也具有方法和属性。既然函数是对象,那么函数名就是一恶搞指向函数对象的指针,不会与特定的函数绑定。函数声明的语句一般是:

function sum(num1, num2){
return num1 + num2;
}

同样你也可以用下面的声明方式,这更好的表面了函数其实也是对象:

var sum = function(num1, num2){
return num1 + num2;
};

这就跟声明其他变量时一样,注意末尾的分号,这就是一个声明sum变量的语句,并且把sum初始化一个函数的实例。这种定义的方式其实是使用的Function类型的构造函数,它的构造函数接受任意的参数,但最后一个参数始终被看成是函数体,前面的参数都作为新函数的参数,也就是,我们还可以这样定义函数:

var sum = new Function("num1", "num2", "return num1 + num2");	//不推荐

从技术上讲,这更加符合函数是个对象的本质。但是我们并不推荐这样方式去定义函数,因为这样将导致两次代码的解析(第一次是常规的ECMAScript代码,第二次是传入构造函数的各种字符串),这将对性能有影响。

既然函数也是对象,函数名就是指向这个对象的指针,所以一个函数也可以有多个名字:

function sum(num1, num2){
return num1 + num2;
}

var anotherSum = sum;

sum = null;
anotherSum(10, 10); //20

sum设置为null只是为了让sum和函数断绝关系,还是可以继续通过anotherSum找到这个函数,继续使用。

深入理解没有重载

之前已经提到ECMAScript中的函数没有重载,但现在我们可以明确的说,没有重载的原因:函数名称只是一个变量,当你给这个变量赋值两次的时候,当然后面一次覆盖了前面的一次:

function addSomeNum(num){
return num + 10;
}

function addSomeNum(num){
return num + 20;
}

addSomeNum(10); //30

这其实跟下面的代码是完全一致的,下面的代码更能说明发生了什么:


var addSomeNum = function(num){
return num + 10;
}

addSomeNum = function(num){
return num + 20;
}

addSomeNum(10); //30

函数声明和函数表达式

上文说提到的两种定义函数的方式分别是函数声明和函数表达式,目前为止我们还没谈到它们的区别。但解释器在执行环境中加载数据时,它们也并不是一视同仁的:

  • 对于函数声明,解析器会率先读取函数声明,并且使其在执行任何代码前可用;
  • 对于函数表达式,必须等到解析器执行到它所在的行,才会被解释执行。
sum(10, 10);				//20

function sum(num1, num2){
return num1 + num2;
}

以上代码完全可以正常运行。因为代码在执行前,解析器就已经通过一个名为函数声明提升的过程,读取并且把函数声明添加到执行环境中。但是如果改成函数表达式,则代码就会在运行期间出错:


sum(10, 10); //error: sum is not a function

var sum = function(num1, num2){
return num1 + num2;
}

作为值的函数

函数本身就是一个变量,所以也可以作为另一个函数的参数和返回值:

function testCondition(num, condition){
return condition(num);
}

function isLargerThan10(num){
return num > 10;
}

var res = testCondition(20, isLargerThan10); //true

当然函数也能作为返回值:


function largerThan(base){
return function(num){
return num > base;
};
}

var largerThan10 = largerThan(10);

var res = testCondition(20, largerThan10); //true

函数内部属性

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

arguments

arguments是类数组对象,包含了传入函数的所有参数,并且还有一个属性callee,该属性是个指针,指向拥有这个arguments对象的函数。下面看一个经典的阶乘问题:

function factorial(num){
if(num < 1){
return 1;
}
else{
return num * factorial(num - 1);
}
}

var anotherFactorial = factorial;

factorial = function(){
return 0;
}

var res = anotherFactorial(5); //0

上面的例子中,递归函数把函数的执行和函数名factorial耦合在一起了,所以当factorial发生改变的时候,anotherFactorial也失效了,因为在anotherFactorial内部,还在调用factorial。所以递归算法就可以用到argumentscallee属性,还是同样的代码:

function factorial(num){
if(num < 1){
return 1;
}
else{
return num * arguments.callee(num - 1);
}
}

var anotherFactorial = factorial;

factorial = function(){
return 0;
}

var res = anotherFactorial(5); //120

这里我们就得到了正确的结果。

this

this引用的是函数据以执行的环境对象,总的说来,就是this总是指向调用函数的那个对象,如果是全局函数,那么就指向全局对象,在网页中就是window


color = "red"; //等价于window.color = "red"

var obj = {color : "blue"};

function tellColor(){

alert(this.color);
}

tellColor(); //red
obj.tellColor = tellColor;
obj.tellColor(); //blue

需要始终牢记的是函数只是一个变量,所以这里的tellColor()obj.tellColor()指向是同一个函数,只是在函数中的this对象不一样而已。

caller

ECMAScript 5中也规范了另一个函数对象的属性:caller,指向调用该函数的函数的引用,如果是全局的函数,它的值为null:

function inner(){
alert(inner.caller); //function outter
}

function outter(){
alert(outter.caller); //null
inner();
}

outter()

函数的属性

函数是对象,当然和其他对象一样,也具有属性和方法。函数有两个属性:lengthprototype

length

length表示函数希望接受到的命名参数的个数:


function sayHi(){
alert("hi");
}

function sayHiTo(name){
alert("hi" + name);
}

alert(sayHi.length); //0
alert(sayHiTo.length); //1

prototype

对于ECMAScript的引用类型而言,prototype是保存它们所有实例方法的所在。也就是诸如tostring(),valueOf()等方法都是保存在prototype名下,只是通过各自对象的实例方法来调用。在创建自定义的引用类型以及实现继承时,prototype也是很很重要的,我们将会在之后讲到。在ECMAScript 5中prototype是不可枚举的。

函数的方法

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

apply()call()

apply()接受两个参数,一个是其中运行函数的作用域,另一个是参数数组,其中第二个参数可以是Array的实例,也可以是arguments对象。
call()apply()方法的作用相同,区别仅仅是接受参数的方式不同,对call()而言,第一个参数任然是运行函数的作用域,而之后的参数用于直接接受参数,而不是以数组的形式给出。


window.num = 10;

var obj = {num : 20};

function addToNum(addNum){
return this.num + addNum;
}

var res = addToNum.apply(window, [10]); //20
var res1 = addToNum.call(obj, 20); //40

由上面的例子也可以看出,apply和call正真强大的地方是能够扩充函数运行的作用域,在上面的例子中,我们就并没有给obj添加方法。

bind()

ECMAScript 5中还定义了一个方法,bind()。这个方法会创建一个函数的实例,其this值会被绑定到传给bind()的参数的值:

window.color = "red";

var obj = {color : "blue"};

function tellColor(){
alert(this.color);
}

var objectTellColor = tellColor.bind(obj);
objectTellColor(); //blue

继承的方法

每个函数的继承的toLocaleString()toString()始终都返回函数的代码,只是返回代码的格式因浏览器而异,有的返回源代码中的一样的函数代码,有的返回函数代码的内部表示——由解析器删除注释并且对某些代码做了改动之后的代码。valueOf()也同样返回函数代码。