1、函数的定义
JavaScript中的函数定义有三种,分别如下:
/** * 函数定义方式一:命名方式/声明式 */ function test_1() { alert("我是命名式函数,或者是声明式函数定义"); } /** * 函数定义方式二:匿名式/引用式函数/函数表达式 */ var test_2 = function() { alert("我是匿名式、引用式或者函数表达式式 的函数定义"); } /** * 函数定义方式三:构造Function对象(直接通过字符串构造函数) */ var test_3 = new Function("arg1","arg2", "alert('我是Function对象构造出来的的方法,参数:'+arg1+','+arg2)"); //test_3的调用方式,如果不传参,则在函数体内获得的参数值将是undefined,多传参数,只前两个有效,不影响功能。 test_3(20,"ttt",200);
注意”声明式函数定义"与"匿名式函数定义"的区别:二者的区别主要在执行的先后顺序上:浏览器加载完js代码后,”声明式函数“代码会在函数执行代码执行之前被解析器解析,而”匿名式函数“定义则是在函数运行中进行动态解析的——不理解?看下面的例子:
/** * 声明式定义函数test */ function test() { alert("one"); } //调用一次 test(); /** * 声明式再定义一遍函数test */ function test() { alert("two"); } //再调用一次 test(); //两次调用的结果都为two,因为“声明式定义函数”的代码先与“执行代码”执行,所以在两次调用test函数时 //test函数的真实函数体是第二次定义后的函数体。
2、函数闭包,一个函数的返回值仍然是一个函数(通俗理解:闭包就是一个函数定义在另一个函数中),例如:
function test() { return test; } //无论调用多少次,最终得到的结果还是函数本身,打印结果将是定义的函数体 alert(test()()()()()()()()()()());
3、一般情况,在JavaScript中,一个函数的形参或者函数体内的局部变量在函数调用完成,执行的返回操作,则全部销毁,不在存在,但是通过闭包,可以让其不销毁,办法如下:
function test(a) { var b = 1; //采用闭包,并且闭包函数内部使用a、b参数,这样a和b参数在函数调用完成后仍不会销毁 return function(x){ return x + a + b; } } //该方法调用结束后,其内容的a、b参数变量不会销毁。 test(100);
4、使用try……catch……finally让函数多次执行return语句。
function test() { var obj = new String("ttttttttttttt"); try { return obj; } catch (ex) { } finally { obj = null; return null; } } //当调用函数,执行多次return语句后,最后得到的结果是最后一次执行return语句的结果。 //以下打印的结果是null alert(test());
5、函数形参和实参
相信任何一个开发人员对于函数的形参和实参的概念都是了如指掌的,不做详述,这里主要要说的是,javascript中函数的实参个数与行参个数没有关系,当实参少于形参时,多余的形参值为undefined,当实参多余形参时,多余部分可以通过Arguments对象来访问。
function test(x, y, z) { } //函数的length属性记录的是函数的形参数量,以下结果打印3,说明test函数有三个形参 alert(test.length)
6、Arguments对象
该对象是任何”函数对象“都拥有的一个属性,记录着调用该函数时传入的实参信息。改变arguments中元素的值,对应的形参值会同步改变,反之亦然。
function test(x, y) { var xcLength = test.length; var scLength = arguments.length; alert("形参的个数为:" + xcLength + ",实参的个数为:" + scLength); var str = ""; for (var i = 0; i < scLength; i=i+1) { str = str + "," + arguments[i]; } alert("你传入的实参值为:" + str); alert("x1="+x); arguments[0] = "呵呵,我变了,x的值也会跟着变,不信你试试!!"; alert("x2="+x); } //调用 test(1, 2, 3, 4, 5, "55555", "OK!");
7、使用Arguments对象模拟实现函数重载
以下函数实现,接收多种不同类型不同数目的实参,模拟函数重载
function test() { var paramsLength = arguments.length; if (0 == paramsLength) { alert("小东西,一个实参都不给,太吝啬!!"); } else if (1 == paramsLength && (typeof(arguments[0]) == "number" || arguments[0] instanceof Number)){ alert("你传入的是Number类型的数据,对不对呀?"); } else if (1 == paramsLength && (typeof(arguments[0]) == "string" || arguments[0] instanceof String)) { alert("呵呵,你传入的是String类型的实参!"); } else if (paramsLength > 1) { throw new TypeError("传入实参过多,该函数不支持!"); } else { throw new TypeError("非法的实参数据类型,该函数不支持!"); } } //执行 try { test(); test(100); test("tttt"); test(100,20,"KKKK"); } catch (e) { alert(e); }
8、Arguments对象的callee属性
该属性被用来引用当前正在执行的函数本身,通过它可以实现一种匿名的递归调用能力,这对于闭包来说非常有用
/** * 递归计算从1加到指定实参值的总和 * 方法一 */ function test1(x) { if (x <= 1) { return x; } else { return x + test1(x -1); } } /** * 方法二 * */ function test2(x) { if (x <= 1) { return x; } else { return x + arguments.callee(x -1); } } //函数调用,计算1+2+3+4+5的值 alert(test1(5));//15 alert(test2(5));//15
callee执行的是函数本身,而this指向的是当前函数的所有者对象,注意区分
function test() { this.x = 100; //打印出来的是函数体本身 alert(arguments.callee); //打印的是一个Object对象引用 alert(this); } //调用 test();
9、下面采用Arguments实现函数的重载机制
首先定义几个不同类型实体类型——交通工具
/** * 定义汽车类 */ function Car(name) { this.name = name; } /** * 重写汽车类的toString方法 */ Car.prototype.toString = function() { return "汽车牌号:" + this.name; } /** * 定义飞机类 */ function Plane(name) { this.name = name; } /** * 重写飞机类的toString方法 */ Plane.prototype.toString = function() { return "飞机编号:" + this.name; } /** * 定义轮船类 */ function Boat(name) { this.name = name; } /** * 重写轮船类的toString方法 */ Boat.prototype.toString = function() { return "轮船标号:" + this.name; }然后定义一个能够,根据不同的参数,动态调用不同的方法的方法
/** * 匹配参数类型和参数值,自动调用符合条件的重载函数 * * @param func 将要被调的函数 * @param func 参数类型与值的映射 * @param func 函数所有者 */ function callOverloadMethod(func, argMaps, owner) { owner = owner || null; var args = []; for (var i = 0; i < argMaps.length; i++) { if (argMaps[i].type != typeof(argMaps[i].arg) && !(argMaps[i].arg instanceof argMaps[i].type)) { return; } else { args.push(argMaps[i].arg); } } return func.apply(owner, args); }定义一个存在重载方法的函数
/** * 定义一个司机类,用于开各种交通工具 * * @param dirverName 司机姓名 * @param meansOfTransportation 交通工具类 */ function Driver(dirverName, transportationTool) { this.diverName = dirverName; this.doing = ""; /** * 开汽车 * @returns {driverCar} */ function driverCar(car) { this.doing = "【"+this.diverName + "】正在开汽车," + car; } /** * 开飞机 * @returns {driverPlane} */ function driverPlane(plane) { this.doing = "【" + this.diverName + "】正在开飞机," + plane; } /** * 开轮船 * @param boat */ function driverBoat(boat) { this.doing = "【" + this.diverName + "】正在开轮船," + boat; } /** * 参数不够时的默认处理方法 */ function doNothing() { this.doing = "你传给的参数不够,我不知道做什么,我勒个去…………"; } /** * 表示参数类型与值的映射类 * * @param type * @param arg * @returns {___anonymous5119_5138} */ function toolType(type, arg) { return {type:type, arg:arg}; } //方法——数据类型——参数的映射关系 var funcs = [ [driverCar, [toolType(Car, transportationTool)]], [driverPlane, [toolType(Plane, transportationTool)]], [driverBoat, [toolType(Boat, transportationTool)]] ]; //开始实现司机的实际处理业务逻辑,如果连司机都没有指定,则默认交给队长开飞机 if (0 >= arguments.length) { Driver.call(this, "队长", new Plane("空军一号")); //仅指定了司机 } else if (1 == arguments.length) { this.doNothing(); //如果既指定了司机,又指定了交通工具,则根据不同的交通工具调用不同的处理方法 } else if (2 == arguments.length) { for (var i = 0; i < funcs.length; i=i+1) { try { callOverloadMethod(funcs[i][0], funcs[i][1], this); } catch (ex) { alert(ex); } } } else { throw new Error("参数过多,系统不接受!"); } } /** * 重写Driver类的toString方法 */ Driver.prototype.toString = function() { return this.doing; }最后测试:
//调用测试 try { var d = new Driver(); alert(d); var d1 = new Driver("张三", new Plane("波音一号001")); alert(d1); var d2 = new Driver("李四", new Car("皖A 000111")); alert(d2); var d3 = new Driver("王五", new Boat("泰坦尼克号")); alert(d3); var d4 = new Driver("赵六", 11, 200); alert(d4); } catch (ex) { alert(ex); }
10、函数的调用者和所有者
//默认的全局this指向的是Window对象 alert(this); /** * 定义一个全局函数 */ function test() { alert(this); } //直接调用test函数,则其内部的this仍然指向window对象,其实本质上下面的调用相当于window.test(); test(); //定义一个对象,并将方法test作为对象的一个成员方法 var testObj = {}; testObj.fun = test; //则此时test()方法内部的this指向testObj对象 testObj.fun(); /** * 方法的调用者和所有者区分 */ function callMethod() { //testObj是fun方法的所有者,而callMethod是fun的调用者。 testObj.fun(); } callMethod();
11、函数的动态调用(call和apply方法)
call和apply大致相同,任何方法通过这两个方法动态调用,则其内部的this都指向call或者apply的第一个参数,二者的区别在于
call和apply除第一个参数都为调用函数的对象外,call剩余参数都为传给被调用方法的实参,可以任意多个,apply只有两个参数,第二个
参数为被调用函数的实参数组
function doWork(name, age) { this.msg = "["+name+"],今年["+age+"]岁了,在干活…………" + this; } function callDoWork(msg, sm){ alert(this.msg+"," + msg+","+sm); } var d = new doWork("张三", 100); //将callDoWork方法作为d对象的方法调用,此时callDoWork方法中的this指向d callDoWork.call(d, "你好!"); //将callDoWork方法作为d对象的方法调用,此时callDoWork方法中的this指向d callDoWork.apply(d, ["您好!","我也好!"])
12、函数作为参数和返回值
使用函数作为参数
//求一个数的平方 function sub2(x) { return x * x; } //计算从1到指定数的平方和 function test(num, op) { var totle = 0; for (var i = 0; i < num; i++) { totle = totle + op(i); } return totle; } //求1到5的每一个数的平方和 alert(test(5, sub2));
使用函数作为返回值
/** * 字符串累加 */ function concatStr() { var temp = ""; if (arguments.length > 0) { for (var i = 0; i < arguments.length; i++) { temp = temp + "," + arguments[i]; } } return temp; } /** * 数字累加 */ function addNum() { var temp = 0; if (arguments.length > 0) { for (var i = 0; i < arguments.length; i++) { temp = temp + arguments[i]; } } return temp; } /** * 如果type为数字,则返回一个数字累加函数,否则返回一个字符串累加函数 * * @param type * @returns */ function test(type) { if (typeof(type) == "number" || type instanceof Number) { return addNum; } else { return concatStr; } } //打印:21 alert(test(1)(1,2,3,4,5,6)); //打印:,1,2,3,4,5,6 alert(test("str")(1,2,3,4,5,6));
13、用var与不用var的区别
全局变量没有区别,但是函数内部的变量,如果不用var声明,则表示你声明了一个全局变量
//函数内部的n用var声明,则n为局部变量,函数外部如果访问n,则报错:Uncaught ReferenceError: n is not defined function test() { var n = 0; } test(); alert(n); //函数内部的变量不用var声明,则表示是一个全局变量,只要执行test函数,则全局就存在n变量,可以在函数外访问 function test() { n = 0; } test(); alert(n);
14、使用Function构造函数,将一个字符串构造成函数对象
var str = "var temp = 0; for (var i=0; i < arguments.length; i++){temp = temp + arguments[i]} return temp;"; var add = new Function(str); alert(add(1,2,3,4,5,6));//21