第2章 第5节 Function类型(存储方式)

当script脚本在执行前,会逐句扫描一遍,碰见函数 function sum(num1,num2){...} ,就以类似Math对象的函数存储成window的函数对象。(就是函数提升)

                                                                   碰见函数表达式 sum=function(num1,num2){......} ,就会把他当成一个变量来处理,给他赋值undefined。

 

以前我们总认为函数是一段代码,是程序部分,从来认为它们应该和if else等那样存储在程序代码中。没有考虑过它也可以像存储数据的方式那样,作为Object的子类,来存储。
Javascript中什么都是对象,给了一个保存数据的统一模板。发现函数也可以套用到这个模板中,来进行保存和调用
所以JavaScript中的函数不但有函数的可重复调用的一个完成特定功能的工具,同时它也有对象的可以赋给变量,可以作为变量等对象的功能
在这里插入图片描述

图 函数是对象的子类

函数是一段代码,它只定义一次,但可以被执行或调用任意次。

在 JavaScript 里,函数即对象,程序可以随意操控它们。
比如,可以把函数赋值给变量,
或者作为参数传递给其他函数,
也可以给它们设置属性,
甚至调用它们的方法。
如果函数挂载在一个对象上,作为对象的一个属性,就称它为对象的方法。
如果函数嵌套在其他函数中定义,这样它们就可以访问它们被定义时所处的作用域中的任何变量。

函数定义

在 JavaScript 中,函数实际上是对象,每个函数都是 Function 构造函数的实例,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。函数通常有以下3中定义方式。例如:

// 写法一:函数声明(推荐写法) 
function sum (num1, num2) { 
return num1 + num2; 
} 
// 写法二:函数表达式(推荐写法)
var sum = function(num1, num2){ 
return num1 + num2; 
}; 
// 写法三:Function 构造函数(不推荐写法)
 var sum = new Function("num1", "num2", "return num1 + num2");

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

function sum(num1, num2){ 
return num1 + num2; 
} 
console.log(sum(10,10)); // 20

var anotherSum = sum;
console.log(anotherSum(10,10)); // 20

sum = null; 
console.log(anotherSum(10,10)); // 20

没有重载
将函数名想象为指针,也有助于理解为什么 JavaScript 中没有函数重载的概念。

function addSomeNumber(num){
 return num + 100; 
}

function addSomeNumber(num) {
 return num + 200; 
}

var result = addSomeNumber(100); // 300

显然,这个例子中声明了两个同名函数,而结果则是后面的函数覆盖了前面的函数。以上代码实际上与下面的代码没有什么区别。

函数声明与表达式

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script>
        alert(sum(10,10)); // 20,不会报错
        function sum(num1, num2){
            return num1 + num2;
        }


      alert(sum1(12,12)); // 会报错,uncaught TypeError:sum1 is not function
        var sum1=function(num1, num2){
            return num1 + num2;
        }
    </script>
</head>
<body>
</body>
</html>

**说明:**第一段代码正常执行,因为在代码开始执行之前,解析器就已经通过一个名为函数声明提升(function declaration hoisting)的过程,读取并将函数声明添加到执行环境中。对代码求值时,JavaScript引擎在第一遍会声明函数并将它们放到源代码树的顶部。所以,即使声明函数的代码在调用它的代码的后面,JavaScript引擎也能把函数声明提升到顶部。

第二段代码出错,原因在于函数位于一个初始化语句中,而不是一个函数声明。也就是说,在执行到函数所在的语句之前,sum1并不会保存有对函数的引用。

作为值的函数

因为 JavaScript 中的函数名本身就是变量,所以函数也可以作为值来使用。也就是说,不仅可以像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回。来看一看下面的函数。

function callSomeFunction(someFunction, someArgument){ 
    return someFunction(someArgument);
 }

这个函数接受两个参数。第一个参数应该是一个函数,第二个参数应该是要传递给该函数的一个值。然后,就可以像下面的例子一样传递函数了。

function add10(num){
   return num + 10;
}

var result1 = callSomeFunction(add10, 10);
 console.log(result1); // 20

function getGreeting(name){
   return "Hello, " + name;
}

var result2 = callSomeFunction(getGreeting, "Nicholas");
console.log(result2); // "Hello, Nicholas"

这里的 callSomeFunction() 函数是通用的,即无论第一个参数中传递进来的是什么函数,它都会返回执行第一个参数后的结果。要访问函数的指针而不执行函数的话,必须去掉函数名后面的那对圆括号。因此上面例子中传递给 callSomeFunction() 的是 add10 和 getGreeting,而不是执行它们之后的结果。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script>
        function callSomeFunction(someFunction, someArgument){
            return someFunction(someArgument);
        }

        function add10(num){
            return num + 10;
        }

        var result1 = callSomeFunction(add10, 10);
        console.log(result1);   // 20

        function getGreeting(name){
            return "Hello, " + name;
        }

        var result2 = callSomeFunction(getGreeting, "Nicholas");
        console.log(result2);   // "Hello, Nicholas"
    </script>
