5.5 function类型

function类型

函数实际上是对象,每个函数都是function;类型的示例。都与其它引用类型一样具有属性和方法。由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。函数通常是使用函数声明语法定义的,如下所示:

function sum(num1,num2) {
return num1 + num2;
}
//与下面使用函数表达式定义函数的方式几乎相差无几
var sum = function(num1,num2) {
return num1+ num2;
}

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

function sum(num1,num2) {
return num1 + num2;
}
alert(sum(10,10)); // 20

var anotherSum = sum;
alert(anotherSum(10,10)); //20

su, = null;
alert(anotherSum(10,10)) //20

注意:使用不带圆括号的函数名是访问函数指针,无非调用函数,此时,anotherSum和sum就都指向了同一个函数,因此,anotherSum()也可以被调用并返回结果。即使将sum设置为null,让它与函数‘断绝关系’’,但仍可以正常调用anotherSum()。

5.5.1 没有重载(深入理解)

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

function sum(num1) {
return num1 + 100;
}

function sum(num1) {
return num1 + 200;
}
var result = sum(100); //300

通过观察重写之后的代码,很容易看清楚: 在创建第二个函数时,实际上覆盖了引用第一个函数的变量sum.

5.5.2 函数声明与函数表达式

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

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

即使声明函数的diamante在调用它的代码后面,javascript引擎也能把函数声明提升到顶部,但是如下所示,把上面的函数声明改为等价的函数表达式,就会在执行期间导致错误。

alert(sum(10,10));
var sum = function(num1,num2) {
return num1 + num2;
} 

以上代码之所以会在运行期间产生错误,原因在于函数位于一个初始化语句中,而不是一个函数声明。
换句话说,在执行到函数所在的语句之前,变量sum中不会保存有对函数的引用;
而且。由于第一行代码就会导致‘unexpected identity’(意外标识符)错误,实际上也不会执行到下一行。
除了什么时候可以通过变量访问函数这一点区别之外,函数声明与函数表达式的语法其实是等价的。

5.5.3作为值的函数

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

function sum(sum1, a){
return sum1(a);
}
//这个函数接受两个参数,第一个参数应该是一个函数,第二个参数应该是要传递给该函数的一个值。
function add(num){
return num + 10;
}
var result = sum(add,10);
alert(result); //20

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

当然,可以从一个函数中返回另一个函数,这也是极为有用的一种技术

function createFucntion(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:28
    },
    {
        name: 'lisi',
        age:18
    }
];
data.sort(createFucntion("name"));
alert(data[0].name); // 'lisi'

data.sort(createFucntion("age"));
alert(data[0].name); // 'zhangsan'

我们调用createFucntion(“name”)方法创建一个比较函数,以便按照每个对象的name属性值进行排序。
同理createFucntion(“age”)也是一样;

5.5.4 函数内部属性

在函数内部,有两个特殊的对象,argumentsthis
其中,argumen是一个类数组对象,包含着传入函数中的所有参数。虽然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。这样,无论引用函数时使用的是什么名字,都可以保证正常完成递归调用。比如:

function factorial(num) {
    if(num <= 1) {
        return 1;
    }else {
        return num * arguments.callee(num-1);
    }
}
var truFactorial = factorial;   //实际上保存了函数的指针
factorial = function(){
    return 0;
}
alert(truFactorial(5));  //120
alert(factorial(5));  //0

函数内部的另一个特殊对象是this,其行为与java和c#中的this大致类似。
换句话说,this引用的是函数据以执行的环境对象 – 或者说是this值(当前网页的全局作用域中调用函数时,this对象引用就是window);比如;

window.color = 'red';
var o = {color: 'blue'};
function sayColor() {
    alert(this.color);
}

sayColor(); //'red'

o.sayColor = sayColor;
o.sayColor(); //'blue'

函数sayColor()是在全局作用域中定义的,它引用了this对象。由于在调用函数之前,this的值并不确定。因此this可能会在代码执行过程中引用不同的对象。当在全局作用域中调用sayColor()时,this引用的是全局对象window;换句话说,对this.color求值会转换成对window.color求值,于是结果返回‘red’;

而当这个函数赋给对象o并调用o.sayColor()时,this引用的对象是o 。因此对this.color求值,结果就返回‘blue’;

5.5.5函数属性和方法

每个函数都包含两个属性: lengthprototype
length :表示函数希望接受的命名参数的个数
prototype: 保存所有实例方法的真正所在;是不可枚举的,因此使用for-in无法发现。(第六章)

function sum(a){
    alert(a);
}
function sum1(a,b){
    return a + b;
}
function sum2(){
    alert("a");
}
alert(sum.length); //1
alert(sum1.length); //2
alert(sum2.length);  //0

每一个函数都包含两个非继承而来的方法: apply() 和call().这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内this 对象的值。首先,apply()方法接收两个参数: 一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数是array的实例,也可以是arguments对象。例如:

function sum(a ,b) {
    return a +b;
}
function callSum1(a ,b){
    return sum.apply(this, arguments);  //传入arguments 对象
}
function callSum2(a ,b){
    return sum.apply(this, [a,b]);   //传入数组
}
alert(callSum1(10,10));  //20
alert(callSum2(10,10));   //20

call()方法和apply()方法的作用相同,它们的区别仅在于接收参数的方式不同,对于call()方法而言,第一个参数是this值没有变化,变化的是其余参数都是直接传递给函数的。换句话说,在使用call()方法时,传递给函数的参数必须逐个列举出来,如下所示:

function sum(a ,b) {
    return a +b;
}
function callSum1(a ,b){
    return sum.call(this, a, b);  
}
alert(callSum1(10,10));  //20

call()方法和apply()方法,它们真正强大的地方是能后扩充函数赖以运行的作用域;比如:

window.color = 'red';
var o = {
    color: 'blue'
};
function sayColor() {
    alert(this.color);
}
sayColor(); //'red'
sayColor.call(this); //'red'
sayColor.call(window); // 'red'
sayColor.call(o); //'blue'

sayColor.call(this); sayColor.call(window) 两种显示地在全局作用域中调用函数的方式,结果当然显示‘red’;
但是,当运行sayColor.call(o)时,函数 的执行环境就不一样的,因此此时函数体内的this对象指向了o,于是结果就显示‘blue’

bind()这个方法会创建一个函数的实例,其this值会被绑定到传给bind()函数的值;比如

window.color = 'red';
var o = {
    color: 'blue'
};
function sayColor() {
    alert(this.color);
}
var objectSayColor = sayColor.bind(o);
objectSayColor() ;//'blue'

猜你喜欢

转载自blog.csdn.net/weixin_42575028/article/details/88719840
5.5