從概念上講,函數接受輸入,進行計算,而後產生輸出。下圖是一個函數黑盒示意圖,它計算一個帳戶在t
年以後的餘額,其初始餘額爲p
,年利率爲r
,每一年取n
次複利。
要使用這個函數,只需向函數發送四個數值,並在其迴應信息中獲取計算所得的餘額。函數的用戶看不到其「內在工做」,因此咱們把函數想象成黑盒子。javascript
這裏書本給出一個關於什麼是抽象的說明:在平常生活中到處能夠看到相似的狀況。咱們開車,但並不瞭解內燃機或者氫燃料電池;咱們用微波爐加熱事食物,卻不明白深層的物理學知識;咱們發送即便消息、推文、打電話,卻對文字、聲音的編碼與傳輸方式一無所知。咱們把這種只看事物的主體部分而不關心細節的理念叫作抽象。函數則是對計算的抽象。html
在JavaScript中,函數類型值包含一個可執行的代碼塊,成爲函數體,以及零個或多個輸入,成爲形參(parameter)。下面這個函數只有一個參數,它會計算此參數的三次方。前端
function (x) {return x*x*x;}
函數也是值,和數字、真值、字符串、數組及普通對象同樣。所以,能夠把函數類型值賦給變量。java
var cube = function (x) {return x*x*x;}
argument
)。在被調用事,函數首先把每一個實參值賦給對應的形參,而後執行函數體。若是存在return
語句,它會將計算結果傳回給調用者。下面的腳本展現一個函數定義以及對它的三次調用。程序員
// 定義函數 —— 這時不會運行函數體 var cube = function (x) { return x*x*x; }; // 進三次調用,將函數體運行三次 alert(cube(-2)); alert(cube(10)); alert(("在一個魔方中有" + (cube(3) - 1) + "個立方體 "))
在第一次調用中,咱們向函數cube
傳遞了-2,cube
會把-2賦值給x
,而後計算-2-2-2,並把結果值(-8)返回給調用處。這個值隨後又被傳給alert
函數的調用。
函數也能夠有名字。函數有了名字,在調用時,就不必定要將它賦值給變量了。web
function cube (x) { return x*x*x; }; alert(cube(-2)); // -8
在JavaScript中,這種定義方式成爲函數聲明,相似於(但又不徹底等同於)把函數賦值給一個同名變量。儘管不少程序員喜歡函數聲明的方式,但咱們更喜歡使用變量聲明方式。咱們會在章尾討論。編程
var diceRoll = function () { return 1+Math.floor(6*Math.random()); };
diceRoll()
,而不能寫成diceRoll
。前一個表達式會調用函數,然後一個就是函數自身。var diceRoll = function () { return 1+Math.floor(6*Math.random()); }; alert( diceRoll() ); alert( diceRoll );
這裏都沒什麼問題,我想了想試了下以下函數:segmentfault
function test() { return "fn-test"; }; alert(test);
若是一個函數完成了某主體的執行,卻沒有執行任何return
語句,它會返回undefined
值。這個undefined
值真的只是一個技術術語,由於在調用一個沒有return
語句的函數時,主要是爲了它產生的效果,而不是爲了它產生的任何值。api
我對這就句話理解:若是函數定義時沒有要求返回最終值,則默認返回undefined
。調用一個沒有返回值的函數後面還不是太理解...效果?何種效果?數組
var echo = function (message) { alert(message + "."); alert("I said: "+message+"!"); }; echo("Sanibonani"); // 調用這個函數最天然的方式 var x =echo("Hello"); // 爲x賦值undefined,但在實際中不會發生 console.log(x); // undefined
若是沒有爲函數傳遞足夠了實參值,則額外的形參變量會被初始化爲undefined。
var show = function (x,y) { alert(x+" "+y); }; show(1); // "1 undefined"
// 返回半徑爲r的圓的面積 var circleArea = function (r) { return Math.PI*r*r; }; // 返回y可否被x整除 var divides = function (x,y) { return y % x === 0; };
咱們能夠利用本身編寫的函數來構建其餘函數。
...都是基本的例子略過。
函數語句內隱式類型轉換和優先級與結合性問題!
略.......
將對象引用做爲參數傳送
看一下向函數傳遞對象的狀況
// 返回一個數組中全部元素之和 var sum = function (a) { var result = 0; for (var i=0;i<a.length;i+=1) { result += a[i]; } return result; }; alert(sum([])); alert(sum([10,-3,8]));
再看另外一個例子,它使用了一種徹底不一樣的風格。
// 把一個數組中全部字符串都轉換成大寫 var uppercaseAll = function (a) { for (var i=0;i<a.length;i+=1) { a[i]=a[i].toUpperCase(); } };
區別在於,函數sum返回一個值,而uppercaseAll根本沒有包含return語句!相反,它修改了傳遞給它
// 把一個數組中全部字符串都轉換成大寫 var uppercaseAll = function (a) { for (var i=0;i<a.length;i+=1) { a[i]=a[i].toUpperCase(); } }; var result = uppercaseAll(["a","b","c"]); alert(result); // undefined alert(uppercaseAll(["a","b","c"])); // undefined
自我理解:調用函數後,傳參,計算,由於沒有return
返回值,因此只是計算而已,則uppercaseAll(["a","b","c"])
就會像章開頭說的那樣,默認返回undefined
,而後賦值給變量result
。(最後alert
調用函數證實這一點)。
var uppercaseAll = function (a) { for (var i=0;i<a.length;i++) { a[i]=a[i].toUpperCase(); } }; var dogs = ["spike","spot","rex"]; alert(uppercaseAll(dogs)) // undefined,此值並不表明函數沒有執行,而是執行了未指定返回值,則返回默認值。 alert(dogs); // ["SPIKE","SPOT","REX"],修改了傳入對象的屬性
alert
調用,結果是undefined
。由於調用函數並無返回結果,只會返回undefined
(雖然函數內部的確執行了大寫轉換操做,可是沒有返回值然並卵
),因此最後顯示undefined
。dogs
引用的數組對象已經被修改,即以前說的,它修改了傳遞給它的對象的屬性。由於沒有設置返回值,默認返回的undefined
被上一個alert
函數調用。而dogs
引用的數組被調用結束後因爲沒有返回值,避免了成爲新數組被返回出去。因此大寫字母保留下來。另外一個:
var uppercaseAll = function (a) { var result = []; for (var i=0;i<a.length;i+=1) { result.push(a[i].toUpperCase()) } return result; // 返回的是一個新數組! }; var dogs = ["spike","spot","rex"]; alert(uppercaseAll(dogs)); // ["SPIKE","SPOT","REX"],這裏alert調用的對象,是函數返回的新數組!不是dogs alert(dogs); // ["spike","spot","rex"]
alert
函數接收,顯示處理結果:["SPIKE","SPOT","REX"]
dogs
引用的數組仍是小寫呢?由於調用函數返回的最終值沒有從新賦值給dogs
。換句話說,alert
函數調用的dogs
數組,和uppercaseAll
函數沒有關係。uppercaseAll
執行結束已經返回了一個新數組
。alert(uppercaseAll(dogs));
這段語句的結果,是大寫字母
仍是undefined
,取決因而否對函數設置返回值!!!確保你理解了最後這兩個函數的區別,第一個函數修改了其實參的屬性,第二個函數沒有改動實參,而是
返回一個新的數組
。
// 返回數組中的最大元素 var max = function (a) { var largest = a[0]; for (var i=0;i<a.lengt;ai++) { if (a[i]>largest) { largest = a[i]; } } return largest; }; max([7,19,-22,0]); max(["dog","rat","cat"]);
它能正常工做嗎?
這個函數依靠>
操做符一次比較數組中的連續值,跟蹤當前找到的最大值(從第一個元素a[0]
開始)。如今>
知道如何比較數字與數字、字符串與字符串,但奇怪的是,除非>兩邊的值都是字符串,不然JavaScript會把這兩個值都看做數字(隱式轉換),而後進行相應比較。有時,這種作法是沒問題的。
但若是有一個值被轉換成NaN
,那麼狀況就不妙了。若是x
或y
爲NaN
,表達式x>y
會得出false
。3>NaN
是false
,NaN>3
也是false
!這就表示:
alert(max([3,"dog"])); // 3 alert(max(["dog",3])); // "dog"
3
和"dog"
實際上是不可比較的,因此計算這種數組的最大值基本上沒有什麼意義。那在這種狀況下難道不該當拋出一個異常嗎?不少語言都會這麼作。其餘語言甚至會拒絕運行包含這種比較的程序!而後,JavaScript很愉快地運行了這種比較,而後給出了沒什麼意義的結果,若是願意的話,能夠嘗試在代碼裏探測這些問題。
/*返回數組中的最大元素。若是數組包含了不可比較的元素,函數會返回一個不肯定的任意值*/
函數在這個註釋中承諾:只要調用者僅傳遞有意義的參數,那它就返回最大值;不然契約失效。函數對實參提出了這些約數條件稱爲先決條件
。函數自身不會檢查先決條件,沒有知足先決條件只是會致使未指明的行爲。先決條件是編程圈子很是熟悉並且深入理解的一個術語,因此咱們將爲引入先決條件的註釋採用一種約定。
// 返回數組中的最大元素。先決條件:數組中的全部元素必須是能夠互相比較的
重構
。重構
:重構就是對代碼作結構性的調整,讓其變得更好,通常(但不必定)是將大而混亂的代碼分解成較小的組成部分。在這個案例中咱們要將用戶交互與主要計算區分開
來,將主要計算部分包裝成一個漂亮的函數。// 返回n是否爲質數。先決條件:n是一個大於或等於2的整數,在JavaScript可表示的整數範圍以內。 var isPrime = function (n) { for (var k=2,last=Math.sqrt(n);k<=last;k+=1) { if (n%k===0) { return false; } } return true; };
請務必注意:這個函數只會返回它的實參是否是質數,並不會彈出一條說明判斷結果的消息!其他腳本負責提示輸入、檢查錯誤、報告結果。
var SMALLEST = 2,BIGGEST = 9E15; var n = prompt("輸入一個數組,我會檢查它是否是質數"); if (isNaN(n)) { alert("這不是個數字"); } else if (n<SMALLEST) { alert("我不能檢測這麼小的數字"); } else if (n>BIGGEST) { alert("這個數字對我來講太大了,沒法檢測"); } else if (n%1!==0) { alert("我只能測試整數"); } else { alert(n+"是"+(isPrime(n)? "質數" : "合數")); // 注意這裏若是去掉三目運算符的括號,則會先計算字符串鏈接符,永遠彈出:"質數" }
分離關注點可讓複雜系統變得容易理解。
對於像航天飛機或金融服務系統這樣額大型系統,要理解或診斷其中的某個問題,必須可以肯定一些具備明確行爲的子系統。若是隻是把一個大型系統當作一系列語句的集合,那就永遠沒法真正理解它。
將質數計算放到它本身的函數中,就能生成一段能夠重複使用的代碼,能夠將它放到咱們未來編寫的任意腳本中。咱們已經體驗過函數的複用性
了:咱們已經調用過alert
和Math.sqrt
,卻不須要本身去編寫其中的細節。
但咱們這個質數函數的複用性到底如何呢?調用這個函數的腳本作了不少錯誤檢查。若是真的但願這個函數只需編寫一次,卻能被數百個、數千個腳本調用,那期待這些「調用者」來作一樣的錯誤檢查是否公平呢?固然不公平了。咱們能夠在函數中檢查錯誤。
// 返回實參是否爲2到9e15之間的質數。 // 若是實參不是整數或者超出2到9e15的範圍,則會拋出異常。 var isPrime = function (n) { if (n%1!==0 || n<2 || n>9e15) { throw "這個數組不是整數或者超出範圍"; }; for (var k=2,last=Math.sqrt(n);k<last;k++) { if (n%k===0) { return true; } } return false; };
注意,這個函數在遇到問題時會拋出異常,而不是彈出錯誤提示!這是很關鍵的。要使函數真正實現可複用,它永遠都不該接管用戶交流的責任。(我理解爲錯誤不與交互模塊混用
)
函數的不一樣用戶對錯誤報告可能會有不一樣的要求。有些人會把錯誤寫到網頁的某個位置,有些人可能會把錯誤收集到一個數組中,有些人可能想用別的某種語言博報告錯誤,預測用戶可能使用的每種語言不是這個函數的任務。
當編寫爲調用者計算數值的函數時,應當經過拋出異常來指示錯誤。
本節最後一個例子是一個生成斐波那契數列的函數。斐波那契數列是一個很是值得注意的數列,在天然、音和金融市場中都會出現它的屬性。這個數列的開頭以下:
0,1,1,2,3,5,8,13,21,34,55,89,144,...
數列中每一個值(前兩個值除外)都是前兩個值之和。
f(n)=f(n-1)+f(n-2)
咱們的函數會構造一個數組f,從[0,1]開始,而後不停地把最後一個元素(f[f.length-1])和倒數第二個元素(f[f.length-2])相加。由於函數只能處理整數,因此咱們必須確保結果只不會超過JavaScript能夠連續表達的整數範圍,大約是9e15。就目前來講,咱們先作個弊,只生成其中的前75的數字,由於我知道這些數字是安全的。
// 返回一個數組,其中包含斐波那契數列的前75個數字。即f.length = 75 var fibonacciSequence = function () { var f = [0,1]; for (var i=0;i<=75;i++) { f.push(f[f.length-1]+f[f.length-2]); } alert(f); }; fibonacciSequence();
<!DOCTYPE HTML> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script type="text/javascript"> /*計算*/ function fbnqFn(n) { var fbnqArr = [0,1]; for (var i=0;i<n;i++) { fbnqArr.push(fbnqArr[fbnqArr.length-2]+fbnqArr[fbnqArr.length-1]); } fbnqArr.length = n; return fbnqArr; }; /*交互*/ function client() { var Max = prompt("須要生成多少個斐波那契數?(不能超過150)"); if (isNaN(Max)===true || Max%1!==0 || Max<0 || Max>150) { throw "輸入數字不能是非數字、整數、負數且不能超過150"; } var test = fbnqFn(Max); console.log(test+" | "+test.length); }; // start client(); </script> </body> </html>
利用函數,能夠將其任意的計算進行打包,在調用者看來,就是一條單獨地簡單命令。請看如下計算階乘的函數:
// 返回n的階乘。先決條件:n是一個介於0到21之間的整數(包含0和21)。超過21,返回近似值 var factorial = function (n) { var result = 1; for (var i=1;i<=n;i++) { result *= i; } return result; };
這個函數聲明瞭一個形參n,以及它本身的兩個變量:i和result。在函數內部聲明的變量成爲局部變量,和形參同樣,屬於函數本身,與腳本其餘位置的同名變量徹底無關。這點很是好,請看:
var result = 100; alert(factorial(5)); // 120 alert(result); // 100
咱們不會希全局變量result
僅僅由於咱們計算了一次階乘就發生改變。腳本的不一樣部分每每是由不一樣人編寫的。編寫函數調用部分的做者徹底不知道在函數中會用到哪些變量。若是你調用了alert
函數,而它改變了你的某些變量,你確定會不高興。
在JavaScript中,在函數內部聲明的變量以及函數的形參均擁有函數做用域,而在函數以外聲明的變量則擁有全局做用域,成爲全局變量。擁有函數做用域的變量只在聲明它們的函數中可見,與外部世界隔離,就像咱們前面看到的那樣。下面這段很是簡短的腳本更清地代表了這一點。
var message = "冥王星只是一個矮行星"; var warn = function () { var message = "你立刻要看到一些爭議性的東西"; alert(message); }; warn(); // "你立刻要看到一些爭議性的東西" alert(message); // "冥王星只是一個矮行星"
這裏有兩個恰巧同名的不一樣變量。全局變量的做用域開始於它的聲明位置,一直延伸到腳本結束,而局部變量的做用域則是聲明它的函數體內部。在這種狀況下,局部變量和全局變量的名字相同(message),其做用域重疊。在重疊區域中,最內層的聲明優先。
var warning = "不要雙擊提交按鈕"; var warn = function () { alert(warning); // 這裏能夠看到全局變量 }; warn(); // "不要雙擊提交按鈕" alert(warning); // "不要雙擊提交按鈕"
能在函數訪問全局變量並無什麼使人驚訝的。實際上,咱們已經用過了不少全局變量:alert
、prompt
、isNaN
、Math
等等。若是不容許在函數中用它們,要完成任何事情都會面臨巨大的阻礙。可是,這也意味着一個潛在的問題。
var message = "新遊戲的時間"; var play = function () { message = "正在玩"; // 沒有聲明 alert(message); }; alert(message); play(); alert(message); play();
上面腳本定義了一個message變量,它的值由函數更新。在函數中修改全局變量幾乎總被認爲是很是差的編程實踐:腳本中的函數進行"相互交流"的正確作法是經過函數實參和返回值,而不是經過全局變量。程序應當儘可能少的使用全局變量:
儘可能減小全局變量的使用。具體來講,函數應該經過參數和返回值進行"交流",而不是經過更新全局變量。
JavaScript中局部變量的做用域包含了聲明它們的整個函數體,這一事實又會致使另外一種可能狀況:全局變量是在聲明以後纔會出現,而局部變量則是在其函數開始執行時就立刻存在的,即使變量是在函數體中間聲明的。考慮如下代碼:
var x = 1; // 在此處,全局變量x已經存在,而全局變量y則還沒有存在 // 在此處使用y則會拋出一個ReferenceError引用錯誤 var y = 2; // 此時全局變量y已經存在 var f = function () { alert(z); // 沒有錯誤,顯示undefined var z = 3; alert(y+3); // 5 }; f();
其實上面例子有一個變量提高
的問題,根據變量提高機制,var
會提高到當前做用域的頂端,z
的做用域是f
所包含的區塊,因此你的代碼等價於
var x = 1; var y = 2; var f = function () { var z; // 會把 var 聲明提高到最高的位置 這種特性叫作 變量提高 此時聲明瞭 z 可是爲定義值 因此z的值是 undefined alert(z); z = 3; alert(y+3); }; f();
當調用函數時,JavaScript引擎會在該處建立一個對象,用以保存函數的形參和局部變量。形參會被當即初始化,得到調用時所傳實參值的副本,全部局部變量會被馬上初始化爲undefined
(這裏不是先初始化再賦值的?)。上面例子裏,在z聲明前就引用了它,但並無拋出ReferenceError
,其緣由就在於此。可是,儘管你知道局部變量在聲明以前便可調用,但這並不意味着就應該使用處於未定義狀態的局部變量。事實上,故意在定義變量以前就使用它們,幾乎可讓全部閱讀你代碼的人產生混淆,因此這被認爲是很是差的風格。不少JavaScript風格指南甚至直接認定這是一種錯誤;JSLint甚至包含了一項設置,專門用於檢查這一狀況。
練習(包含變量聲明提高和函數聲明提高問題)
函數內部定義的變量只對函數內部可見,對外部不可見
或,函數內部定義的變量、對象。使其能被外部發現,使用的範圍。
var x = 1; var f = function (y) { alert(x+y); }; f(2); // 3
那麼按照變量提高,其實是:
var x = 1; var f = function (y) { var y; // 聲明提高 y = 2; // 得到調用函數傳入的實參 alert(x+y); // 這裏的y引用的是全局變量y }; f(2);
若是把形參y
更名爲x
,腳本會提示什麼?
var x = 1; var f = function (x) { alert(x+y); }; f(2);
實際上會報錯,由於變量y
沒有定義
var x = 1; var f = function (x) { var x; x = 2; alert(x+y); };
JavaScript是每個值,只要它不是undefined、null、布爾值、數字和字符串,那它就是一個對象。所以,函數值也是對象,並且跟全部對象同樣,也能夠有屬性。它們還能夠像其餘值同樣,其自己是其餘對象的屬性。
知道函數是對象以後,天然會問,函數有那些屬性?
函數屬性的其餘用途包括:計算生成特定結果的次數、記住函數在給定實參下的返回值,以及定義與特定對象集合相關的數據。
當建立了函數對象以後,JavaScript會其初始化兩個屬性。第一個是length
,初始值爲函數的形參
個數。
var average = function (x,y) { return (x+y)/2; }; alert(average.length); // 2,一個用於x,一個用於y
第二個預約義屬性是prototype
,以後在討論
因爲函數也是值,因此能夠做爲對象的屬性。把函數放在對象內部有兩個主要理由,第一個理由是把許多相關函數放在一組。例如:
var geometry = { circleArea:function (radius) { return Math.PI*radius*radius; }, circleCircumference:function (radius) { return 2*Math.PI*radius; }, sphereSurfaceArea:function (radius) { return 4*Math.PI*radius*radius; }, boxVolume:function (length,width,depth) { return length*width*depth; } };
把許多函數組合到單個對象中,有助於組織和理解大型程序。人類不但願去嘗試理解一個擁有數百個甚至數千個函數的系統,若是一個系統只有數十個軟件組成部分,那咱們理解起來會容易不少。例如,在一個遊戲程序中,咱們會很天然地爲玩家、地貌、物理屬性、消息傳遞、裝備、圖像等分別建立出子系統,每一個都是一個很大的對象。
面向過程
轉向面向對象
。例如,咱們不必定要將函數看做對形狀執行操做,將函數存儲爲形狀的屬性。將函數放在對象的內部,可讓人們專一於這些函數,讓函數扮演對象行爲的角色。 var circle = { radius:5, area:function () { return Math.PI*this.radius*this.radius; }, circumference:function () { return 2*Math.PI*this.radius; } }; alert(circle.area()); // 78.53981633974483 circle.radius = 1.5; alert(circle.circumference()); // 9.42477796076938
this
表達式,這是一個至關強大的表達式,能夠根據上下文表達出不一樣含義。當一個調用中引入了包含函數的對象時(就如上面的circle.area()
),this
指的就是這個包含函數的對象。使用
this
表達式的函數屬性稱爲方法
。所以,咱們說circle
有一個area
方法和一個circumference
方法。
談談本身理解,有大神有別的建議歡迎評論
更好的運用面向對象編程思惟
對象在建立時自帶操做函數(屬性),存儲在對象內部,做爲對象的一部分存在。
var x = 2; var p = { x:1, y:1, z:function () { return x + this.x; // x引用的是全局變量,this.x指向的是p.x }, }; alert(p.z()); // 3
在上一節,咱們僅定義了一個circle
圓對象。但若是須要不少個圓,怎麼辦?
// 錯誤的示範 var Circle = function (r) { return { radius:r, area:function () { return Math.PI*this.radius*this.radius; }, circumference:function () { return 2*Math.PI*this.radius; } }; }; var c1 = Circle(2); // 建立一個半徑爲2的圓 var c2 = Circle(10); // 建立一個半徑爲10的圓 alert(c1.area()) // "314.1592653589793"
這段代碼表面上看沒問題,但有一個缺陷。每次建立一個圓,也另行建立了額外的面積和周長方法。
在建立多個圓時,會浪費大量的內存來保存面積和周長函數的冗餘副本——這是很糟糕的事情,由於內存資源是有限的。當腳本耗盡內存就會崩潰。型號,JavaScript的原型prototype
提供了一種解決方案。
// 一個圓的原型,其設計目的是做爲下面用Circle函數建立的全部圓的原型 var protoCircle = { radius:1, area:function () {return Math.PI*this.radius*this.radius;}, circumference:function () {return 2*Math.PI*this.radius;} }; // 建立具備給定半徑的圓 var Circle = function (r) { var c= Object.create(protoCircle); // 將protoCircle原型建立到變量c中 c.radius = r; // c的_proto_指向protoCircle對象 return c; };
Circle
建立的圓都有本身的radius
屬性和一個隱藏連接,指向一個惟一的共享原型
,其中包含了area
和circumference
函數(分別只有一個)。這是極好的,不過還只是有小小缺陷。咱們使用了兩個全局變量Circle
和protoCircle
。若是隻有一個就更好了,這樣可讓咱們的原型圓做爲Circle
函數的一個屬性。咱們如今就有了一模式,用於很方便的定義一系列同種"類型"
的對象。/* 一個圓數據類型。概要: * * var c = Circle(5); * c.radius => 5 * c.area() => 25pi * c.circumference() => 10pi */ var Circle = function (r) { var circle = Object.create(Circle.prototype); circle.radius = r; return circle; }; Circle.prototype = { area:function () {return Math.PI*this.radius*this.radius}, circumference:function () {return 2*Math.PI*this.radius}, };
咱們能夠應用這一模式,生成一個用於建立矩形的函數。
/* 矩形數據類型。概要: * * var r = Rectangle(5,4); * r.width => 5 * r.height => 4 * r.area() => 20 * r.perimeter() => 18 */ var Rectangle = function (w,h) { var rectangle = Object.create(Rectangle.prototype); rectangle.width = w; rectangle.height = h; return rectangle; }; Rectangle.prototype = { area:function () {return this.width*this.height}; perimeter:function () {return 2*(this.width+this.height)} };
全新方式:JavaScript中的每一個函數對象都自動包含一個prototype
屬性,prototype
是函數兩個預約義屬性中的第二個,第一個length
。只要函數一經定義,它的prototype
屬性就會被初始化爲一個全新對象。(這個全新對象有本身的一個屬性,叫作constructor
)。
下圖展現了一個新鮮出爐的函數,用於算兩個值的平均值。
其次在使用函數建立對象時,只要是用來魔法操做符new,就無需明確鏈接原型,也無需返回新建立對象。當你在函數調用以前加上了new時,會發生三件事情。
JavaScript會建立一個全新的空對象,而後使用引用這個新對象的表達式this來調用此函數。
該構造對象的原型被設定爲函數的prototype屬性。
該函數會自動返回新的對象(除非你明確要求函數返回其餘東西)
這些規則看上去很複雜,但看一個例子就清楚了。
產生一個圓的函數,如何使用new操做符來調用該函數,建立的圓的實例
/*一個圓數據類型。概要: * var c = new Circle(5); * c.radius => 5 * c.area() => 25pi * c.circumference() => 10pi */ var Circle = function (r) { this.radius = r; }; Circle.prototype.area = function () { return Math.PI*this.radius*this.radius; }; Circle.prototype.circumference = function () { return 2*Math.PI*this.radius; }; var c1 = new Circle(2); // 建立半徑爲2的圓 var c2 = new Circle(10); // 建立半徑爲10的圓 alert(c2.area()); // "314.1592653589793"
此腳本先建立一個函數對象,咱們將用變量Circle
引用它。和全部函數同樣,建立它時,擁有一個第二對象,這個對象被prototye
屬性引用。隨後,咱們向這個原型對象添加area
和circumference
函數。接下來咱們調用new Circle
建立一對圓對象。操做符new
建立新的對象,這個對象其原型爲Circle.prototype
。
根據設計,諸如Circle
這樣的函數就是要用new
調用的,這種函數稱爲構造器。根據約定,咱們用大寫首字母命名,並省略return
語句,優先使用JavaScript的自動功能返回新建立的對象。之因此要約定使用大寫首字母,緣由在下一節給出。
沒有return
語句的構造器調用將返回對象,而不是返回一般的undefined
,新建立對象的原型將被神奇地指定給一個歷來不會顯式建立的對象。
Object.create
的緣由之一。一些JavaScript程序員建議對於新腳本僅使用Object.create
,由於這樣可讓對象與其原型之間的連接更爲明確。明確的代碼更易讀易懂易於處理。堅持使用Object.create
的另外一個緣由多是出於哲學考慮:咱們能夠直接用對象來考慮問題,而不用另行引用「類型」的概念。可是,咱們不能放棄構造器和操做符new
。JavaScript從一開始就在使用它們,數以千計的現有腳本中都使用了它們,JavaScript的許多內置對象都是經過這些方式構建的,因此咱們須要真正理解它們。經過一些練習能夠熟悉它們,對目前來講,請複習如下步驟。
new
建立和使用一種自定義數據類型,好比圓: this.radius = r
這樣的賦值語句,爲每一個圓初始化一個獨有的屬性;Circle.prototype
;new Circle()
來建立特定圓。對於如此建立的每一個圓,其原型將自動變爲Circle.prototype
apply
和call
)前面的在JavaScript——this、全局變量和局部變量混談中已經給出前兩種規則(全局做用域和函數做用域下的this引用),接下來要說一個注意點。
3
:當用一個以new
操做符調用的函數中時,this
引用指的是新建立的對象。var Point = function (x,y ) { this.x = x; this.y = y; }; var p = new Point(4,-5); // 新的實例 var q = Point(3,8); // 這裏修改了全局變量x和y!
上面的最後一行代表,咱們必定要很是注意,老是以new
來調用構造器,以避免修改了已有的全局變量,致使腳本運行失控。爲減小發生這種意外的可能性,JavaScript程序員用大寫字母書寫構造器的名字。可使用一些工具(JSLint)來掃描代碼,不要調用函數而不使用new前綴,很危險!
4
:利用函數方法apply
和call
,能夠專門定義一個但願用做this
值的對象。var f = function (a,b,c) { this.x += a+b+c; }; var a = {x:1,y:2}; f.apply(a,[10,20,5]); // 調用f(10,20,5),以"a"爲this f.call(a,3,4,15); // 調用f(3,4,15),以"a"爲this alert(a.x); // 58 var Point = function (x,y) { this.x = x; this.y = y; }; var p = {z:3}; Point.apply(p,[2,9]); // 如今p爲{x:2,y:9,z:3} Point.call(p,10,4); // 如今p爲{x:10,y:4,z:3}
這些方法容許借用(或劫持)現有的方法和構造器,將它們用於一些原本沒打算爲其使用的對象。這些方法稍有不一樣:call
會傳送其實參,而apply
會將實參打包放在一個數組中。
this
引用有哪四種應用?分別對應不一樣的做用域和上下文中,全局做用域、被當作方法調用、new
構造函數調用、apply
與call
。
var p = { x:1, f:function (y) { this.x += y; return this.x; } }; var q = {x:5}; alert(p.f(1)); alert(p.f.call(q,3));
先分析第一個alert
:既然是p.f
,接收方對象是p
,則this
引用指向p
。且本來的p
對象中,局部變量x
值爲1,執行函數傳參後,1 += 1;因此最後p.x
的值爲2。
第二個alert
一樣是p.f
,只是此次用了call
方法,這個方法能夠借用現有的方法和構造器,也就是說,p.f
這個方法被借用了,給誰呢?對了括號內的q
對象,並傳參3,此時this
引用指向了q
對象(注意當調用的一瞬間,this
已經指向了q
對象),且q
對象已經有q.x=5
,傳參相加,最終結果q.x
值爲8。
考慮下面兩個函數:
var squareAll = function (a) { var result = []; for (var i=0;i<a.length;i+=1) { result[i]=a[i]*a[i]; } return result; }; var capitalizeAll = function (a) { var result = []; for (var i=0;i<a.length;i+=1) { result[i]=a[i].toUpperCase(); } return result; };
這兩個函數只有很小的一點不一樣,他們都是向一個數組中的每一個元素應用一個函數,並收集結果;可是,第一個函數是計算這些元素的平方,而第二個函數則是將這些元素變爲大寫。咱們能不能僅爲共同結構編寫一次代碼,而後用參數來實現它們之間的小小區別?
var collect = function (a,f) { var result = []; for (var i=0;i<a.length;i+=1) { result[i]=f(a[i]); } return result; };
對每一個數組元素實際執行的函數(好比求平方或轉換爲大寫)如今做爲實參傳送。
var square = function (x) {return x*x}; var capitalize = function (x) {return x.toUpperCase();}; var squareAll = function (a) {return collect(a,square);}; var capitalizeAll = function (a) {return collect(a,capitalize)};
對於這些小小的square和capitalize函數,咱們甚至能夠不爲其聲明變量。
var squareAll = function (a) { return collect(a,function (x) {return x*x};) }; var capitalizeAll = function (a) { return collect(a,function (x) {return x.toUpperCase();}); };
好,來看看它們如何工做的。
var arr1 = [-2,5,0]; var arr2 = ["hi","ho"]; alert(squareAll(arr1)); alert(capitalizeAll(arr2));
函數f接受一個另外一個函數g做爲其實參(並在本身體內調用g),這種函數f稱爲高階函數。函數collect稱爲高階函數,內置的sort也是如此。咱們能夠向sort傳送一個比較函數,使它採用不一樣的排序方式。比比較函數就是咱們本身編寫的一個兩實參函數,當第一個實參小於第二個時返回一個負值,當兩個實參相等時返回0,當第一個實參較大時則返回一個正值。
var a = [3,6,10,1,40,25,8,73]; alert(a.sort()); // 按字母排序 alert(a.sort(function (x,y) {return x-y;})); // 按數值遞增排序 alert(a.sort(function (x,y) {return y-x;})); // 按數值遞減排序
由於咱們能夠告訴sort
函數,按照咱們喜歡的任意方式來比較元素,因此能夠編寫一些代碼,用幾種不一樣方式對一組對象進行排序。
在web頁上設置定時器、與用戶操做進行交流時,常常會傳送函數。它也是人工智能編程中最爲重要的程序設計範例之一。且有助於構建很是大的分佈式應用程序。
高階函數一詞不只適用於以函數爲實參的函數,還適用於返回函數的函數。
var withParentheses = function (s) {return "("+s+")";}; var withBrackets = function (s) {return "["+s+"]";}; var withBraces = function (s) {return "{"+s+"}";};
這三個函數很是相似。能夠怎樣進行重構呢?這三個函數中的每個均可以由另外一函數構造而成,只需告訴構造者要使用那種分隔符便可。
var delimitWith = function (prefix,suffix) { return function (s) {return prefix+s+suffix;} }; var withParentheses = delimitWith("("+s+")"); var withBrackets = delimitWith("[","]"); var withBraces = delimitWith("{","}");
withParentheses、withBrackets、withBraces
這三個函數都成爲閉包。粗略的說,JavaScript閉包是一種函數,它的函數體使用了來自外圍(enclosing)函數的變量。閉包在一些很是高級複雜的java結構中扮演着不可或缺的角色。
function circleArea(x) { return Math.PI*Math.pow(x,2) };
這種形式的函數官方名稱爲函數聲明,它的工做方式與前者很是類似,可是這兩種定義形式是不一樣的。
具體來講,函數聲明只能出如今腳本中的全局位置,或者出如今一個函數體的"頂級",不容許只出如今語句內部。根據官方的EA規範,下面代碼出現移一處語法錯誤:
if (true) { function successor() {return x+1;} // 不容許 }
這裏的戒律是:即使瀏覽器容許,也絕對不要將函數聲明放在一條語句內。不管是否選擇使用函數聲明,它們的存在都會影響咱們編寫特定表達式的方式,由於函數聲明一以單詞function開頭,因此JavaScript的設計者決定任何語句都不能以這個單詞開頭,以避免讀者混淆。