美文网首页
js中的函数

js中的函数

作者: 不会潜水的猫小喵 | 来源:发表于2019-03-29 19:46 被阅读0次
    • 函数的基本概念

    函数对于任何语言来说都是一个核心概念。通过函数可以封装任意多条语句,而且可以在任何地方任何时候调用执行。js中的函数使用function关键字来声明,后跟一组参数以及函数体,函数的基本格式如下:

    //函数基本格式
    function functionName(arg0, arg1, ..., argN) {
        statements
    }
    
    //函数示例
    function sayHi(name, message) {
        console.log("Hello" + name + "," + message);
    }
    

    函数通过其函数名来调用,后面还要加上一对圆括号和参数(圆括号中的参数如果有多个可以用逗号隔开)。具体如下所示:

    sayHi("World", "The wind is very strong today."); //HelloWorld,The wind is very strong today.
    

    js中的函数在定义时不必指定是否返回值。如果需要返回值,通过return语句跟要返回的值来实现返回值。例如:

    function sum(num1, num2) {
        return num1 + num2;
    }
    sum(1, 2);
    

    函数会在执行完return语句之后停止并立即退出。因此,位于return语句之后的任何代码都永远不会执行。

    function sum(num1, num2) {
        return num1 + num2;
        console.log("HelloWorld!"); //永远不会执行
    }
    

    return语句也可以不带任何返回值,在这种情况下,函数在停止执行后将返回undefined值。这种用法一般用在需要提前停止函数执行又不需要返回值的情况下。例如:

    function sayHi(name, message) {
        return;
        console.log("Hello" + name + "," + message); //永远不会调用
    }
    

    推荐的做法是要么让函数始终都返回一个值,要么永远都不要返回值。否则,如果函数有时候返回值,有时候不返回值,会给代码调试带来不便。

    • 定义函数的方法

    函数实际上是对象。每个函数都是Function类型的实例,而且都与其他引用类型一样具有属性和方法。由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。

    1. 使用函数声明语法定义
    function sum(num1, num2) {
        return num1 + num2;
    }
    
    1. 使用函数表达式定义
    var sum = function(num1, num2) {
        return num1 + num2;
    };
    

    3.使用Function构造函数

    var sum = new Function("num1", "num2", "return num1 + num2"); //不推荐
    

    从技术角度讲,第三种方法其实是一个函数表达式,但是不推荐使用这种方法定义函数,因为这种方法会导致两次解析代码(第一次是解析常规的js代码,第二次是解析传入构造函数中的字符串),从而影响性能。不过这种语法对于理解“函数是对象,函数名是指针”的概念非常直观。

    由于函数名仅仅是指向函数的指针,因此函数名与包含对象指针的其他变量没有什么不同。也就是说,一个函数可能会有多个名字,例如:

    function sum(num1, num2) {
        return num1 + num2;
    }
    
    console.log(sum(1, 2)); //3
    
    var anotherSum = sum;
    console.log(anotherSum(1, 2)); //3
    
    sum = null;
    console.log(anotherSum(1, 2)); //3
    
    • 函数声明与函数表达式

    解析器向执行环境中加载数据时,会率先读取函数声明,并使其在执行任何代码之前可用(可以访问);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。

    console.log(sum(1, 2)); //3
    function sum(num1, num2) {
        return num1 + num2;
    }
    

    以上代码完全可以正常运行。因为在代码开始执行之前,解析器就已经通过一个名为函数声明变量提升的过程,读取并将函数声明添加到执行环境中。对代码求值时,js引擎在第一遍会声明函数并将它们放到源代码树的顶部。如果像下面的例子所示的,把上面的函数声明改为等价的函数表达式,就会在执行期间导致错误。

    console.log(sum(1, 2)); //Uncaught TypeError: sum is not a function
    var sum = function(num1, num2) {
        return num1 + num2;
    }
    

    除了什么时候可以通过变量访问函数这一点区别之外,函数声明和函数表达式的语法其实是等价的。

    也可以同时使用函数声明和函数表达式,例如var sum = function sum() {}。不过,这种语法在Safari中会导致错误。

    • 函数的内部属性

    在函数内部,有两个特殊的对象:argumentsthis。其中,arguments是一个类数组对象,包含着传入函数中的所有参数。虽然arguments的主要用途是保存函数参数,但这个对象还有一个名叫callee的属性,该属性是一个指针,指向拥有这个arguments对象的函数。请看下面这个非常经典的阶乘函数。

    function factorial(num) {
        if(num <= 1) {
            return 1;
        } else {
            return num * factorial(num - 1);
        }
    }
    

    定义阶乘函数一般都要用到递归算法;如上面代码所示,在函数有名字,而且函数名字不会变的情况下,这样定义没有问题。但问题是这个函数的执行与函数名factorial紧紧耦合在了一起。为了消除这种紧密耦合的现象,可以像下面这样使用arguments.callee

    function factorial(num) {
        if(num <= 1) {
            return 1;
        } else {
            return num * arguments.callee(num - 1);
        }
    }
    

    在这个重写后的factorial()函数的函数体内,没有再引用函数名factorial。这样,无论引用函数时使用的是什么名字,都可以保证正常完成递归调用。例如:

    var anotherFactorial = factorial;
    factorial = function() {
        return 0;
    };
    
    console.log(anotherFactorial(5)); //120
    console.log(factorial(5)); //0
    

    函数内部的另一个特殊对象是thisthis引用的是函数据以执行的环境对象——或者也可以说是this值(当在网页的全局作用域中调用函数时,this对象引用的就是window)。

    window.color = "red";
    var  o = {
        color: "blue"
    };
    
    function sayColor() {
        console.log(this.color);
    }
    
    sayColor(); //red
    o.sayColor = sayColor;
    o.sayColor(); //blue
    
    • 函数的属性和方法

    因为js中的函数是对象,因此函数也有属性和方法。每个函数都包含两个属性:lengthprototype。其中length属性表示函数希望接收的命名参数的个数。对于js的引用类型而言,prototype是保存它们所有实例方法的真正所在。换句话说,诸如toString()valueOf()等方法实际上都保存在prototype名下,只不过是通过各自对象的实例访问罢了。在js中,prototype属性是不可枚举的,因此用for-in无法发现。

    每个函数都包含两个非继承而来的方法:apply()call()。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。

    apply()方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是Array的实例,也可以是arguments对象。

    function sum(num1, num2) {
        return num1 + num2;
    }
    
    function callSum1(num1, num2) {
        return sum.apply(this, arguments); //传入arguments对象
    }
    
    function callSum2(num1, num2) {
        return sum.apply(this, [num1, num2]); //传入数组
    }
    
    console.log(callSum1(1, 2)); //3
    console.log(callSum2(1, 2)); //3
    

    call()方法与apply()方法的作用相同,区别仅在于接收参数的方式不同。对于call()而言,第一个参数是this不变,变化的是其余参数必须逐个列举出来传递给函数。

    function sum(num1, num2) {
        return num1 + num2;
    }
    
    function callSum(num1, num2) {
        return sum.call(this, num1, num2); 
    }
    
    console.log(callSum(1, 2)); //3
    

    事实上,传递参数并非apply()call()真正的用武之地;它们真正强大的地方是能够扩充函数赖以运行的作用域,对象不需要与方法有任何耦合关系。

    window.color = "red";
    var  o = {
        color: blue
    };
    
    function sayColor() {
        console.log(this.color);
    }
    
    sayColor.call(this); //red
    sayColor.call(window); //red
    sayColor.call(o); //blue
    

    es5中还定义了一个方法:bind()。这个方法会创建一个函数的实例,其this值会被绑定到传递给bind()函数的值。

    window.color = "red";
    var  o = {
        color: blue
    };
    
    function sayColor() {
        console.log(this.color);
    }
    
    var objSayColor = sayColor.bind(o);
    objSayColor(); //blue
    

    每个函数继承的tolocaleString()toString()方法始终都返回函数的代码。返回代码的格式因浏览器而异——有的返回的代码与源代码一样,而有的则返回函数代码的内部表示,即由解析器删除了注释并对某些代码作了改动之后的代码。由于存在这些差异,我们无法根据这两个方法返回的结果来实现任何重要功能;不过,这些信息在调试代码时倒是很有用。另外一个继承的方法valueOf()方法同样也只返回函数代码。

    参考书籍:《JavaScript高级程序设计(第3版)》。

    相关文章

      网友评论

          本文标题:js中的函数

          本文链接:https://www.haomeiwen.com/subject/mlqrbqtx.html