什么是函数(Function)
function sum(a,b){
return a+b;
}
其实通俗的说就是一个有名称的代码段,方便重用。
要注意的是:
1.Javascript 的函数语法,因为Javascript本身就是区分大小写的,所以function不能写作Function或FUNCTION.
2.sum是函数的名称,这个并不是必须的,等以后我们会说到。
3.return是返回,如果不写的话,函数的返回是undefined.如果要返回多个值,可以返回个数组或者对象(以后再说)
函数的调用
下面我们讲函数的基本调用。
var result = sum(1,2)
函数的参数
不好意思的说,这个才是本篇文章的重点。
实例一,参数少于 实际定义参数的数目
var result = sum(1);
结果result 为NaN,这就说明了,参数b的值为undefined,但并不会报错,这就无形中制造了bug.
实例二,参数多于 实际定义参数的数目
sum(1,2,3,4,5)
结果为3.发现多于的参数被忽略了。
实例三,没有参数的函数
function args(){return arguments;}
每个函数里都有一个默认的数组那就是arguments .它就是每个函数默认的参数为[] 。如果我们调用函数如下
args(1,2,3,4,5,6);
会发现arguments的值为[1,2,3,4,5,6]。这下容易了,我们可以改造下上面的sum方法
sum(1,2,3,4);function sum(){
var res= 0;
for(i=0;ires+=arguments[i];
}
return res;
}
Functions are data
这一篇是函数里比较重要的概念,那就是函数是一个数据。看个例子
function f(){return 1;}
var f=function(){return 1;}
这两个函数定义都是相同的。
typeof f;
f的值为"function",所以说Javascript 的 函数是个数据类型。它有比较两个重要的特点
1.它包含了代码
2.能被执行
看个例子
var sum = function(a,b){return a+b;}
var add = sum;
sum=undefined;
typeof sum;
typeof add;
add(1,2);
我们把函数sum做为一个值赋给了add,发现删除了sum不影响add的调用。所以函数也就是个正常的赋值。
匿名函数(Anonymous Functions)
Javascript中,可以不用写赋值的代码,如
"abcd" 1 [1,2,3]
这样都不会报错,这些数据叫做匿名的。同样的函数做为数据也可以是匿名的
function(a){return a}
匿名函数的作用有两个
1.可以把匿名函数做为一个参数传入到另一个函数中。
2.你可以理解运行这个匿名函数
下面就会详细讨论这两个功能的作用了。
回调函数(Callback Functions)
因为函数和其他数据一样可以被赋值,删除,拷贝等,所以也可以把函数作为参数传入到另一个函数中。
实例一
function invoke_and_add(a,b){
return a()+b();
}
function one(){
return 1;
}
function two(){
return 2;
}
invoke_and_add(one ,two);
结果为3;
再来看看匿名函数的用法。
实例二
invoke_and_add(function(){return 1;},function(){return 2;}),直接一个函数体传进去,没有函数名。
我们称,invoke_and_add为回调函数
我们用匿名函数替代了 one,two两个函数。
通过上面两个实例,回调函数的定义为:传递一个函数A到另一个函数B中,并且这个函数B执行函数A。我们就说函数A叫做回调函数。 说白了,就是被人直接调用的函数,在一个函数执行另一个函数!
如果没有名称,就叫做匿名回调函数
回调函数的作用
主要有三个
1.当函数作为参数传递的时候,不用为这个函数定义一个名称,这样的好处是减少了全局变量。
2.节省了大量的代码。
3.提高了程序的性能。
自调用函数(Self-invoking Functions)
自调用函数也是匿名函数的一种表现形式,这个函数在定义之后,直接调用。如下
(
function(){
alert('haha');
}
)()
看起来还挺怪异,不过确实很简单。
自调用函数很方便使用,可以不用定义更多的全局变量。还有个好处,就是这个函数不能被执行两遍。真是非常适合做初始化的工作。
许多著名的javascript库特别多的源码中使用了这个功能,例如本人喜欢的Jquery.
内部函数(Inner Functions)
把函数作为一个值来思考一下,既然一个值可以定义在函数中,把函数做为数据放在函数中也未尝不可。如下:
function a(param){
function b(theinput){
return theinput *2;
}
return 'The result is '+b(param);
}
也可以这么写
var a = function(param){
var b = function(theinput){
return theinput*2;
};
return 'The result is '+b(param);
};
b函数是在a函数之中的 ,也就是意味着,在a函数的外部是无法访问b函数的。所以也称之为私有函数(private function)
a(2);
a(8);
b(2);
发现b(2)是没有定义的。也就确定了它确实是私有函数。
内部函数的是使用还是有很多好处的。
1.可以有更少的全局变量。过多的使用全局变量就有可能由于命名冲突而产生过多的bugs
2.私有性,可以设计更好的接口函数供外部访问。
返回值为函数的函数(Functions that Return Functions)
在前几篇文章已经介绍了函数要有返回值,即使没有写return,函数也会返回一个undefine。
接下来看看返回值为函数的情况
function a(){
alert('a');
return function(){
alert('b');
};
}
在这个例子中,a函数执行了alert('a'),以及它返回了另一个函数b。关于返回b的调用我们可以这样来用。
var newFunc = a();
newFunc();
执行结果为 alert a和alert b
如果不想赋值调用这个函数也可以简写如下
a()();
函数的自身重写
因为函数可以返回一个函数,那就意味着可以用一个新的函数替代一个旧的函数,根据前一个例子来改进一下
a=a();
第一次运行函数a,就alert a,再次运行函数a,就alert b,这个功能对初始化非常有用。这个函数a重写了自己,避免在以后调用初始化的功能(上个例子为alert a)。
当然我们还有更简单的方法来重写a函数那就是在a函数的内部重写它,来看看代码
function a(){
alert("a")
a=function(){
alert("b");
}
}
只有在初次调用a函数才会执行 alert a 在以后的调用中,都会执行alert b
下面结合前几节看个综合的例子
var c = function(){
function a(){
alert('a')
}
function b(){
alert('b')
}
a();
return b;
}();//alert('a');
c();//alert('b');
这个例子有以下几点需要注意
1. a函数,b函数是内部函数。
2. return b 返回的是个函数的引用。
3. 子调用函数重写了函数c。
如果能明白这个例子,关于内部函数,子调用函数和返回值为函数的函数就可以都理解了。
闭包(Closures)闭包属于比较难的一部分,在学习闭包之前,先来学习以下Javascript的作用域(Scope)
作用域链(Scope Chain)
函数内部定义的变量,在函数外不能访问,或者变量定义在代码段中(如if,for中),在代码段之外也不可访问。
var a =1;
function f(){
var b=1;
return a;
}
f();//a=1
b;//b 没有定义
a 是全局变量,而b定义在函数f之中。所以:
在f函数中,a和b都可以访问。
在f函数之外,a可以访问,而b不可以
再次看个例子
var a = 1;
function f(){
var b = 1;
function n(){
var c =3;
}
}
如果定义一个函数n在函数f中,函数n不但可以访问自己作用域的c,而且还能访问所谓的父作用域的b和a
这就是作用域链(Scope Chain)
词法作用域(Lexical Scope)
JavaScript には字句スコープ (Lexical Scope) もあります。これは、関数が呼び出されたときではなく、定義されたときにスコープを生成することを意味します。理解するには例を見てください。
function f1(){var a=1;f2();}
function f2(){return a;}
f1();//a は定義されていません
関数 f1 を見てくださいまず、関数 f2 が呼び出されます。関数のローカル変数 a も f1 にあるため、関数 f2 も a にアクセスすると予想されるかもしれませんが、そうではありません。
現時点では f2 関数が定義されていますが、その範囲内に a が見つからないためです。関数 f1 または関数 f2 のどちらでも、アクセスできるのは自身のローカル変数またはグローバル変数のみです。
クロージャーによるチェーンの切断
例を使ってクロージャを説明してみましょう。
例 1
function f(){
var b="b";
return function(){
return b;
}
関数 f には変数 b が含まれます。はグローバル スコープ内の b にアクセスできず、結果は未定義です。
この関数の戻り値を関数として見てみましょう。この新しい関数は、f のスコープ内の変数 b にアクセスできます。次のコードを見てください。
var n = f();
n();//Access b
関数 f 自体はグローバルであり、それが返す関数は新しいグローバル関数であるため、関数 f のスコープにアクセスできます。
この例の結果は前の例と同じですが、関数 f は関数を返しませんが、新しいグローバル変数 n を作成します。コードは次のとおりです。 >var n;
function f(){
var b = "b";
return b;
それでは、n() を使って関数 f の変数 b にアクセスするように指示してください
上記の 2 つの例を通して、関数が親スコープを指し、その関数がローカル変数を指す場合、それをクロージャーと呼ぶことができると言えます。 クロージャは実際には言い訳、つまり内部変数への外部アクセスの方法を提供します。
パラメータを渡す関数 f を作成すると、このパラメータは関数 f ローカル変数になります。このパラメータを返す f の内部関数を作成できます。 function f(arg){
var n =function(){ return args; arg; n;
}
var m= f(123);//124
ループ内でのクロージャーの適用
表面は正常ですが、リサイクル中にバグが発生しやすいです。
次のコードを見てください
function f(){
var a = [];
var i;
a[0]();//3
a[1]();//3
a[2] ();//3
新しいループを作成します。目的は、ループするたびに新しい関数が作成され、その関数がループのシーケンス値 (i) を返すことです。上記のコード
の実行結果がすべて 3 であることを見てみましょう。そして、期待される結果は 1、2、3 です。
正確にはなぜですか? 3 つの新しいクロージャーを作成し、すべて変数 i を指しました。クロージャは変数 i の値を記憶せず、変数 i への参照のみを行います。ループが終了すると、i の値は 3 になるため、結果はすべて 3 になります。
正しい書き方を見てみましょう
function f() {
var a = [];
for(i = 0; i a[i] = (function(x){
return function(){
alert(x);
return x;
}
})(i);
}
return a;
}
a[0]();//0
a[1]();//1
a[2]();//2
変数 i をローカル変数 x に変換するために再びクロージャを使用しました。x 変数は毎回異なる値を持ちます。自己呼び出し関数がよく分からない場合は、次のように書くと理解できます。
function f() {
function makeClosure(x) {
return function(){
戻り x;
}
}
var a = [];
for(i = 0; i a[i] = makeClosure(i); / /makeClosure、i の値を記憶するために使用されます。
}
を返します
}