• (第三天)函数


    定义函数

    关键字function用来定义函数。定义函数有两种方法

    (1)函数定义表达式

    1 var f = function(x) { return x+1; }

    (2)函数声明语句

    1 function funcname([arg1 [, arg2 [...,argn]]]) {
    2 
    3 }

     函数声明语句通常出现在JavaScript代码的最顶层,也可以嵌套在其他函数体内。但在嵌套时,函数声明只能出现在所嵌套函数的顶部。也就是说函数定义不能出现在if语句、while语句后者其他语句中。

    二者异同

    (1)都创建了相同的新函数对象

    (2)函数声明语句中的函数名是一个变量名,变量指向函数对象。函数定义表达式并未声明一个变量,如果一个函数定义表达式包含名称,函数的局部作用域将会包含一个绑定到函数对象的名称。所以名称存在函数体中,并指代该函数本身,也就是说函数的名称将成为函数内部的一个局部变量。【注】函数定义表达式特别适合用来定义那些只会用到一次的函数

    (3)函数声明语句中的函数被显式地“提前”到了脚本或函数顶部。因此它们在整个脚本和函数内都是可见的。但以函数表达式定义函数则不同,想要调用此定义的函数,必须要引用它,而要使用一个表达式方式定义的函数之前,必须要把他赋值给一个变量,前面说过:变量的声明提前了,但给变量赋值是不会提前的,所以,以表达式方式定义的函数在定义之前无法调用。如下代码:

    1 person();   /*函数声明语句显式提前至顶部,所以能在定义之前调用*/
    2 function person() {}
    3 person();              /*在函数定义之后调用毋庸置疑正确*/
    1 p();
    2 
    3 var p = function(){
    4 
    5 }
    6 
    7 /*
    8 打印出:undefined is not a function 变量p还未初始化,因此函数定义表达式无法在函数定义之前调用
    9 */

    函数调用 

    (1)作为函数

    1 function person(age,name){
    2 
    3 }
    4 
    5 person(12,"嘿嘿");   /*函数调用*/

    (2)作为方法

     1 var calculator = {
     2       operator1 : 1,
     3       operator2 : 2,
     4       add: function(){
     5              this.result = this.operator1 + this.operator2;
     6       }
     7 
     8 };
     9 
    10 calculator.add();      //方法调用计算1+1的结果 
    11 calculator.result      =>2
    12  
    13 /*
    14 通过对象直接量来创建属性和方法,此时calculator作为调用上下文,所以this关键字则引用该对象
    15 */

    (3)作为构造函数

    1 var o = new Object();
    2 
    3 var o = new Obejct;
    4 
    5 /*
    6 构造函数调用创建一个新的空对象,这个对象继承自构造函数的prototype属性。构造函数试图初始化这个新创建的对象,并将这个对象用做其调用上下文,因此构造函数可以使用this关键字来引用这个新创建的对象
    7 */

    【注】如果构造函数没有形参,JavaScript构造函数调用的语法是允许省略实参列表和圆括号的。所以凡是没有形参的构造函数调用都可以省略圆括号。

    (4)通过它们的call()和apply方法间接调用 【后面讲】

    this关键字

    this是 一个关键字,不是变量,也不是属性名。JavaScript的语法不允许给this赋值。和变量不同,关键字this没有作用域的限制,嵌套的函数不会从调用它的函数中继承this。对此我们用实例说明

     1 var o ={
     2       m: function() {
     3              var self = this;
     4              console.log(this === o);  (1 5              f();
     6             
     7              function() {
     8                      console.log(this === o);  (2 9                      console.log(self == o);     (310              }
    11       }
    12 }
    13 
    14 /*
    15 上述(1)中打印出true,因为this就指代o。(2)中输出false,此时this是全局对象或undined。(3)输出true,self指代外部函数的this值
    16 */

    综上所知:

    (1)如果嵌套函数作为方法调用,其this的值指向调用它的对象。

    (2)如果嵌套函数做为函数调用,其this值不是全局对象(非严格模式下)就是undefined(严格模式下)。

    (3)如果想访问外部函数的this的值,需要将this的值保存在一个变量里,这个变量和内部函数都同在一个作用域内。通常使用变量self来保存this。

    作为命名空间的函数

    引入

    当有一段JavaScript模块代码,这段代码将要用在不同的JavaScript程序中(对于客户端JavaScript来讲通常是用在各种各样的网页中)。假如这段代码定义了一个用以存储中间计算结果的变量。如此就出现一个问题,当模块代码放在不同的程序中时,你无法得知这个变量是否已经建好,如果已经存在这个变量,那么将会和代码发生冲突。解决办法就是将代码放入一个函数内,然后调用这个函数。这样全局变量就变成了函数内的局部变量。用匿名函数来定义,用法如下

    1 (function(){
    //模块代码中所使用的变量都是局部变量
    2 console.log(12) (13 }()); //结束函数定义并立即调用它 4 5 或者 6 7 (function(){ 8 console.log(12); (29 })();

    这里有个疑问尚未解决,如果有园友知道,希望能帮助我解决,谢谢!不知道大家注意到上述两个匿名函数的不同之处,对,在(1)中输出的最后没有加分号,若加分号则错误,无法输出,不加分号则正常输出,(2)中则是好使的!希望得到各位园友的帮助,在此表示感谢!

    这种定义匿名函数并立即在单个表达式中调用它的写法很常见,如代码检测中是否出现了一个bug,如果出现这个bug,就返回一个带补丁的函数的版本! 

    函数属性、方法

    (1)length属性

     length到没什么可说的,最主要是要属函数的arguments的属性了,在函数体内,arguments.length表示传入函数的实参的 个数,以此来模拟函数重载。请看下面代码

     1                          function person(age, name, gender, addr) {
     2                              this.age = age;
     3                              this.name = name;
     4                              this.gender = gender;
     5                              this.addr = addr;
     6                                 /*获得传入实参的个数*/ 
     7                              switch(arguments.length)
     8                              {
     9                                   case  1:
    //
    10 break; 11 case 2:
    //
    12 break; 13 case 3:
    //
    14 break; 15 case 4:
    //
    16 break; 17 } 18 } 19 20 var p = new person(1,'嘿嘿'); 21 var p1 = new person(1,'嘿嘿',false); 22 var p2 = new person(1,'嘿嘿',false,'hunan'); 23 24

    补充:arguments的callee属性

    引入

    1             var factorial = function(x) {
    2                 if (x <= 1) return 1;
    3                 return x * factorial(x - 1);
    4             }
    5             console.log(factorial(5));
    6                 /*打印出120*/

    上述代码为求一个数的阶乘,毫无疑问没有错误。现在进行一点小改动,如下

    1             var factorial = function(x) {
    2                 if (x <= 1) return 1;
    3                 return x * factorial(x - 1);
    4             }
    5             var fact2 = factorial;
    6             factorial = function() {
    7                 return 0;
    8             }
    9             console.log(fact2(5));

    上述很明显fact2变量指向两个函数,当要求调用fact2(5)时,先执行 第一个 factorial函数,当执行到 return x * factorial(x - 1); 时,这时执行的就是 第二个 factorial函数,所以此时打印出0。很显然这不是我们想要的结果,我们需要的是求阶乘即执行的函数接下来还是它本身也就是第一个,所以这个时候callee()方法就派上了用场:用于调用自身。所以上述代码这样修改即可 

    1             var factorial = function(x) {
    2                 if (x <= 1) return 1;
    3                 return x * arguments.callee(x - 1);
    4             }
    5             var fact2 = factorial;
    6             factorial = function() {
    7                 return 0;
    8             }
    9             console.log(fact2(5));

    【注】arguments还有一个长得相似callee的属性就是caller,而在非严格模式下,ECMAScript标准规范规定callee属性指代当前正在执行的函数。caller是非标准的但大多数浏览器都 实现了这个属性,它指代当前正在执行的函数的函数。通过caller属性可以访问调用栈。而通过callee来进行递归调用自身,因为它保存了当前执行方法的地址,而不会出差错。

    (2) prototype属性

    每个函数都包含一个prototype属性,这个属性指向一个对象的引用,这个对象叫做原型对象。每一个函数都包含不同的原型对象。当将函数用做构造函数的时候,新创建的对象会从原型对象上继承属性。有关原型对象前面已讲,请参考原型、继承这一讲

    (3)call()和apply()方法

    这两种方法可以用来间接地调用函数,两个方法都允许显式指定调用所需的this的值,任何函数可以作为任何对象的方法来调用,哪怕这个函数不是那个对象的方法。两个方法都可以指定调用的实参。call()方法使用它自有的实参列表作为函数的实参,apply()方法则要求以数组的形式传入参数。在ECMAScript5的严格模式中,call()和apply()的第一个实参都会变为this的值,哪怕传入的实参是原始值甚至是null或undifined。在ECMAScript3和非严格模式中,传入的null和undefined都会被全局对象代替,而其他原始值则会被相应的包装对象所替代。

     下面用代码来解释上述概念

    (1)call()方法

     1                 function person(age,name){
     2                     this.age=age;
     3                     this.name=name;
     4                 }
     5    
     6                 var obj=new Object();
     7                 person.call(obj,12,'小黑');
     8                 console.log(obj.age);
     9                 console.log(obj.name);
    10 
    11                /*
    12                创建空对象obj,此时调用person类的call()方法,并将obj传递进去,此时obj成为其调用上下文,此时this即obj,最终能打印出12和小黑
    13                */

    (2)apply()方法 

     更多用法请参考:apply()详情

    (3)bind()方法

    bind()是在ECMAScript5中新增的方法,但在ECMASript3中可以轻易模拟bind(),从名字可以看出,这个方法的主要作用就是将函数绑定至某个对象上。

     1 function f(y){
     2        return this.x + y;
     3 }
     4 var o = { x : 1 };
     5 var g =  f.bind(o);
     6 console.log(g(2));
     7 
     8 function bind(f,o){
     9        if(f.bind) return f.bind(o);
    10        else return function(){
    11               return f.apply(o, arguments);
    12        };
    13 }

    上述将函数f绑定至对象o上,当在函数f()上调用bind()方法并传入一个对象o作为参数,这个方法将返回一个新的函数。调用新的函数将会把原始的函数f()当做o的方法来调用。传入新函数的任何实参都将传入原始函数。上述中 f.bind(o); 则此时函数f()中的this即为o,此时this.x=1,绑定后返回新的函数g,再调用函数g(2),此时函数f()即为其参数,所以打印出3。

    函数传参

    (1)传递原始类型(所谓c#中的值类型)

    1                          function person(age){
    2                              age++;
    3                          }
    4                          var age = 12;
    5                          person(age);
    6                          console.log(age);

    根据上述代码你觉得会打印出多少呢?我们一句一句分析:

     (1)var age = 12;栈上开辟一段空间地址假设为0x1,此时0x1存的是变量age并且其值为12。

     (2)person(age);调用函数person并将实参传进去,此时person中的形参相当于是局部变量,所以同样在栈上开辟一段空间地址为0x2,变量为age,将上述(1)中的值赋给(2)中的age所以同样为12,然后进入函数体内,将age++,此时相加的是新创建的局部变量的值,并未改变(1)中的age值。

    (3)综上打印出12。

    (2)传递对象(所谓c#中的引用类型)

    1                          function person(p){
    2                                 p.age++;
    3                          }
    4                          var p = { age : 12 };
    5                          person(p);
    6                          console.log(p.age);

    同理进行分析

    (1) var p = { age : 12 };首先在栈上开辟一段空间地址为0x1值为null,然后右边在堆上首先开辟一段空间地址为0x2的空对象,然后定义属性age并且其值为12,并且此对象指向变量p,所以此时变量的值为0x2。

    (2)person(p);同样p此时相当于是person类的局部变量,首先在栈上开辟一段空间地址为ox3名字为p的变量,此时将(1)中p的存的值0x2赋给0x3中p的值,所以此时0x3中的值为0x2,也就是此时ox2指向了堆上那个地址为0x2,属性为age值为12的对象。然后进入函数体内部,对其年龄进行age++,此时堆上对象的属性age变为13。

    (3)综上,此时打印出的P中的age为13。

     总结传参

    无论是传递原始类型(值类型)还是对象(引用类型)只需要看栈上变量的值是存的值还是地址,若存的是值,则相当于复制一份即副本不会改变原来的值,若是变量的值存的是地址,若在函数体内改变值则原来对象中的值也会受影响。

  • 相关阅读:
    java 数据结构(一):java常用类 一 String类
    java 数据结构(三):java常用类 三 日期时间API
    java 数据结构(四):java常用类四 比较器以及其他类
    java 数据结构(五):数据结构简述
    Android开发 无法导入ViewPagerIndicator或其他开源框架无法导入
    开源控件ViewPagerIndicator的使用
    Android简易实战教程--第二十五话《网络图片查看器》
    The type org.apache.http.HttpResponse cannot be resolved. It is indirectly referenced from required
    Mac 下安装node.js
    Android简易实战教程--第二十四话《画画板》
  • 原文地址:https://www.cnblogs.com/CreateMyself/p/4692357.html
Copyright © 2020-2023  润新知