第六章

6.1函數定義和函數調用

6.1.1 函數的定義

JavaScript中函數定義的方式主要有兩種,分別是通過function語句來定義以及已經通過構造Function對象來定義。形式上,前者將函數視爲一種靜態語法,而後者將函數作爲一種動態對象。
不過不論採用何種方式,JavaScript的一個函數都是Function對象的一個實例,因此Function對象又被稱爲函數模板。

6.1.1.1聲明式函數定義與函數表達式及其例子

通過function來定義函數又有兩種不同的方式,分別是命名方式(聲明式函數定義)和匿名方式(引用函數定義或者函數表達式)。
function f2(){alert()};//命名方式
var f1 = function(){alert()};//匿名方式

聲明式函數定義與函數表達式.html
<html>
	<head>
		<title>Example-6.1(1)聲明式函數定義與函數表達式</title>
	</head>
	<body>
		<script>
		<!--
			function dwn(s){
				document.write(s + "<br/>");
			}
			function t1(){//聲明式函數
				dwn("t1");
			}
			t1();
			function t1(){//重新聲明瞭一個新的t1
				dwn("new t1");	
			}
			t1();
			t1 = function(){//用函數表達式給t1重新複製
				dwn("new new t1");	
			}
			t1();
		-->	
		</script>
	</body>
</html>

運行結果:
new t1
new t1
new new t1

===============分界線==============
原因:聲明式函數定義的代碼先於函數執行代碼被解析器解析,而引用式函數定義,或者函數表達式則是在函數運行中進行動態解析的。



通過命名方式定義的函數稱爲函數常量,而把賦給變量的匿名函數稱爲函數對象,把引用了函數對象的變量稱爲函數引用。

JavaScript並不要求函數的形式參數和實際調用時的參數完全匹配。

函數參數變量一般情況下同在函數體內部用var聲明的變量一樣,只有在執行函數的時候纔會被定義,一旦函數返回,它們就不再存在。
函數的運行域不一定會被銷燬
不但函數本身是一種數據,而且它在被調用時會生成一個臨時的調用對象,通常這個調用對象會被添加到作用域鏈的頭部,取代當前域成爲默認的域,而在函數體內的局部變量和參數就會作爲這個域上的變量,或者這個臨時對象的屬性來訪問。一般情況下,這個域在函數調用結束後就會被銷燬,因此在一次調用之後,那些調用時初始化的局部變量和參數就不再存在了。但是,這個域有可能在函數調用結束之前就被外部引用,並且這種引用並沒有隨着函數調用的結束而結束,在這種情況下,被引用的環境就不會被銷燬(也不應該被銷燬)。
function step(a){
	return function(x){
		return x + a++;//返回的閉包中引用了函數step調用對象域的屬性a
								   //所以它不會被銷燬
	}
}

return語句後的表達式如果默認,或者程序執行到函數體的末尾,那麼函數的返回值就爲undefined

函數對象定義時的形參數量:函數名.length


利用arguments實現的函數重載機制.html
<html>
	<head>
		<title>Example-6.8 利用arguments實現的函數重載機制</title>
	</head>
	<body>
		<script>
		<!--
			function dwn(s){
				document.write(s + "<br/>");
			}
			//$overload用來匹配參數類型和參數值,自動調用符合條件的重載函數
			function $overload(func,argMaps,owner){
				//owner是函數的所有者,即調用對象
				owner = owner || null;
				var args = [];
				for(var i = 0; i < argMaps.length;i++){
					//判斷argMaps中存放的參數類型聲明是否同實際的參數類型相匹配
					if(argMaps[i].type != typeof(argMaps[i].arg) && !(argMaps[i].arg 
					instanceof argMaps[i].type)){
						throw new Error("參數不匹配");//不匹配則拋出異常

					}	
											args.push(argMaps[i].arg);   //否則將參數放入args數組準備調用
				}	
				return func.apply(owner,args);
			}
			
			function Point(x,y){
				this.x = x;
				this.y = y;	
			}
			function Vector(x,y){
				//私有方法,簡單封裝一個argMaps的結構
				function $t(type,arg){
					return {type:type,arg:arg};
				}
				//用向量構造向量
				function vector_vector(v){
					this.x = v.x;
					this.y = v.y;	
				}
				//用點構造向量
				function point_vector(p){
					this.x = p.x;
					this.y = p.y;	
				}
				//用x,y座標構造向量
				function number_number_vector(x,y){
					this.x = x;
					this.y = y;	
				}
				//用兩個點所構成的線段構造向量
				function point_point_vector(p1,p2){
					this.x = p2.x - p1.x;
					this.y = p2.y - p1.y;	
				}
				//參數類型對應表,根據這個表指派正確的函數進行調用
				var funcs = [
					[number_number_vector,[$t("number",x),$t("number",y)]],
					[point_point_vector,[$t(Point,x),$t(Point,y)]],
					[vector_vector,[$t(Vector,x)]],
					[point_vector,[$t(Point,x)]],
				];
				//如果不帶參數調用,默認調用Vector(0,0);
				if(arguments.length == 0){
						Vector.call(this,0,0);	
				}
				for(var i = 0;i < funcs.length; i++){
					try{
							//嘗試選擇合適的funcs進行調用
							return $overload(funcs[i][0],funcs[i][1],this);	
					}	catch(ex){
						
					}
				}
				//如果參數類型和上面列表中的任何一個都不匹配,則拋出異常
				throw new Error("參數不匹配!");
			}
			//重載toString()方法,便於顯示
			Vector.prototype.toString = function(){
				 return "[" + this.x + "," + this.y + "]";
			}	
			try{
				var v1 = new Vector(1,2); //用x,y形式構造Vector
				dwn(v1);
				var p1 = new Point(0,3);
				var p2 = new Point(2,4);
				var v2 = new Vector(p1); //用單點形式構造Vector
				var v3 = new Vector(p1,p2); //用兩點確定的線段的形式構造Vector
				dwn(v2);
				dwn(v3);
				var v4 = new Vector("str"); //用字符串構造,類型都不匹配,拋出異常
		}catch(ex){
			dwn(ex.message);	
		}
		-->	
		</script>
	</body>
