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