上一篇文章是讲了JavaScript中的变量,这篇讲是讲一下JavaScript中的function。只是我在看这本书的一个粗略的了解。这篇文章将从一下几个方面来讲解:
- JavaScript中函数的介绍
- JavaScript中函数的参数
- JavaScript中的匿名函数
- JavaScript中函数的一些特性,函数可以当作data使用
- 函数的属性以及函数的触发
- 函数的scope以及闭包
首先介绍一下JavaScript中的function。在JavaScript中,function是通过一下方法来声明的。
function func(/*parameter*/) {
/*the body of the function*/
/*we can return the result*/
}
从上面这个例子中我们可以看出JavaScript中的函数声明方式,首先,是使用function这个关键字来声明函数的。func是我们声明的函数的名字,在括号里可以添加我们自己的函数参数,有关函数参数方面,将会在后面详细的讲解。然后就是在“{}”内的函数体。这里我们会发现JavaScript中函数和其他编程语言中函数的一个不同的地方,这就数JavaScript中,函数就不表明返回值的类型的。这个也可以理解,因为JavaScript是一个弱类型的语言,我们没有必要表明返回值的类型,因为程序会自动根据类型自动地转变返回值的。
这一段将讲讲JavaScript中函数的参数。在JavaScript中,函数的参数也和其他编程语言有点不同。
function add(a , b) {
return a + b;
}
上面是一个很简单的add的函数,在这个函数中,我们会发现函数的参数没有类型要求的,只有参数的名字。这个也是因为JavaScript是一种弱类型的语言。我们可以通过a , b来获得传进来的变量的值。在JavaScript中,我们还可以使用另外的一种方法来获得传来的参数的值,那就是arguments这个关键字,这个也只能在函数体里使用,我们可以通过这个arguments对象来或者传进来的参数值。如arguments[0]就是a,arguments[1]就是b。另外,在JavaScript中,我们可以想函数中传递任意个实参,即
add(1 , 2 , 3 , 4 , 5);
上面这个代码,我们可以调用上面的那个add函数,因为只有两个参数有在函数中用到的,所以其他参数也就无用的了。但是我们可以通过arguments来获得传进来的函数。如下:
function max(a , b) {
var ret = a;
for (var i=0 ; i<arguments.length ; i++) {
if (ret < arguments[i])
ret = arguments[i];
}
return ret;
}
alert(max(1 , 2 , 3 , 4 , 100 , 5));
上面那段代码,虽然我们只有在函数中定义了两个形参,但是我们可以在调用函数的时候使用多个实参,使用arguments来获得这些形参的。如果当传入的参数个数少于在声明时候的个数,则后面的都是undifined的。我们还可以使用argumens.length来获得传进来实参的个数。相应的,如果我们想知道这个函数在声明的时候定义了几个形参,我们可以通过函数的一个length属性来获得。
function test(a , b) {
if (arguments.callee.length > arguments.length) return "less";
else if (arguments.callee.length < arguments.length) return "more";
else return "equal";
}
alert(test(1));
alert(test(1 , 2));
alert(test(1 , 2 , 3));
上面的代码中,arguments.callee就是或者这个arguments所在的函数,自此的test函数。这里我还不明白既然length是function的属性了,为什么我们不能直接在函数中使用length,即
function test(a , b) {
alert(length);
}
但是上面那段代码显示的是0,希望有大牛能帮我解答这个问题。
button.onclick = function() {
/*body of function*/
}
例如上面的函数,我们没有必要知道函数的名字,所以就直接使用了匿名函数,这样使用起来比较的方便。接着让我们看一个比较有意思的事情,我们使用匿名函数来写递归。这里我们使用到的是上面我们讲到过的callee这个对象。在看代码之前,我首先介绍一下JavaScript中函数声明的特殊的方式,在JavaScript中,我们匿名声明函数的方式是成为Function Literals,这个和我们和我们通常声明的函数是有区别的,
function f(x) { return x*x; } // function statement
var f = function(x) { return x*x; }; // function literal
所以使用上面的方法,我们会看到一种比较有趣的方式,匿名函数实现递归。
var f = function (a) {
if (a <= 1) return 1;
else return (a * arguments.callee(a - 1));
}
alert(f(10));
JavaScript中函数的一些特性,函数可以当作data使用
在JavaScript中,一个比较有趣的事情就是,这里的函数我们可以当做data来使用。我们可以通过()来实行这个函数。请看下面这段代码:
function test() {
var a = new Array();
a[0] = function(x) {return x * x};
a[1] = 10;
a[2] = a[0](a[1]);
alert(a[2]);
}
在上面的代码中,虽然上面的代码看起来比较的奇怪,但是正是说明了在JavaScript中,函数可以当做一种data进行保存起来。其实这种使用的方式给我们在编程的时候带来了很多方便,看看下面的代码:
function addEvent(f) {
if (typeof window.onload != 'function') {
window.onload = f;
}
else {
var oldOnload = window.onload;
window.onload = function() {
oldOnload();
f();
}
}
}
上面的代码主要是为了在我们在添加多个window.onload事件,在上面的代码中,我们首先是检查window.onload有没有事件了,如果没有的话,我们就把f当做window.onload事件,如果已经有添加了,那么就先保存前面的事件,然后在创建一个匿名function,先执行老的事件,然后执行新添加的事件,我们发现这里我们都是使用()来执行函数的。
在上面,我们看到了我们可以使用()来invoke一个函数,在JavaScript中,还有其他方式来invoke一个函数。
- call
var a = 1;
function test(num) {
alert(this.a + num);
}
var b = new Object();
b.a = 2;
test.call(b , 1);
test(1);
在上面的代码中,我们可以看到call中函数分为两部分,一部分是调用这个函数的对象,另外一部分是参数列表。还有在上面的代码中我们可以看到,类似this.a这种的,是根据调用这个函数不同的对象而不同的。
2. apply
apply也可以用来invoke一个函数,只是在invoke一个函数的时候,第二部分的参数有一点不同,call是一个一个分开的,但是在apply中,使用的是array。见下面代码:
function flexisum(a) {
var total = 0;
for(var i = 0; i < arguments.length; i++) {
var element = arguments[i];
if (!element) continue;
var n;
switch(typeof element) {
case "number":
n = element;
break;
case "object":
if (element instanceof Array)
n = flexisum.apply(this, element);
else n = element.valueOf();
break;
case "function":
n = element();
break;
case "string":
n = parseFloat(element);
break;
case "boolean":
n = NaN;
break;
}
if (typeof n == "number" && !isNaN(n)) total += n;
else throw new Error("sum(): can't convert " + element + " tonumber");
}
return total;
}
alert(flexisum(1 , [1 , 2]));
从上面代码,能看到一个apply的使用方式。
最后,我们讲讲JavaScript中函数的scope以及闭包的使用。
在JavaScript中,函数的scope在我们定义函数的时候就已经确定了的。我们这里主要看看函数中闭包,在讲函数中的闭包之前,我们先回忆一下scope chain。如果一个函数中使用一个变量,首先查看的是传进来的参数和函数中有没有定义这个变量,如果没有的话,我们就从上一层scope寻找。在上面,我们也已经知道,一个函数中有一个arguments这样一个变量,在我们调用一个函数中,我们首先会初始化这个变量,在这个变量中存放着我们传进来的参数和我们在函数中声明的变量。这样当我们在函数中使用到一个变量是,我们就是从arguments这里寻找的,如果没有的话,就从上一层的arguments进行寻找。在介绍上面的背景只是之后,我们看看下面的代码:
(function test () {
var a = [];
for (var i=0 ; i<10 ; i++) {
a[i] = function() {
alert(i);
};
}
for (var i=0 ; i<10 ; i++) {
a[i]();
}
})();
看看上面代码的结果是什么?
(function test () {
var a = [];
for (var i=0 ; i<10 ; i++) {
a[i] = function() {
alert(i);
};
}
for (var j=0 ; j<10 ; j++) {
a[j]();
}
})();
在比对上面的结果后,是不是觉得有点奇怪。首先我们说明一下上面函数的调用方式,上面函数test是会自动执行的,这样做的好处就是不会混论命名空间。现在我们来具体分析上面代码的原理。首先我们在test函数中声明了10个函数,并保存起来,然后在最后才执行这些函数。但是我们会发现上面两段代码执行的结果并不相同。只是一个循环变量不一样,为什么会出现上面的情况。这样就要讲到变量的scope chain了。我们看看执行a[]()函数时,i这个变量时从哪里来的。i这个变量没有在我们声明的function() {alert(i)}中,所以这个变量是在函数test中的。我们模拟一下a[]()函数的执行的过程。当执行到alert(i)时,首先在本函数的arguments中找有没有i这个变量,发现没有,就到上一级找,发现上一级arguments有i这个变量,就把上一级的变量拿过来了。所以women分析一下第二段代码,在这段代码中,当我们执行a[j]()这个函数是,test中变量i的值已经是10了,所以才会都是10的。但是在第一段代码中,由于JavaScript中没有块级的变量,第二个var i其实和第一个for中是同一个i,所以在arguments中只有一个i,这样我们每次在invokea[i]()时,i的值又从0到9,所以才会出现那种情况的。我们再来看一段代码:
var a = [];
(function test () {
for (var i=0 ; i<10 ; i++) {
a[i] = function() {
alert(i);
};
}
})();
for (var i=0 ; i<a.length ; i++) {
a[i]();
}
从上面的代码中,我们更加能体会到JavaScript中的闭包。在我们执行a[i]()的时候,函数test已经执行完毕了,local变量i应该是已经清除了的,那为什么我们在执行a[i]()时,函数还是可以访问i这个变量呢?这个因为在JavaScript中,由于发现有a是在外层的scope定义的,所以虽然test执行完了,但是在a[i](),使用到了i这个局部变量,所以test的arguments一直是保存着的,知道定义a[i]的scope也执行完了,才会一起删除arguments。在后面将JavaScript的class时,我们还能看到我们可以使用闭包来实现JavaScript的私有变量等。