</html>

6.3函數的調用者和所有者

函數的調用者指的是調用這個函數的域,而函數的所有者指的是調用這個函數的對象。

6.3.1函數的調用者

6.3.2函數的所有者——一個爲函數指定所有者的例子

函數作爲對象的屬性,將作爲對象屬性的函數稱爲對象方法,而相應的對象就是這個函數的所有者。
構造函數的所有者是構造函數創建的對象本身。
在函數被調用的過程中,一個特殊的調用對象屬性“this”總是引用函數的所有者。在函數調用的過程中,函數所有者的屬性都可以"this.屬性"的方式來訪問。

在瀏覽器客戶端JavaScript中,頂層定義的全局函數的所有者是window,他們也可以視爲Window對象的屬性。

函數調用允許嵌套,但是每個嵌套調用的子函數有自己的所有者,如果它和調用者的所有者不同,那麼在嵌套函數內部,"this"屬性就會被新的所有者覆蓋,但是當調用結束返回父函數時,“this”屬性又會恢復爲父函數的所有者。

<script>
	var outVar = 1;
	function out(){
		this.outVar = 2;
		console.log(this.outVar);	
		console.log(this.constructor);
	}	
	function test(){
		out();	
		console.log(this.constructor);
	}
	new test();
</script>


如果某個局部函數是在函數體內嵌套定義的,並且不作爲對象屬性來調用,則它的this屬性視具體實現而定(一般爲一個特殊的全局對象,在客戶端瀏覽器中,這個全局對象通常仍然是window)

<script>
	var varTmp = 3;
	function test(){
		var varTmp = 1;
		this.temp = 5;
		function inner(){              //這個函數只能在test裏面訪問,在外面是訪問不了的
			console.log("訪問包含這個函數的函數的屬性:" + varTmp); //可以訪問
		//	console.log("訪問包含這個函數的公開屬性" +  temp); //報錯
			console.log("訪問包含這個函數的公開屬性" +  this.temp); //undefined
			console.log(this.varTmp);		//但是這個函數裏面的this指向的既然是window
		}
		console.log(inner()+ "  " +this.constructor);
	}
	new test();
 // inner(); //在這裏是訪問不了test()函數中的inner()方法
</script>

要爲函數指定所有者,只要將函數引用賦給指定對象的屬性即可。

將函數引用賦給一個對象的屬性,可以將函數作爲該對象的方法來調用。將函數賦給一個函數的原型(prototype),則可以將函數作爲一類對象的方法來調用。

call()和apply()方法的第一個參數都是要調用函數的對象,用call()和apply()調用函數時,函數內的this屬性總是引用這個參數。call()的剩餘參數是傳遞給要調用的函數的值,它們的數量可以是任意的。apply()方法和call()方法類似,只不過它只接受兩個參數,除了調用者之外,它的第二個參數是一個帶下標的集合(比如數組,但也可以不是數據),apply()方法把這個集合中的元素作爲參數傳遞給調用的函數。

<html>
	<head>
		<title>Example-6.10 用call和apply調用函數</title>
	</head>
	<body>
		<script>
			<!--
				function dwn(s){
					document.write(s + "<br/>");	
				}
				//定義一個Point類型
				function Point(x,y){
						this.x = x;
						this.y = y;
						this.toString = function(){
								return "(" + [x,y] + ")";	
						}	
				}
				
				//定義一個Vector類型
				function Vector(x,y){
						this.x = x;
						this.y = y;
						this.toString = function(){
								return "[" + [x,y] + "]"; 	
						}	
				}
				//這個函數將傳入的參數累加到對象的x、y屬性上
				function add(x,y){
						return new this.constructor(this.x + x,this.y + y);	
				}
				var p = new Point(1,2);
				var v = new Vector(-1,2);
				var p1 = add.call(p,3,4);  //把add函數作爲p的方法調用
				var v1 = add.apply(v,[3,4]); //把add函數作爲v的方法調用
				dwn(p1);
				dwn(v1);
			-->
		</script>
	</body>
