JavaScript入門之基本函數詳解

來源:互聯網
上載者:User

總的來說,函數在JavaScript中可以:

◆ 被賦值給一個變數

◆ 被賦值為對象的屬性

◆ 作為參數被傳入別的函數

◆ 作為函數的結果被返回

◆ 用字面量來建立

函數對象

1.1 建立函數

建立JavaScript函數的一種不長用的方式(幾乎沒有人用)是通過new操作符來作用於Function“構造器”:

複製代碼 代碼如下:var funcName = new Function( [argname1, [... argnameN,]] body );

參數列表中可以有任意多的參數,然後緊跟著是函數體,比如:

複製代碼 代碼如下:var add = new Function("x", "y", "return(x+y)");
print(add(2, 4));

將會列印結果:

6

但是,誰會用如此難用的方式來建立一個函數呢?如果函數體比較複雜,那拼接這個String要花費很大的力氣,所以JavaScript提供了一種文法糖,即通過字面量來建立函數:

複製代碼 代碼如下:function add(x, y){
return x + y;
}

或:

複製代碼 代碼如下:var add = function(x, y){
return x + y;
}

事實上,這樣的文法糖更容易使傳統領域的程式員產生誤解,function關鍵字會調用Function來new一個對象,並將參數表和函數體準確的傳遞給Function的構造器。

通常來說,在全域範圍(範圍將在下一節詳細介紹)內聲明一個對象,只不過是對一個屬性賦值而已,比如上例中的add函數,事實上只是為全域對象添加了一個屬性,屬性名稱為add,而屬性的值是一個對象,即function(x, y){return x+y;},理解這一點很重要,這條語句在文法上跟:

複製代碼 代碼如下:var str = "This is a string";

並無二致。都是給全域對象動態增加一個新的屬性,如此而已。

為了說明函數跟其他的對象一樣,都是作為一個獨立的對象而存在於JavaScript的運行系統,我們不妨看這樣一個例子:

複製代碼 代碼如下:function p(){
print("invoke p by ()");
}

p.id = "func";
p.type = "function";

print(p);
print(p.id+":"+p.type);
print(p());

沒有錯,p雖然引用了一個匿名函數(對象),但是同時又可以擁有屬性,完全跟其他對象一樣,運行結果如下:

function (){
print("invoke p by ()");
}
func:function
invoke p by ()

1.2 函數的參數

在JavaScript中,函數的參數是比較有意思的,比如,你可以將任意多的參數傳遞給一個函數,即使這個函式宣告時並未制定形式參數,比如:

複製代碼 代碼如下:function adPrint(str, len, option){
var s = str || "default";
var l = len || s.length;
var o = option || "i";

s = s.substring(0, l);
switch(o){
case "u":
s = s.toUpperCase();
break;
case "l":
s = s.toLowerCase();
break;
default:
break;
}

print(s);
}

adPrint("Hello, world");
adPrint("Hello, world", 5);
adPrint("Hello, world", 5, "l");//lower case
adPrint("Hello, world", 5, "u");//upper case

函數adPrint在聲明時接受三個形式參數:要列印的串,要列印的長度,是否轉換為大小寫標記。但是在調用的時候,我們可以按順序傳遞給adPrint一個參數,兩個參數,或者三個參數(甚至可以傳遞給它多於3個,沒有關係),運行結果如下:

Hello, world
Hello
hello
HELLO

事實上,JavaScript在處理函數的參數時,與其他編譯型的語言不一樣,解譯器傳遞給函數的是一個類似於數組的內部值,叫arguments,這個在函數對象產生的時候就被初始化了。比如我們傳遞給adPrint一個參數的情況下,其他兩個參數分別為undefined.這樣,我們可以才adPrint函數內部處理那些undefined參數,從而可以向外部公開:我們可以處理任意參數。

我們通過另一個例子來討論這個神奇的arguments:

複製代碼 代碼如下:function sum(){
var result = 0;
for(var i = 0, len = arguments.length; i < len; i++){
var current = arguments[i];
if(isNaN(current)){
throw new Error("not a number exception");
}else{
result += current;
}
}

return result;
}

print(sum(10, 20, 30, 40, 50));
print(sum(4, 8, 15, 16, 23, 42));//《迷失》上那串神奇的數字
print(sum("new"));

函數sum沒有顯式的形參,而我們又可以動態傳遞給其任意多的參數,那麼,如何在sum函數中如何引用這些參數呢?這裡就需要用到arguments這個偽數組了,運行結果如下:

150
108
Error: not a number exception

函數範圍

範圍的概念在幾乎所有的主流語言中都有體現,在JavaScript中,則有其特殊性:JavaScript中的變數範圍為函數體內有效,而無塊範圍,我們在Java語言中,可以這樣定義for迴圈塊中的下標變數:

public void method(){
for(int i = 0; i < obj1.length; i++){
//do something here;
}
//此時的i為未定義
for(int i = 0; i < obj2.length; i++){
//do something else;
}
}
而在JavaScript中:

複製代碼 代碼如下:function func(){
for(var i = 0; i < array.length; i++){
//do something here.
}
//此時i仍然有值,及I == array.length
print(i);//i == array.length;
}

