ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
[toc] ##概念 > 函数是由事件驱动的或者当它被调用时执行的可重复使用的代码块。 `js` 支持两种函数:一类是语言内部的函数(如`eval()` ),另一类是自己创建的。 在 `JavaScript` 函数内部声明的变量(使用 var)是局部变量,所以只能在函数内部访问它。(该变量的作用域是局部的)。 您可以在不同的函数中使用名称相同的局部变量,因为只有声明过该变量的函数才能识别出该变量。 ##函数调用 有如下四种调用`js`函数的方式: * 作为函数 * 作为方法 * 作为构造函数 * 通过`call()`和`apply()` ##返回函数的函数 1. 当函数无明确返回值时,返回的值就是`undefined`。 2. 当函数有返回值时,返回值是什么就返回什么。 我们可以通过使用 `return` 语句实现将函数返回调用它的地方。 在使用 `return` 语句时,函数会停止执行,并返回指定的值。 函数通常会返回一个唯一值,那么这个值也可能是另一个函数: ``` <script type="text/javascript"> //函数表达式 var box = function(){ var a=1; return function(){ alert(a++) } alert(a);//永远不会执行 } alert(box());//弹出"function(){alert(a++)}" </script> ``` 在这里,我们只需将返回值赋值给某个变量,然后就可以像使用一般函数那样调用它了: ``` <script type="text/javascript"> var box = function(){ var a=1; return function(){ alert(++a) } } var newFunc = box(); newFunc();//2 </script> ``` 如果想让返回的函数立即执行,亦可以使用`box()()`来执行这段代码。 `ECMAScript`所有函数的参数都是按值传递的,言下之意就是参数不会按引用传递。 PS:如果存在按引用传递的话,那么函数里的那个变量将会是全局变量,在外部也可以访问。 ``` (1)值类型:数值、布尔值、null、undefined。 (2)引用类型:对象、数组、函数。 ``` `引用类型值`:指的是那些保存在堆内存中的对象,意思是,变量中保存的实际上只是一个指针,这个指针执行内存中的另一个位置,由该位置保存对象; ##创建匿名函数 ``` function(){ return ‘hi’; //单独的匿名函数是无法运行的,就算能运行也无法调用,因为没有名字 } ``` 这种匿名函数的用法在`JQuery`中非常多。直接声明一个匿名函数,立即使用。用匿名函数的好处就是省得定义一个用一次就不用的函数,而且免了命名冲突的问题,`js`中没有命名空间的概念,因此很容易函数名字冲突,一旦命名冲突以最后声明的为准。 在`javascript`语言里任何匿名函数都是属于`window`对象。在定义匿名函数时候它会返回自己的内存地址,如果此时有个变量接收了这个内存地址,那么匿名函数就能在程序里被使用了,因为匿名函数也是在全局执行环境构造时候定义和赋值,所以匿名函数的`this`指向也是`window`对象 ``` (function(){ console.log(this === window);//true })(); ``` 通过自我执行来执行匿名函数: ``` //通过自我执行来执行匿名函数 <script type="text/javascript"> (function (){ // (匿名函数)();第一圆括号放匿名函数,第二个圆括号执行 alert('Lee'); })(); </script> ``` 把匿名函数自我执行的返回值赋给变量: ``` //把匿名函数自我执行的返回值赋给变量 <script type="text/javascript"> var box = (function (){ alert('Lee'); })(); //弹出”Lee”; alert(box); //弹出 undefined,如果写出alert(box()),那么只会弹出一个"Lee" </script> var box= (function () { return 'hi'; })(); console.log(box);//hi ``` 自我执行匿名函数的传参: ``` //自我执行匿名函数的传参 <script type="text/javascript"> (function (age){ alert(age); })(100); //弹出100 </script> ``` ### 自执行函数的三种写法 ``` var result = function (){ alert(2); }(); ``` 另一种语法也可得到同样结果: ``` var result = (function () { console.log(2); })(); ``` 将函数返回值分配给变量: ``` var result = (function () { return 2; }()); ``` ##js创建动态函数: `js`支持创建动态函数,动态函数必须用`Function`对象来定义(Function是`js`中的一个对象,是固定不变的,规定`Function`对象的"F"必须大写,当是`function`的时候,我们知道是定义函数的时候所使用的一个关键字:`function funName(x,y)`,当是`Function`的时候(F大写的时候),我们知道是`js`中的对象) 创建动态函数的基本格式:`var 变量名 = new Function("参数1","参数2","参数n","执行语句");` 看下面的一段代码: ``` <script type="text/javascript"> var square = new Function ("x","y","var sum ; sum = x+y;return sum;"); alert("square(2,3)的结果是:"+square(2,3)); //square(2,3)的结果是:5 </script> ``` `square`是动态创建的函数,在`Function`对象后面的括号里的每一部分内容都必须是字符串形式的,也就是说都必须用引号(""或者是'')括起来 这段代码: ``` var square = new Function ("x","y","var sum ; sum = x+y;return sum;"); ``` 和下面这段代码: ``` //函数声明 function square (x,y){ var sum; sum = x+y; return sum; } ``` 是一摸一样的,只不过一个是`动态函数`,一个是`静态函数`。 我们为什么要把代码分成一小段一小段的代码呢?,把一个字符串分成了若干个独立的字符串的优点就在于我们可以通过修改其中的某些字符串来随时改变函数的作用。 ##回调函数 回调就是一个函数的调用过程。那么就从理解这个调用过程开始吧。函数a有一个参数,这个参数是个函数b,当函数a执行完以后执行函数b。那么这个过程就叫回调。 其实中文也很好理解:回调,回调,就是回头调用的意思。函数a的事先干完,回头再调用函数b。 这里必须清楚一点:函数b是你以参数形式传给函数a的,那么函数b就叫回调函数。 在jquery里的绝大多数效果函数都涉及到callback函数。 例如: ``` <script type="text/javascript"> $("div").show(1000,function(){ //callback function }); </script> ``` 这里的`callback function`换成实例可以是: ``` <script type="text/javascript"> $("div").show(1000,function(){ console.log("hello world") }); </script> ``` `Callback`实际上是,当一个函数执行完后,现执行的那个函数就是所谓的`callback`函数。怎么样?很好理解吧…… ##方法和函数的区别 ``` var arr = [1,2,3,4,5] var a =12; // 变量:自由的 arr.a= 5; //属性:属于一个对象 function show() //函数:自由的 { alert(‘a’); } arr.fn = function() //方法:属于一个对象 { alert(‘b’); } ``` 其实方法就是函数,只不过方法是有所属的对象。 我们所熟知的,将函数绑定到 `click` 事件 语法: ``` $(selector).click(function) ``` 参数描述function可选。规定当发生 click 事件时运行的函数. 这种形式在`jquery`中经常见到。它是将`function`当做该方法的参数,向该方法添加一个事件处理函数。 ##js全局函数 全局函数与内置对象的属性或方法不是一个概念。全局函数它不属于任何一个内置对象。 JavaScript 中包含以下 7 个全局函数,用于完成一些常用的功能: ``` escape( )、eval( )、isFinite( )、isNaN( )、parseFloat( )、 parseInt( )、unescape( )。 ``` ##函数的几个作用 ### 作为一个类构造器使用 ``` function Class(){} Class.prototype={}; var item=new Class(); ``` ### 作为闭包使用 ``` (function(){ //独立作用域 })(); ``` ### 作为构造函数调用 所谓构造函数,就是通过这个函数生成一个新对象(object)。 ``` <script type="text/javascript"> function Test(){//大写,以区分普通函数 this.x = 10; } var obj = new Test(); alert(obj.x); //弹出 10; </script> ``` 可以使用 `new` 运算符结合像 `Object()`、`Date()` 和 `Function()` 这样的预定义的构造函数来创建对象并对其初始化。面向对象的编程其强有力的特征是定义自定义构造函数以创建脚本中使用的自定义对象的能力。创建了自定义的构造函数,这样就可以创建具有已定义属性的对象。下面是自定义函数的示例(注意 `this` 关键字的使用)。 ``` function Circle (xPoint, yPoint, radius) { this.x = xPoint; // 圆心的 x 坐标。 this.y = yPoint; // 圆心的 y 坐标。 this.r = radius; // 圆的半径。 } ``` 调用 Circle 构造函数时,给出圆心点的值和圆的半径(所有这些元素是完全定义一个独特的圆对象所必需的)。结束时 Circle 对象包含三个属性。下面是如何例示 `Circle` 对象。 ``` var aCircle = new Circle(5, 11, 99); ``` 使用构造器函数的优点是,它可以根据参数来构造不同的对象。 缺点是构造时每个实例对象都会生成重复调用对象的方法,造成了内存的浪费。 ``` <script type="text/javascript"> function Test(name){ this.occupation = "coder"; this.name = name; this.whoAreYou = function(){ return "I'm " + this.name + "and I'm a " + this.occupation; } } var obj = new Test('trigkit4');//利用同一个构造器创建不同的对象 var obj2 = new Test('student'); obj.whoAreYou();//"I'm trigkit4 and I'm a corder" obj2.whoAreYou();//"I'm student and I'm a corder" </script> ``` 依照惯例,我们应该将构造器函数的首字母大写,以便显著地区别于一般的函数。 以下两种形式的定义函数方式是等价的。 ``` <script type="text/javascript"> var test = function(){ alert("Hello World"); } alert(typeof(test));//output function </script> ``` 这里明确定义了一个变量`test`,他的初始值被赋予了一个`function`实体 <br/> ``` <script type="text/javascript"> function test(){ alert("Hello World"); } alert(typeof(test));//output function </script> ``` <br/> 看看下面这种定义式函数形式: ``` <script type="text/javascript"> function test(){ alert("Hello World"); }; test();//居然输出Hello,很奇怪不是吗? function test(){ alert("Hello"); }; test();//正常滴输出了Hello </script> ``` 很显然,第一个函数并没有起到作用,很奇怪不是吗?我们知道,`javascript`解析引擎并不是一行一行地执行代码,而是一段一段地执行代码。在同一段程序的分析执行中,定义式的函数语句会被优先执行,所以第一个定义的代码逻辑已经被第二个覆盖了,所以两次调用相同函数,只会执行第二个。 ##作为值的函数 函数在`js`中不仅是一种语法,也是一个值。也就是说可以将函数赋值给变量,存储在对象的属性或数组的元素中,作为参数传入另一个函数中。 函数的名字实际是看不见的,它仅仅是变量的名字,这个变量指代函数对象 ``` <script type="text/javascript"> function square(x,y){ return x*y; } var s = square; //s和square指代同一个函数 square(2,3);//6 s(2,4);//8 </script> ``` 除了可以将函数赋值给变量,同样可以将函数赋值给对象的属性,当函数作为对象的属性调用时,函数就称为方法 ``` <script type="text/javascript"> var obj = {square:function(x,y){ //对象直接量 return x*y; }}; var ect = obj.square(2,3); </script> ``` ##prototype属性 每一个函数都包含prototype属性,这个属性指向一个对象的引用,这个对象称为原型对象。 ##call()和apply() `apply()`函数有两个参数:第一个参数是上下文,第二个参数是参数组成的数组。如果上下文是null,则使用全局对象代替。例如: ``` function.apply(this,[1,2,3]) ``` `call()`的第一个参数是上下文,后续是实例传入的参数序列,例如: ``` function.call(this,1,2,3); ``` ##高阶函数 这里的高阶函数可不是高数里的那个高阶函数,所谓高阶函数就是操作函数的函数,它接收一个或多个函数作为参数,并返回新函数 ##参数arguments 当函数被调用时,会得到一个免费奉送的参数数组,那就是`arguments`数组。通过它,函数可以访问所有它被调用时传递给他的参数列表。这使得编写一个无需指定参数个数的函数成为可能。 ``` <script type="text/javascript"> var sum = function(){ var i ,sum =0; for(i = 0;i<arguments.length;i+=1){ sum+=arguments[i]; } return sum; }; document.writeln(sum(4,5,23,13,35,46,-10));//116 </script> ``` 在`ECMAScript`中的参数在内部是用一个数组来表示的,函数接收到的始终都是这个数组,而不关心数组中包含哪些参数 ``` function add(num1,num2){ num = num1 + num2; return num; } var result = 12,count = 20; alert(add(result,count));//32;命名的参数只提供便利,解析器不会验证命名参数 ``` 实际上,`arguments`并不是一个真正的数组,它只是一个类数组的对象,它拥有一个`length`属性,但他缺少所有数组的方法。另外,`arguments`对象的长度是由传入的参数个数决定的,而不是由定义函数时的命名参数的个数决定的 函数在定义或者声明的时候,所有的参数都是形参,因此,我们可以根据实际情况来命名参数,函数也只有在被调用时才会传入实参。而每个函数在被调用时都会自动取得两个特殊变量:this 和 arguments ##函数的递归 函数的递归,即一个函数在通过名字调用自身的情况下构成的: 通过使用`argument.callee`代替函数名: ``` //arguments.callee是一个指向正在执行的函数的指针 <script> function factorial(num){ if(num<=1){ return 1; }else{ return num*arguments.callee(num-1); } } </script> ```