</html>
 javascript可以將函數作爲數據對象使用,作爲函數本體,它像普通的數據對象一樣,不一定要有名字。默認名字的函數被稱爲“匿名函數”。

既然函數可以被引用,那麼函數可以用作參數,也可以作爲返回值。

閉包是非常好的天然“域”,在很多JavaScript開源模塊中,都採用了閉包來作爲域,以隔離外部,從而消除了全局變量。
<script>
	(function(){
		var defaultX = 0; //局部域
		var defaultY = 0; //局部域
		
		Point = function(x,y){  //全局域
			this.x = x || defaultX;
			this.y = y || defaultY;
		}	
	})();
</script>


作爲動態腳本語言,JavaScript擁有解析和執行自身數據的能力,這是通過兩個操作實現的,第一個是前面見過的eval函數,它將字符串作爲代碼來解析執行,另一個就是Function函數。
在JavaScript中,Function既是一個函數,也是一個類型,它是所有函數的類型。第一,Function是對JavaScript函數的抽象,JavaScript中所有的函數都是Function的實例;
第二,Function本身又是一個函數,所以它是它自身的泛型,即Function instanceof Function的結果爲true。這種以自身爲泛型的類型我們稱爲“元類型”,它是自我描述的。

作爲類型來看待,Function是構造函數,它可以通過字符串動態構造出函數對象。
<script>
	new Function("alert('abc')")();//彈出abc
	//相當於
	a = function(){
			alert('abc');	
	};
	a();
</script>

作爲函數:
<script>
	Function("alert('abc')")();
</script>


函數工廠是一種模板,它用來創建一組具有某類功能的函數。
<script>
	//count定義骰子的數量,side定義每個骰子的面數,each爲骰子修正數
	function dice(count,side,ench){
			var ench = ench || Math.floor(Math.random() * 6); //+0~+5的骰子隨機變數修正
			//返回一個閉包,這個閉包負責"擲"骰子
			return function(){
					var score = 0;
					for(var i = 0; i < count; i++){
							score += Math.floor(Math.random() * side) + 1;	
					}	
					return score + ench;
			}
	}
	var d1 = dice(2,6);  //生成一組2d6+n的骰子,其中的n爲0~5的隨機數
	var d2 = dice(1,20); //生成一顆20面的骰子,帶有0~6的隨機點數修正
	for(var i = 0; i < 100; i++){
		console.log(d1());
	}
	console.log("============================================");
	console.log("============================================");
	for(var i = 0; i < 100; i++){
		console.log(d2());
	}
</script>




<html>
	<head>
		<title>Example-6.16</title>
	</head>
	<body>
		<script>
		<!--
		  function dwn(s){
		  	document.write(s + "<br/>");	
		  }
		  function ListTemplate(type){//一個集合模板,可以用來構造任何一種類型的"集合"
		  	 var members = [];
		  	 var list = function(){ //定義一個list構造函數
		  			this.append.apply(this,arguments);
		  	 }
		  	 list.prototype.append = function(){//list.append()方法,這個方法的作用是往集合中添加元素
				 		for(var i = 0;i < arguments.length; i++){
				 			if(typeof(arguments[i]) == type ||
				 			 (typeof(type) == 'function' && arguments[i] instanceof(type))){
				 			 		members.push(arguments[i]);	
				 			 }else{
				 			 		throw new TypeError('元素類型與集合聲明不符合');
				 			 		//當添加的元素與集合聲明的類型不匹配時,將會拋出TypeError異常	
				 			 }
				 		}
				 		list.prototype.toArray = function(){//toArray方法將集合轉換爲數組
				 			console.log(members);
				 			return members.slice(0);
				 		
				 		}
				 }
				 return list;	
		  }
		  NumberList = new ListTemplate("number");
		  //構建一個數值集合,要求集合中的每一個元素都必須是數值
		  var a = new NumberList(1,2,3);
		  dwn(a.toArray());
		  a.append(4,5,6);
		  dwn(a.toArray());
		  
		  ObjectList = new ListTemplate(Object);
		    //構建一個Object集合,要求集合中的每一個元素都必須是Object元素
		  var b = new ObjectList({x:1,y:2},{x:3,y:4});
		  dwn(b.toArray());
		  
		  function Point(x,y){
		  	this.x = x;
		  	this.y = y;	
		  }
		  Point.prototype.toString = function(){
		  		return "(" + this.x + "," + this.y + ")";	
		  }
		  PointList = new ListTemplate(Point);
		  //構建一個集合,要求集合中的每一個元素都是一個Object
		  var c = new PointList();
		  c.append(new Point(1,3),new Point(2,4));
		  dwn(c.toArray());
		-->
		</script>
	</body>
</html>

對於JavaScript來說,函數是“第一型”。
JavaScript支持聲明式函數定義、函數表達式和Function構造函數三種方式定義函數。

發佈了17 篇原創文章 · 獲贊 11 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章