JavaScript的函數是在局部範圍內啟動並執行,在局部範圍內啟動並執行函數體可以訪問其外層的(可能是全域範圍)的變數和函數。JavaScript的範圍為詞法範圍,所謂詞法範圍是說,其範圍為在定義時(詞法分析時)就確定下來的,而並非在執行時確定,如下例:

複製代碼 代碼如下:var str = "global";
function scopeTest(){
print(str);
var str = "local";
print(str);
}

scopeTest();

運行結果是什麼呢?初學者很可能得出這樣的答案:

global
local

而正確的結果應該是:

undefined
local

因為在函數scopeTest的定義中,預先訪問了未聲明的變數str,然後才對str變數進行初始化,所以第一個print(str)會返回undifined錯誤。那為什麼函數這個時候不去訪問外部的str變數呢?這是因為,在詞法分析結束後,構造範圍鏈的時候,會將函數內定義的var變數放入該鏈,因此str在整個函數scopeTest內都是可見的(從函數體的第一行到最後一行),由於str變數本身是未定義的,程式順序執行,到第一行就會返回未定義,第二行為str賦值,所以第三行的print(str)將返回”local”。

函數上下文

在Java或者C/C++等語言中,方法(函數)只能依附於對象而存在,不是獨立的。而在JavaScript中,函數也是一種對象,並非其他任何對象的一部分,理解這一點尤為重要,特別是對理解函數式的JavaScript非常有用,在函數式程式設計語言中,函數被認為是一等的。

函數的上下文是可以變化的,因此,函數內的this也是可以變化的,函數可以作為一個對象的方法,也可以同時作為另一個對象的方法,總之,函數本身是獨立的。可以通過Function對象上的call或者apply函數來修改函數的上下文:

call和apply

call和apply通常用來修改函數的上下文,函數中的this指標將被替換為call或者apply的第一個參數,我們不妨來看看JavaScript入門之對象與JSON中的例子:

//定義一個人,名字為jack
var jack = {
name : "jack",
age : 26
}

//定義另一個人,名字為abruzzi
var abruzzi = {
name : "abruzzi",
age : 26
}

//定義一個全域的函數對象
function printName(){
return this.name;
}

//設定printName的上下文為jack, 此時的this為jack
print(printName.call(jack));
//設定printName的上下文為abruzzi,此時的this為abruzzi
print(printName.call(abruzzi));

print(printName.apply(jack));
print(printName.apply(abruzzi));
只有一個參數的時候call和apply的使用方式是一樣的,如果有多個參數:

setName.apply(jack, ["Jack Sept."]);
print(printName.apply(jack));

setName.call(abruzzi, "John Abruzzi");
print(printName.call(abruzzi));
得到的結果為:

Jack Sept.
John Abruzzi
apply的第二個參數為一個函數需要的參數組成的一個數組,而call則需要跟若干個參數,參數之間以逗號(,)隔開即可。

使用函數

前面已經提到,在JavaScript中,函數可以

◆ 被賦值給一個變數

◆ 被賦值為對象的屬性

◆ 作為參數被傳入別的函數

◆ 作為函數的結果被返回

我們就分別來看看這些情境:

賦值給一個變數:

//聲明一個函數,接受兩個參數,返回其和
function add(x, y){
return x + y;
}

var a = 0;
a = add;//將函數賦值給一個變數
var b = a(2, 3);//調用這個新的函數a
print(b);
這段代碼會列印”5”,因為賦值之後,變數a引用函數add,也就是說,a的值是一個函數對象(一個可執行代碼塊),因此可以使用a(2, 3)這樣的語句來進行求和操作。

賦值為對象的屬性:

複製代碼 代碼如下:var obj = {
id : "obj1"
}

obj.func = add;//賦值為obj對象的屬性
obj.func(2, 3);//返回5

事實上,這個例子與上個例子的本質上是一樣的,第一個例子中的a變數,事實上是全域對象(如果在用戶端環境中,表示為window對象)的一個屬性。而第二個例子則為obj對象,由於我們很少直接的引用全域對象,就分開來描述。

作為參數傳遞:

//進階列印函數的第二個版本
function adPrint2(str, handler){
print(handler(str));
}

//將字串轉換為大寫形式,並返回
function up(str){
return str.toUpperCase();
}

//將字串轉換為小寫形式,並返回
function low(str){
return str.toLowerCase();
}

adPrint2("Hello, world", up);
adPrint2("Hello, world", low);
運行此片段,可以得到這樣的結果:

HELLO, WORLD
hello, world

應該注意到,函數adPrint2的第二個參數,事實上是一個函數,將這個處理函數作為參數傳入,在adPrint2的內部,仍然可以調用這個函數,這個特點在很多地方都是有用的,特別是,當我們想要處理一些對象,但是又不確定以何種形式來處理,則完全可以將“處理方式”作為一個抽象的粒度來進行封裝(即函數)。

作為函數的傳回值:

先來看一個最簡單的例子:

複製代碼 代碼如下:function currying(){
return function(){
print("curring");
}
}

函數currying返回一個匿名函數,這個匿名函數會列印”curring”,簡單的調用currying()會得到下面的結果:

複製代碼 代碼如下:function (){
print("curring");
}

如果要調用currying返回的這個匿名函數,需要這樣:

currying()();
第一個括弧操作,表示調用currying本身,此時傳回值為函數,第二個括弧操作符調用這個傳回值,則會得到這樣的結果:

currying

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.