目录
构建函数的基本概念 - 详细解读
构建函数的几种方式
匿名函数
函数调用的几种方式
function构造函数与函数直接量
嵌套函数
递归函数
一、概念详解
在JavaScript中,函数使用关键字function来定义函数。它是脚本中的一部分代码,用于执行一项或一系列的任务处理,由事件驱动或被调用时执行可重复使用的代码块。使用函数可以使代码更加简洁、提高重用性和可移植性。
二、函数的构建与调用
2.1、构建函数的基本概念 - 详细解读
明白了函数的定义和用途后,我们来看看如何在脚本中构建函数。
函数通过function关键字来声明函数,同时我们还要指定这个函数的名称。另外,还有一些用于增强函数的功能。比如,在调用函数的时候传递一个或多个值,这些值被称为参数。还可以用return语句向函数的主脚本返回值或结束函数的执行。它的基本定义结构如下:
function funcName([parameter1,parameter2,...]){
//函数执行的代码
[return expression]
}
- funcName:必填,指定函数名。函数名必须是唯一的,并且区分大小。命名时一定要避免使用关键字和保留字。
- parameter:可选,指定参数列表。当使用多个参数时,通过逗号进行分割,并且最多只有255个参数。注意的是,参数的本质是一个数组对象,这一点和Java是不一样的。
- return:可选,指定返回值。expression可以是表达式、变量或常量。
参数用于从外部向函数内传递一个或多个值。任何参数在函数中都会变成变量,并使用圆括号中定义的名称作为变量名。值得注意的是,为函数设置参数时,不使用var关键字。这是因为,在函数内部,参数会被JavaScript自动声明为变量,所以不需要手动添加var关键字,例如下面的例子。
funcName(5,10);
function funcName(A,B){
return A+B;
}
注意:函数的参数分为形式参数(A和B)和实际参数(5和10)。其中,系统并不为形式参数分配内存空间。调用函数时传递给函数的参数称为实际参数。实际参数通常在调用函数之前以及被分配了内存,并且赋予实际的数据。在函数执行过程中,形式参数通过内存地址的引用指向实际参数,实际上是实际参数参与了函数的运行。
2.2、构建函数的几种方式
构建函数可以通过以下几种方式:
(1) 函数声明
function funName(){
//函数执行的代码...
}
(2) 函数表达式
var funcName = function(){
//函数执行的代码....
}; //注意:花括号的后面有分号,这是因为函数被赋给一个变量,所以需要用分号结束变量的赋值语句。
注意:函数表达式和函数声明之间最重要的一点是,函数声明之前,调用函数的位置在前在后,系统都不会报错,都能正常执行,这是因为系统在"预编译"阶段会解释声明。但是函数表达式就不一样了,系统必须在加载到该函数之后,才能被调用,所以调用函数表达式时只有将调用语句放在后面才会被调用。
(3) 函数构造函数
var funcName = new function(){
//函数执行的代码
}
注意:函数构造函数在系统执行到这里的时候不需要调用就会自动执行。但是这种方式的性能很差,每次使用的时候都要重新解析,而不是只解析一次。
(4) 属性函数
//定义对象
var FuncObj = {
func1:function(){
console.log("对象函数属性1");
},
func2:function(){
console.log("对象函数属性2");
}
}
//访问对象的函数属性
FuncObj.func1(); //控制台输出:对象函数属性1
2.3、匿名函数
(1) 匿名函数声明的常见场景
//第一种:事件字面量
elementObj.onclick = function() {
// 执行代码
}
//第二种:事件参数
setInterval(function() {
// 执行代码
}, 1000);
//第三种:字面量
var FuncObj = function() {
// 执行代码
}
//第四种:对象属性
var FuncObj = {
fun1:function() {
// 执行代码
}
}
//第五种:事件监听的参数
(1)、基于IE内核浏览器
DOM对象.attachEvent(onclick,function(){
//执行代码
},false);
(2)、基于W3C内核的事件监听
DOM对象.addEventListener(onclick,function(){
//执行代码
},false);
(2) 匿名自执行函数
//第一种
(function(data){
alert(data);
})("第一种");
//第二种
(function(){
alert("第二种");
}());
//第三种
!function(data){
alert(data);
}("第三种");
//第四种
var fun = function(data){
alert(data);
}("第四种");
类似第一种的匿名自执行函数,第一个括号中创建了匿名函数,第二个括号用于调用该匿名函数,并传入参数。那问题就来了,为什么(function(){})()可以被执行, 而function(){}()却会报错?
首先,(function(){})是表达式, function(){}是函数声明。
其次, js在"预编译"阶段, 会解释函数声明, 但却会忽略表式。当js执行到function() {}()时, 由于function(){}在"预编译"阶段已经被解释过, js会跳过function(){}, 试图去执行后面的(), 所以必然会报错。
当JS执行到(function(){})()时, 由于(function(){})是表达式, 括号是表达式,所以必然有返回值。所以JS会去对它求解得到返回值, 由于返回值是一个函数, 故而遇到()时, 便会被执行。
另外,函数转换为表达式的方法并不一定要靠分组操作符(),我们还可以用void操作符,~操作符,!操作符等等,例如上述的第二种。
(3) 匿名自执行函数的作用
匿名自执行函数最常见的作用是用于实现闭包的情况中。闭包是JavaScript中非常重要的一部分知识,通过闭包可以实现函数内外部的连接,并且可以使得函数的局部变量始终存在于内存中,还大大减少代码量,使代码看上去更加清晰。
闭包就是函数的嵌套,内层的函数可以使用外层函数的所有变量,即使外层函数已经执行完毕(这点涉及JavaScript作用域链),例如:
function timeFunc(){
var Param = "Laoye";
setTimeout(function(){
//执行的代码...
},1000);
}
匿名自执行函数还可以用于在JavaScript中模拟创建块级作用域,即如果使用匿名自执行函数将某些代码包裹起来可以实现块级作用域的效果,减少全局变量的数量,在匿名自执行函数执行结束后变量就会被内存释放掉,从而也会节省了内存。
JQuery就是通过匿名自执行函数创建了$的对象,在这个匿名函数中,往往会定义一个属于自己的命名空间,或者返回一个属于自己的对象。
(4) 匿名函数和匿名自执行函数的总结
- 匿名函数可以理解为没有名字的函数。
- 匿名自执行函数可以理解为可以自己执行的匿名函数。
- 匿名自执行函数的作用就是用于闭包和创建独立的命名空间。
2.4、函数调用的几种方式
函数定义后并不会主动执行。要执行一个函数,需要在特定的位置通过创建调用语句来调用函数。调用语句包含了需要调用的函数名和传递的参数值。函数的调用包括以下几种常见的调用。
function timeFunc([parameter1,...]){ //案例函数
//执行的代码...
}
上述函数声明不属于任何对象。但JavaScript中它始终是默认的全局对象。在HTML中默认的全局对象是HTML页面本身,所以函数是属于HTML页面。而在浏览器中的页面对象是浏览器窗口(window 对象),所以函数会自动变为window对象的函数。所以myFunction()和window.myFunction()是一样的。
(1) <script>标记中的简单调用
<script>
timeFunc(value); //函数调用
</script>
(2) <body>段中的简单调用
<body>
<script type="text/javascript">
timeFunc(); //调用函数
</script>
</body>
(3) 在事件响应中调用函数
//第一种:在HTML标签的事件属性中调用
<input type="button" value="提交" onclick="FuncName()" />
//第二种:在JS事件响应中调用
DOM对象.onclick = function() {
// 执行代码
}
//第三种:在JS事件监听中调用
DOM对象.addEventListener(onclick,function(){
//执行代码
},false);
(4) 通过链接调用函数
<a href="javascript:FuncName()">链接调用</a>
(5) 通过方法属性调用函数
var FuncObj = {
fun1:function() {
// 执行代码
}
}
FuncObj.fun1();
(6) 通过构造函数调用函数
function myFunction(arg1, arg2) {
this.firstName = arg1;
this.lastName = arg2;
}
var x = new myFunction("John","Doe");
x.firstName; // 返回 "John"
三、作用域与上下文
变量可以通过var关键字在函数外创建全局变量,或者在函数内创建局部变量。全局变量可以在函数外的作用域中被所有的函数调用,而局部变量只能在被定义的那个函数中调用。例如:
var paramOut = "Lao";
console.log(paramInt ); //报错:paramInt is not defined
(function() {
var paramInt = "Ye";
console.log(paramOut); //正常输出"Lao"
})();
全局作用域内的变量,可以从变量被声明之后的任意位置调用。例如上述代码中,如果在paramOut的前面访问该变量,控制台会输出"undefined"。
四、调用顺序
var Func;
//函数声明
function Func(){return 1;}
console.log(Func()); // 第四个函数把第一个函数覆盖
//构造函数
Func = new Function("return 2;");
console.log(Func()); // 第二个函数把第四个函数覆盖
//函数表达式
Func = function() {return 3;}
console.log(Func()); // 第三个函数把第二个函数覆盖
//函数声明
function Func(){return 4;}
console.log(Func()); // 第四个函数已经被覆盖
//构造函数
Func = new Function("return 5;");
console.log(Func()); // 第五个函数把第三个函数覆盖
//函数表达式
Func = function(){return 6;}
console.log(Func()); // 第六个函数把第五个函数覆盖
//最终输出结果为:4,2,3,3,5,6
之所以会出现这样的结果,是因为JS在预编译阶段优先加载函数声明,又因为加载时是从上往下读取的,所以下面的会覆盖掉之前的同名函数,所以在编译阶段第一次输出为4。之后往下读取时正常输出,在读取到第四个,函数声明的时候,由于预编译的时候以及读取了,所以被跳过了,所以第四个输出的自然是之前第三个的函数,输出自然还是3了。
五、特殊的函数
5.1 function构造函数与函数直接量
除了使用function函数声明外,还可以使用构造函数function()和使用函数表达式,但是这两者之间存在很重要的差别。
首先,构造函数function()在运行时动态创建和编译JavaScript代码,而函数表达式却是程序结构的静态部分,就像函数语句一样。
其次,每次调用构造函数function()时都会解析函数体,并且创建一个新的函数对象。如果对构造函数的调用出现在循环或者经常被调用的函数中,这种方法的效率将非常低。而函数表达式就不会出现上述的情况。
最后,使用function()创建的函数使用的不是静态作用域。相反地,该函数总是被当作顶级函数来编译。
个人观点:构造函数是动态访问,而函数表达式是静态访问。也就是说,每次访问构造函数时,都需要在内存中开辟一块内存空间。而函数表达式的函数访问则不会如此,他访问的永远都是第一次所存储的内存地址。
5.2 嵌套函数
嵌套函数就是在函数内部再定义一个函数。这样的有点在于使用内部函数可以轻松获得外部函数的参数以及函数的全局变量等。例如:
function outerFunc(num1,num2){
function innerFunc(){
console.log("参数的和为:" + (num1 + num2));
}
innerFunc();
}
outerFunc(1,1); //参数的和为:2
注意:虽然嵌套函数再JavaScript语言中非常强大,但是却会使程序的可读性大大降低。
5.3 递归函数
递归函数就是函数在自身的函数体内调用自身。使用递归时要注意,如果处理不当将会使程序进入死循环。
var num = 1;
function Func(sum){
if(++num < 10){
Func(num)
}
}
Func(num)
console.log(num) //输出为10
注意:递归函数只在特定的情况下使用,例如:定时器或者处理阶乘等问题。在使用递归函数时,需要注意的两点:
- 包括一个结束递归的条件或满足递归的条件,当结束递归时,要么不调用自身函数,要么就执行return结束执行。
- 包括一个递归调用函数,用于实现调用递归函数。