在编程过程中,我们很经常会需要多次使用到一段代码,此时便需要把这段代码定义成函数,从而可以调用任意次。
函数是JavaScript中最出色的设计之一,它们是JS中基础模块单元,可用于代码复用,信息隐藏和组合调用。JS中的函数就是对象,也拥有自己的方法,它连接到Function.prototype上。
本文参考《JavaScript权威指南》;
一般有两种方式可以创建函数:函数声明与函数表达式;
函数声明:
function add(a,b){
return a+b;
}
这种形式的声明会被提前到作用域的顶部,所以可以在被定义之前调用它,称为 声明提前;
函数表达式:
var add = function(a,b){
return a+b;
}
表达式的形式并不能在定义之前调用它,虽然
var add
也会被提前到作用域顶部,但赋值语句并不会被执行,此时add
扔为undefined
,所以只有在表达式定以后,才能进行函数的调用。
1、函数声明可以出现在全局代码中,也可以被嵌套在其它函数内,但根据规定,不能出现在循环,判断,try/catch/finally与with语句中。而函数表达式可以出现在任何地方。
2、定义在其它函数内部的函数,包含一个连接到外部上下文的连接[[scope]],可以自由访问它的父函数中的参数与变量,这便是JS中的 闭包。
调用一个已经定义好的函数有4种方式,会传入 实参,this, arguments。
可以直接在函数名称的后面加上()
,传入参数完成函数的调用,如add(3,4)
;此时,根据ES5或者ES5非严格模式,调用上下文( this )被指向全局对象;而在ES5严格模式中,this 则为 undefined
;
因此可以这样来检测是否为严格模式:
var strict = (function(){return !this;})();
当一个函数被保存成一个对象的属性时,则成为一个方法,此时 this 指向保存该函数的对象。如 object.method()
;
多数情况下,可以直接用 .
符号来直接访问,但少数情况下可能需要使用[ ]
来进行属性的访问操作。
方法链 也称为 链式调用或级联调用,我们可以在一个方法中返回一个对象,从而可以继续调用它的方法。如JQuery中就是这么实现的;
$(".header").map().get().sort();
这将使代码变得更为简洁和易读,因此当方法不需要返回值时,最好直接返回this。
this是函数中的一个关键字 ,它指向函数的调用上下文,并不是变量,也不是属性名,因此不能对其进行赋值。
如果作为方法调用函数,则this指向保存该方法的对象;
如果在函数调用中,this可能指向全局或者undefined
,因此,在闭包中,如果想访问父级的this,则通常是将this保存在一个变量里,便可以通过作用域链进行访问,一般可以命名为 self/that/me。
1、全局环境中,则指向 全局对象(windows)。
2、构造函数里的this,则指向 新创建的对象;
3、函数中的this,指向 函数的调用者;
4、new Function,指向 全局对象(windows);
5、eval中,指向调用上下文中的this;
如果函数或者方法调用之前带有关键字 new
,它就构成构造函数式调用。构造函数的处理方式跟普通的调用在实参处理、调用上下文和返回值方面都有着不同。
1、当构造函数没有形参时,可以省略实参列表和圆括号;
2、
new
操作符的操作步骤可以分成3步:(1)创建一个新对象;
(2)新对象继承构造函数的
prototype
属性,this指向该新对象,也就是说,就算即使使用new o.m()
,函数中的this
值也是指向新对象,而不是o
;(3)执行函数语句,为新对象添加属性,方法;
(4)返回该新对象;通常,构造函数中不适用
return
,但如果有主动return
一个对象,则返回该对象;如果没有返回或者返回一个原始值,则返回最初创建的对象;
使用call()
和apply()
可以间接地调用函数,它们可以显式地指定this值,也就是说,任何函数都可以作为任何对象的方法来调用。
call(this,p1,p2,p3)
:以自己的实参列表作为传入函数的实参;
apply(this,[p1,p2,p3])
:以数组的形式传入实参;
调用函数时,当实参的数量少于定义的形参时,则后面的形参的值均为undefined
。因此,在设置形参的时候,理想的情况是为参数设置一个合理的默认值,可以使用下面的形式:
a = a || 0; // 形参设置为 a
但传入的实参和形参只是以顺序的位置对应,并无法主动地为他们指定关系,也就是说,无法省略第一个实参,而直接传入第二个实参,必须填入占位的undefined
或者null
,因此可选的形参只能放在是参列表的最后。推荐对可选参数进行清晰的注释。
调用函数式,当实参的数量大于定义的形参时,可以通过参数对象来获取超过部分的实参。这个对象称为 arguments
,是一个类数组,包含着传入的所有实参列表,它可以通过类似于数组的[0]
,[1]
进行索引,访问传入的实参。同时,它还拥有可以length
的属性,可以获取到传入函数的实参数量。
该对象有一个重要的用处,既让函数可以处理不定数量的实参,这种函数也成为 不定实参函数(varargs function)。比如,求任意个数字的总和,便可以定义为:
var add = function(){
var sum = 0,
i;
l = arguments.length;
// 遍历实参对象,累加每一个实参;
for(i=0;i<l;i++){
sum += arguments[i];
}
// 返回结果;
return sum;
}
var sum = add(1,2,3,4,5); // sum为15;
Tips: 1、实参对象arguments其实是实参的一个指针数组,并不是一个真正的数组,也就是说,上面函数中,
arguments[0] === 1
的值为true
。在非严格模式下,可以通过实参对象来修改传入函数的实参的值。
2、实参对象还包含两个值,
callee
和caller
。在严格模式下,不能对这两个值进行读写操作,但在非严格模式下,callee
指向当前函数本身,caller
则指向调用函数的函数。
3、记住定义的形参顺序,是个麻烦的事,因此可以使用直接传入对象,对象中是一个所有参数的键值对集合。
4、合理的实参类型检测,是避免函数执行错误的保证。
由于函数的本质也是一个特殊的对象,它也可以拥有自己的属性。当有些函数需要一些静态的值时,其实可以将其保存在函数的自身属性中,从而避免保存到全局环境中。
function integer(){
return integer.counter++;
}
integer.counter = 0;
// 点击一次,计时器累加1;
$(document).on("click",function(){
console.log(integer());
})
一般,我们会避免在全局环境中添加变量,避免污染全局的命名空间,一个很好的解决方法,便是将代码放置于一个函数作用域内。一般使用匿名的立即执行函数:
(function(){
// 模块代码;
})();