第六章

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构造函数三种方式定义函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值