學習javscript函數筆記(二)
定義: 函數包含一組語句,他們是JavaScript的基礎模塊單元,用於代碼復用、信息隱藏和組合調用。函數用於指定對象的行為。
1.函數對象
JavaScript中的函數就是對象,函數對象連接到Function.prototype(該原型對象本身連接到Object.prototype)。沒個函數創建時會附加兩個隱藏屬性:函數的上下文和實現函數行為的代碼。
因為函數是對象,所以它們可以像任意其他的值一樣被使用。函數可以保存在變量、對象和數組中。函數可以被當做參數傳遞給其他函數,函數也可以在返回函數。因為函數是對象,所以函數可以擁有方法。
函數的與眾不同在於它們可以被調用。
2.函數字面量
函數對象通過函數字面量來創建:
例: var add = function (a,b) { return a+b; };
函數字面量包括四部分
a. 保留字function;
b. 函數名,它可以被省略。 如果沒有給函數命名,被稱為匿名函數。
c. 包括在圓括號中的一組參數,多個參數用逗號分開。這些參數的名稱將被定義為函數中的變量,他們不像普通的變量那樣將初始化為undefined,而是在該函數調用時初始化為實際提供的參數值。
d. 包括在花括號中的一組語句。這些語句是函數的主體,它們在函數被調用時執行。
函數字面量可以出現在任何允許表達式出現的地方。函數也可以被定義在其他函數中。一個內部函數除了可以訪問自己的參數和變量,同時它也能訪問把它嵌套在其中的父函數的參數與變量。通過函數字面量創建的函數對象包含一個連到外部上下文的連接。這被稱為閉包。
3.調用
調用一個函數會暫停當前函數的執行,傳遞控制權和參數給新函數。除了聲明時定義的形式參數,每個函數還都接受兩個附加的參數:this和arguments。函數有四種調用模式:方法調用模式、函數調用模式、構造器調用模式和apply調用模式。這些模式在如何初始化關鍵參數this上存在差異。
每個表達式產生一個參數值。每個參數值被賦予函數聲明時定義的形式參數名。當實際參數(arguments)的個數與形式參數(paramertes)的個數不匹配時,不會導致運行時錯誤。如果實際參數值過多,超出的參數值會被忽略。如果實際參數值過少,缺失的值會被替換為undefined。對參數不會進行類型檢測:任何類型的值都可以被傳遞給任何參數。
a.方法調用模式
當一個函數被保存為對象的一個屬性時,我們稱它為一個方法。當一個方法被調用時,this被綁定到該對象。
例: var myObject = { value: 0, increment: function (inc) { this.value += typeof inc === ‘number‘ ? inc : 1; } }; myObject .increment(); document.writeln(myObject .increment()) //1 myObject .increment(2); document.writeln(myObject .increment()) //3
b.函數調用模式
當一個函數並非對象的屬性時,那麽他就是被當作一個函數來調用的。 以此模式調用時,this被綁定到全局對象。 (這是語言設計上的一個錯誤,這個設計錯誤的後果就是方法不能利用內部函數來幫助它工作)
例: var sum = add(3,4) //sum的值為7
解決這設計的錯誤方法: 如果該方法定義一個變量並給它賦值為this,那麽內部函數就可以通過那個變量訪問到this。按約定,把那個變量命名為that;
//給myObject添加一個double方法 myObject.double = function () { var that = this; //解決方法 var helper = function () { that.value = add(that.value,that.value); }; helper(); //以函數的形式調用helper. }; //以方法的形式調用double. myObject.double(); document.writeln(myObject.value); //6
c.構造器調用模式
javascript是一門基於原型繼承的語言。意味著對象可以直接從其他對象繼承屬性。該語言是無類型的。
如果在一個函數前面帶上new來調用,那麽背地裏將會創建一個連接到該函數的prototype成員的新對象,同時this會被綁定到那個新的對象上。
例: //創建一個名為Quo的構造器函數,它構造一個帶有status屬性的對象。 var Quo = function (string) { this.status = string; }; //給Quo的所有實例提供一個名為get_status的公用方法。 Quo.prototype.get_status = function () { return this.status; }; //構造一個Quo的實例 var myQuo = new Quo("confused"); document.writeln(myQuo.get_status()); //打印顯示"confused"
一個函數,如果創建的目的就是希望結合new前綴來調用,那它就被稱為構造器函數。按照約定,他們保存的一大寫格式命名的變量裏。如果調用構造器函數時沒有在前面加上new,可能發生非常糟糕的事情,既沒有編譯時警告,也沒有運行時警告,所以大寫約定非常重要。(有更好的代替方式。)
d.apply調用模式
apply方法讓我們構建一個參數數組傳遞給調用函數。它也允許我們選擇this的值。apply方法接受兩個參數,第一個是要綁定給this的值,第二個是一個參數數組。
例: //構造一個包含兩個數字的數組,並將它們相加。 var array = [3,4]; var sum = add.apply(null, array); //sum值為7. //構建一個包含status成員的對象。 var statusObject = { status: ‘A-OK‘ }; //用statusObject 並沒有繼承自Quo.prototype,但我們可以在statusObject 上調用get_statust 方法,盡管statusObject 並沒有一個名為get_status的方法。 var status = Quo.prototype.get_status.apply(statusObject); //status 值為‘A-OK‘。
4.參數
當函數被調用時,會的到一個“免費”配送參數,那就是arguments數組。函數可以通過此參數訪問所有它被調用時傳遞給他的參數列表,包括哪些沒有被分配給函數聲明時定義的形式參數的多余參數。這是的編寫一個無需指定參數個數的函數成為可能。
例: //構造一個大量的值相加的函數 //註意函數內部定義的變量sum不會與函數外部定義的sum 產生沖突。 var sum = function () { var i, sum = 0; for(i=0;i<arguments.length; i++) { sum += arguments[i]; } return sum; }; document.writeln(sum(4, 8, 15, 16, 23, 42)) //108
因為語言的設計錯誤,arguments並不是一個真正的數組。他只是一個“類似數組”的對象。arguments擁有一個length屬性,但它沒有任何數組的方法。
5.返回
return 語句可用來使函數提前返回。當return被執行時,函數立即返回而不再執行余下的語句。
一個函數總會有返回值。如果沒有指定返回值,則返回undefined。
如果函數調用時在前面加上了new前綴,且返回值不是一個對象,則返回this(該新對象)
6.異常
javascript提供了一套異常處理機制。宜昌市幹擾程序的正常流程的不尋常(單並非完全是出乎預料的)的事故。當發現這樣的事故時,你的程序應該拋出一個異常。
例: var add = function (a,b) { if(typeof a !== ‘number‘ || typeof b != ‘number‘) { throw { name: ‘TypeError‘, message: ‘add needs numbers‘ }; } return a+b; } throw語句中斷函數的執行。他應該拋出一個exception對象,該對象包含一個用來識別異常類型的name屬性和一個描述性的message屬性。也可以添加其他的屬性。 該exception對象將被傳遞到一個try語句的catch從句: //構造一個try_it函數,以不正確的方式調用之前的add函數。 var try_it = function () { try { add("seven"); } catch (e) { document.wrieln(e.name + ‘:‘ + e.message); } } try_it();
如果在try代碼塊內拋出了一個異常,控制權就會跳轉到它的catch從句。
一個try語句只會有一個捕獲所有異常的catch代碼塊。如果你的處理手段取決於異常的類型,那麽異常處理器必須檢查異常對象的name屬性來確定異常的類型。
7.擴展類型的功能
javascript允許給語言的基本類型擴充功能。基本數據類型有這五種:Undefined、Null、String、Number、Boolean。引用類型有這幾種:object、Array、RegExp、Date、Function、特殊的基本包裝類型(String、Number、Boolean)以及單體內置對象(Global、Math)。
通過給object.prototype添加方法,可以讓該方法對所有對象都有用。這樣的方式對函數、數組、字符串、數字、正則表達式和布爾值同樣適用。
例: //通過給Function.prototype增加方法來使得該方法對所有函數可用。 Function.prototype.method = function (name, func) { this.prototype[name] = func; return this; } //通過給Function.prototype添加一個method方法,我麽下次給對象增加方法的時候就不必鍵入prototype這幾個字符,省掉了一點麻煩。
javascript沒有專門的整數類型,我們通過給Number.prototype增加一個integer方法來改善。它會根據數字的正負來判斷是適用Math.ceiling還是Math.floor。
例: Number.method(‘integer‘, function () { return Math[this<0 ? ‘cell‘ : ‘floor‘](this); }); document.writln((-10 / 3).integer()); //-3
javascript缺少一個移除字符串首位空白的方法。這個小疏忽很容易彌補:
例: String.method(‘trim‘, function() { return this.replace(/^\s+|\s+$/g, ‘‘); }) document.writeln(‘"‘ + " neat ".trim() + ‘"‘);
因為javascript原型繼承的動態本質,新的方法立刻被賦予到所有的對象實例上,哪怕對象實例是在方法被增加的之前就創建好了。
基本類型的原型是公用結構,所以在類庫混用時務必小心。一個保險的做法就是只在確定沒有該方法時才添加它。
例: //符合條件時才增加 Function.prototye.method = function (name, func) { if(!this.prototype[name]) { this.prototype[name] = func; } return this; };
8.遞歸
遞歸函數就是會直接或間接地調用自身的一種函數。
例: //構建一個帶尾遞歸的函數。因為他會返回自身調用的結果,所以他是尾遞歸。 //javascript當前沒有對這種形式的遞歸做出優化。 var factorial = function (i,a) { a = a || 1; if(i < 2) { return a; } return factorial (i-1,a*i) }; document.writeln(factorial(4)); //24
9.作用域
在編程語言中,作用域控制變量與參數的可見性及生命周期。它減少了名稱沖突,並且提供了自動內存管理。
例: var foo = function () { var a = 3, b = 5; var bar = function () { var b = 7, c = 11; //此時,a為3, b為7, c為11。 a += b+c; //此時,a為21, b為7, c為11。 }; //此時,a為3, b為5, 而c還沒有定義。 bar(); //此時,a為21, b為5。 }
javascript確實有函數作用域。那意味著定義在函數中的參數和變量在函數外部是不可見的,而在一個函數內部任何位置定義的變量,在該函數內部任何地方都可見。
很多現代語言都推薦盡可能延遲聲明變量。而用javascript上的話卻會成為糟糕的建議,因為它缺失塊級作用域。所以,最好的做法是在函數體的頂部聲明函數中可能用到的所有變量。
10.閉包
作用域的好處是內部函數可以訪問定義它們的外部函數的參數和變量(除了this和arguments)。
一個有趣的情形是內部函數擁有比他的外部函數更長的生命周期。
該函數可以訪問被他創建時所處的上下文環境。被稱為閉包。
例; //構建一個函數,用正確的方式給一個數組中的節點設置事件處理程序。 //點擊一個節點,將會彈出一個對話框顯示節點的序號。 var add_the——handlers= function (node) { var helper = function (i) { return function (e) { alert(i); }; }; var i; for (i =0; i<node.length; i++) { node[i].onclick = helper(i); } }
註意:避免在循環中創建函數,它可能只會帶來無謂的計算,還會引起混淆,我們可以先在循環之外創建一個輔助函數,讓這個輔助函數再返回一個綁定了當前i值的函數,這樣就不會導致混淆了。
11.回調
函數使得對不連續事件的處理變得更容易。發起異步請求,提供一個當服務器的響應到達時隨機觸發的回調函數。異步函數立即返回,這樣客戶端就不會被阻塞。
12.模塊
使用函數和閉包來構造模塊。模塊是一個提供接口卻隱藏狀態與實現的函數或對象。
例: String.method(‘deentitify‘, function() { var entity = { quot: ‘ " ‘, lt : ‘<‘, gt : ‘>‘ }; //返回deentitify方法 return function() { return this.replace(/&([^&;]+);/g, function (a,b) { var r = entity[b]; return typeof r === ‘String‘ ? r : a; } ) } }())
模塊模式的一般形式是: 一個定義了私有變量和函數的函數;利用閉包創建可以訪問私有變量和函數的特權函數;最後返回這個特權函數,或者把它們保存到一個可訪問的地方。
使用模塊模式就可以摒棄全局變量的使用。它促進了信息隱藏和其他優秀設計的實踐。對於應用程序的封裝,或構造其他單列對象,模塊模式非常有效。
13.記憶
函數可以將先前的操作的結果記憶在某個對象裏,從而避免無謂的重復的運算。這種優化被稱為記憶。
學習javscript函數筆記(二)