</head>
<body>



</body>
</html>

调试F9一步一步调试演示。
在这里插入图片描述

作为返回值的函数,例子:(获取一个根据指定属性名排序的函数)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script>
        function getComparisonFunction(propertyName){
            return function(object1,object2){
                var value1 = object1[propertyName];//使用[]可根据变量访问属性
                var value2 = object2[propertyName];

                if(value1<value2){
                    return -1;
                }else if(value1>value2){
                    return 1;
                }else{
                    return 0;
                }
            }
        }

        var data  = [{name:"zhangsan",age:25},{name:"lisi",age:26}];
        data.sort(getComparisonFunction("name"));//根据name排序,lisi,zhangsan
        console.log(data);
        data.sort(getComparisonFunction("age"));//根据age排序,zhangsan,lisi
        console.log(data);

    </script>
</head>
<body>



</body>
</html>

函数内部属性

函数内部有两个特殊的对象,arguments(类数组对象)和this:arguments包含着传入函数中的所有参数。虽然arguments对象的主要用途是保存函数参数,但这个对象还有一个名叫callee的属性,该属性是一个指针,指向拥有这个arguments对象的函数。

经典的阶乘函数

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script>
        function factorial(num){
            if (num <= 1) {
                return 1;
            } else {
                return num * factorial(num-1)
            }
        }
        //使用callee改造
        function factorial(num){
            if (num <=1) {
                return 1;
            } else {
                return num * arguments.callee(num-1)
            }
        }

        var trueFactorial = factorial;//让trueFactorial指向阶乘函数

        factorial = function(){//解除factorial与阶乘函数的关联
            return 0;
        };

       
        console.log(trueFactorial(5));  // 120
        console.log(factorial(5));      // 0

    </script>
</head>
<body>



</body>
</html>

this引用的是函数据以执行的环境对象,当在全局作用域中调用函数时,this对象引用的就是window

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script>
        window.color = "red";
        var o = { color: "blue" };

        function sayColor(){
            console.log(this.color);
        }
        sayColor();     // "red"

        o.sayColor = sayColor;
        o.sayColor();   // "blue"
    </script>
</head>
<body>



</body>
</html>

ECMAScript5中也规范化了另一个函数对象的属性:caller。这个属性中保存着调用当前函数的函数的引用(如果在全局作用域中调用,它的值为null)

在这里插入图片描述

函数属性和方法

  • length:表示函数希望接收的命名参数的个数
  • prototype:对于ECMAScript中的引用类型而言,prototype是保存它们所有实例方法的真正所在。toString()和valueOf()等方法都保存在prototype名下,只不过是通过各自对象的实例访问罢了。prototype是不可枚举的,无法使用for-in发现。
    每个函数都包含两个非继承而来的方法:apply()和call()。用途就是在特定的作用域中调用函数,相当于设置函数体内this对象的值。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script>
        function sayName(name){
            console.log(name);
        }

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

        function sayHi(){
            console.log("hi");
        }

        console.log(sayName.length);      // 1
        console.log(sum.length);          // 2
        console.log(sayHi.length);        // 0
    </script>
</head>
<body>



</body>
</html>
  • apply():接收两个参数,一个是在其中运行函数的作用域,另一个是参数数组(可以是Array的实例,也可以是arguments对象),如:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script>
        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(10,10));   // 20
        console.log(callSum2(10,10));   // 20
    </script>
</head>
<body>



</body>
</html>
  • call()方法与apply()方法的作用相同,它们的区别仅在于接收参数的方式不同。call()方法接收参数时必须逐个列出来(明确传入的每个参数)
    使用apply()与call()方法扩充函数赖以运行的作用域。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script>
        window.color = "red";
        var o = { color: "blue" };

        function sayColor(){
            console.log(this.color);
        }
        sayColor();                // red

        sayColor.call(this);       // red
        sayColor.call(window);     // red
        sayColor.call(o);          // blue
    </script>
</head>
<body>



</body>
</html>

注意:使用call()或apply()来扩充作用域的好处,就是对象与方法不需要有任何耦合

  • bind():创建函数的实例,this指向传给bind()函数的值。
    在这里插入图片描述
posted @ 2022-04-06 23:53  szmtjs10  阅读(65)  评论(0编辑  收藏  举报