定义: 函数包含一组语句,他们是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.记忆
函数可以将先前的操作的结果记忆在某个对象里,从而避免无谓的重复的运算。这种优化被称为记忆。