zoukankan      html  css  js  c++  java
  • 小白科普之JavaScript的函数

    一 概述

    1.1 函数声明

    (1)function命令

      函数就是使用function命令命名的代码区块,便于反复调用。这种声明方式叫做函数的声明(Function Declaration)。

    function print(){
      // ...
    }

    (2)函数表达式

      除了用function命令声明函数,还可以采用变量赋值的写法。

    var print = function (){
      // ...
    };

      这种写法将一个匿名函数赋值给变量。这时,这个匿名函数又称函数表达式(Function Expression),因为赋值语句的等号右侧只能放表达式。采用函数表达式声明函数时,function命令后面不带有函数名。如果加上函数名,该函数名将成为函数内部的一个局部变量,只在函数体内部有效,在函数体外部无效。

    var print = function x(){
      console.log(typeof x);
    };
    
    x // ReferenceError: x is not defined
    
    print() // function

    (3)Function构造函数

      还有第三种声明函数的方式:通过Function构造函数声明。如果只有一个参数,该参数就是函数体。

    var add = new Function("x","y","return (x+y)");
    // 相当于定义了如下函数
    // function add(x, y) {
    //   return (x+y);
    // }

    1.2 return语句

      JavaScript引擎遇到return语句,就直接返回return后面的那个表达式的值,后面即使还有语句,也不会得到执行。也就是说,return语句所带的那个表达式,就是函数的返回值。return语句不是必需的,如果没有的话,该函数就不返回任何值,或者说返回undefined。

    1.3 第一等公民

      JavaScript的函数与其他数据类型处于同等地位,可以使用其他数据类型的地方就能使用函数。比如,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者作为函数的结果返回。这表示函数与其他数据类型的地方是平等,所以又称函数为第一等公民。

    function add(x,y){
      return x+y;
    }
    
    // 将函数赋值给一个变量
    var operator = add;
    
    // 将函数作为参数和返回值
    function a(op){
      return op;
    }
    a(add)(1,1) // 2

    1.4 函数名的提升

      JavaScript引擎将函数名视同变量名,所以采用function命令声明函数时,整个函数会被提升到代码头部。所以,下面的代码不会报错:

    f();
    function f(){}

      表面上,上面代码好像在声明之前就调用了函数f。但是实际上,由于“变量提升”,函数f被提升到了代码头部,也就是在调用之前已经声明了。但是,如果采用赋值语句定义函数,JavaScript就会报错:

    f();
    var f = function (){}; // TypeError: undefined is not a function

     上面的代码等同于:

    var f;
    f();
    f = function (){};

      当调用f的时候,f只是被声明,还没有被赋值,等于undefined,所以会报错。因此,如果同时采用function命令和赋值语句声明同一个函数,最后总是采用赋值语句的定义。

    // 由于不带var的function声明被提前解析,被带var的函数覆盖
    var
    f = function() { console.log ('1'); } function f() { console.log('2'); } f() // 1

    1.5 不能声明函数的情况

      函数声明语句并非真正的语句,ECMAScript规范只是允许他们作为顶级语句。它们可以出现在全局代码里,或者内嵌在其他函数里,但它们不能出现在循环、条件判断、或者try/catch/finally以及with语句中。

    1.6 name属性

      大多数JavaScript引擎支持非标准的name属性。该属性返回紧跟在function关键字之后的那个函数名。

    function f1() {}
    f1.name // 'f1'
    
    var f2 = function () {};
    f2.name // ''
    
    var f3 = function myName() {};
    f3.name // 'myName'

      上面代码中,函数的name属性总是返回紧跟在function关键字之后的那个函数名。对于f2来说,返回空字符串,对于f3来说,返回函数表达式的名字(真正的函数名还是f3,myName这个名字只在函数体内部可用)。

    2. 函数的实参和形参

    2.1 length属性

      所有函数都有一个length属性,返回函数定义中参数的个数。length属性提供了一种机制,判断定义时和调用时参数的差异,以便实现面向对象编程的”方法重载“(overload)。

    function f(a,b) {}
    
    f.length // 2

      上面代码定义了空函数f,它的length属性就是定义时参数的个数。不管调用时输入了多少个参数,length属性始终等于2。

    2.2 参数的省略

      参数不是必需的,Javascript语言允许省略参数。

    function f(a,b){
        return a;
    }
    
    f(1,2,3) // 1
    f(1) // 1
    f() // undefined
    
    f.length // 2

      上面代码的函数f定义了两个参数,但是运行时无论提供多少个参数(或者不提供参数),JavaScript都不会报错。被省略的参数的值就变为undefined。需要注意的是,函数的length属性与实际传入的参数个数无关,只反映定义时的参数个数。

      但是,没有办法只省略靠前的参数,而保留靠后的参数。如果一定要省略靠前的参数,只有显式传入undefined。

    2.3 传参方式

      JavaScript的函数参数传递方式是传值传递(passes by value),这意味着,在函数体内修改参数值,不会影响到函数外部。

    // 修改原始类型的参数值
    var p = 2; 
    
    function f(p){
        p = 3;
    }
    
    f(p);
    p // 2
    
    // 修改复合类型的参数值
    var o = [1,2,3];
    
    function f(o){
        o = [2,3,4];
    }
    
    f(o);
    o // [1, 2, 3]

      上面代码分成两段,分别修改原始类型的参数值和复合类型的参数值。两种情况下,函数内部修改参数值,都不会影响到函数外部。

      需要十分注意的是,虽然参数本身是传值传递,但是对于复合类型的变量来说,属性值是传址传递(pass by reference),也就是说,属性值是通过地址读取的。所以在函数体内修改复合类型变量的属性值,会影响到函数外部。

    // 修改对象的属性值
    var o = { p:1 };
    
    function f(obj){
        obj.p = 2;
    }
    
    f(o);
    o.p // 2
    
    // 修改数组的属性值
    var a = [1,2,3];
    
    function f(a){
        a[0]=4;
    }
    
    f(a);
    a // [4,2,3]

      上面代码在函数体内,分别修改对象和数组的属性值,结果都影响到了函数外部,这证明复合类型变量的属性值是传址传递。

      某些情况下,如果需要对某个变量达到传址传递的效果,可以将它写成全局对象的属性。

    var a = 1;
    
    function f(p){
        window[p]=2;
    }
    
    f('a'); //变量a本来是传值传递,但是写成window对象的属性,就达到了传址传递的效果。
    
    a // 2

    2.4 可变长的实参列表arguments对象

      ECMAScript函数不介意传递进来多少个参数,也不在乎传进来的参数是什么类型的,原因是在ECMAScript中的参数是用一个数组表示的,这个数字就是arguments。arguments对象包含了函数运行时的所有参数,arguments[0]就是第一个参数,arguments[1]就是第二个参数,依次类推。这个对象只有在函数体内部,才可以使用。

    var f = function(one) {
      console.log(arguments[0]);
      console.log(arguments[1]);
      console.log(arguments[2]);
    }
    
    f(1, 2, 3)
    // 1
    // 2
    // 3
    View Code

      arguments对象除了可以读取参数,还可以为参数赋值(严格模式不允许这种用法)。可以通过arguments对象的length属性,判断函数调用时到底带几个参数。

    (1)与数组的关系

      需要注意的是,虽然arguments很像数组,但它是一个对象。某些用于数组的方法(比如slice和forEach方法),不能在arguments对象上使用。但是,有时arguments可以像数组一样,用在某些只用于数组的方法。比如,用在apply方法中,或使用concat方法完成数组合并。

    // 用于apply方法
    myfunction.apply(obj, arguments).
    
    // 使用与另一个数组合并
    Array.prototype.concat.apply([1,2,3], arguments)

    (2)caller和callee

      callee属性指代当前正在执行的函数,caller指代调用当前正在执行的函数的函数。用caller属性可以访问调用栈。

    3 函数调用

    3.1 作为函数调用

    var total = distance(0,0,2,1) + distance(2,1,3,5);
    var probability = factorial(5) / factorial(13);

    3.2 作为方法调用

      一个方法就是保存在一个对象的属性里的JavaScript函数。例如:

    o.m = f; //给o定义一个名为m()的方法
    o.m() //调用m()函数的方法
    o.m(x.y) // m()函数包含两个参数

      方法调用和函数调用有一个重要的区别,即:调用上下文。属性访问表达式由两部分组成:一个对象(例如o)和属性名称(m)。在像这样的方法调用表达式里,对象o成为调用上下文,函数体可以使用关键字this引用该对象。

    3.3 作为构造函数

    var o = new Object();

      如果函数或方法调用之前带有关键字new,它就构成了构造函数调用。构造函数调用会试图创建一个新的空对象,这个对象继承自构造函数的prototype属性。

    3.4 间接调用

      JavaScript中的函数也是对象,和其他JavaScript对象没有两样,函数对象也可以包含方法。其中call()和apply()可以用来间接的调用函数。两个方法都允许显式指定调用所需的this值,也就是说,任何函数可以作为任何对象的方法来调用,哪怕这个函数不是这个对象的方法。

      call()和apply()看做某个对象的方法,通过调用方法的形式来间接调用函数。call()和apply()的第一个参数是要调用函数的母对象,它是调用上下文(运行函数的作用域),在函数体内通过this来获得对它的引用。

    // 以对象o的方法来调用函数f()
    f.call(o);
    f.apply(o);

      对于call()来说,第一个调用上下文参数之后的所有实参就是要传入待调用函数的值。

    f.call(o,1,2); //以对象o的形式调用函数f(),并传入两个参数

      apply()方法和call()方法类似,但传入实参的形式和call()有所不同,它的实参都放入一个数组中。传入apply()的参数数组可以是类数组对象也可以是真实数组。

    f.apply(o, [1,2]);

    4 作为命名空间的函数

      由于在函数中声明的变量在整个函数体内都是可见的,在函数的外部是不可见的。而不在任何函数中声明的变量是全局变量,为了不污染全局命名空间,可以声明一个函数用作临时的命名空间。

    // 将代码放入一个函数内,然后调用这个函数
    (function(){ // 代码模块 }());

      function之前的左圆括号是必需的,因为如果不写这个左圆括号,JavaScript解释器会试图将关键字function解析为函数声明语句。使用圆括号JavaScript解释器才会正确地将其解析为函数定义表达式。

    var students = (function() {
        // 这里定义了很多类如课程类/成绩类, 使用局部变量和函数
        function Subject() { /* ... */ }
        function Grade() { /* ... */ }
    
        // 通过返回命名空间对象将API导出
        return {
            Subject: Subject,
            Grade: Grade
        };
    }());

    5. 函数属性、方法  

    5.1 length属性

      函数的length属性是只读的,代表函数实参的数量,这里的参数指的是“形参”而非“实参”,也就是函数定义时给出的形参个数,通常也是在函数调用时期望传入的实参个数。

    function check(args){
        var actual = args.length;
        var expected = args.callee.length;
        if(actual !== expected){
            throw Error("Expected "+expected+"args; got"+actual);
        }
    }
    function f(x, y, z){
        check(arguments);
        return x+y+z;
    }
    console.log(f(1,2,3)); //6
    View Code

    5.2 prototype属性

      每个函数都包含一个prototype属性,这个属性是指向一个对象的引用,这个对象称作“原型对象”。当将函数用做构造函数时,新创建的对象会从原型对象上继承属性。

    5.3 bind()方法

      bind()方法:将函数绑定至某个对象。在函数f()上调用bind()方法并传入一个对象o作为参数时,这个方法返回一个新函数。调用新的函数将会把原始的函数f()当做o的方法来调用。传入新函数的任何实参都是传入原始函数。

    function f(y) {return this.x + y;}
    var o = {x:1};
    var g = f.bind(o);
    console.log(g(2)); // 3 

    5.4 toString()方法

      toString方法返回函数的源码。

    function f() {
      a();
      b();
      c();
    }
    
    console.log(f.toString());
    // function f() {
    //  a();
    //  b();
    //  c();
    // }
    View Code

     

  • 相关阅读:
    Linux记录-shell实现脚本监控服务器及web应用
    Hadoop记录-hadoop和hbase监控有那些比较好的工具
    Hadoop记录-Ganglia监控HDFS和HBase指标说明
    Linux记录-CPU指标介绍
    Linux记录-I/O系统监控
    Linux记录-linux系统监控命令汇总
    Hadoop记录-hadoop2.x常用端口及定义方法
    Linux记录-linux系统常用监控指标
    在IIS6上部署WebService
    《软件测试自动化之道》读书笔记 之 请求-响应测试
  • 原文地址:https://www.cnblogs.com/borage/p/4419454.html
Copyright © 2011-2022 走看看