你还傻傻的分不清javascript中的caller和callee吗?

3,708 阅读2分钟

前言

javascript中的caller和callee一眼望过感觉是一样的,然而一个字母的区别在应用中却天差地别。字面意思caller直接翻译为调用者,callee翻译为被召者。那么这两个到底是什么?怎么用?是我们本文的重点。

1. callee

在了解callee之前我们先了解函数内部arguments这个特殊的属性。arguments是一个类数组对象,它包含着所有传入函数中的参数。这也让我们明白了 arguments的主要用途就是保存函数参数。这个对象有一个名叫做callee的属性,该属性是一个指针,指向拥有这个arguments对象的函数

function testArguments(num,type){
    console.log(arguments);
    console.log(arguments[0],arguments[1]);
}
testArguments(10,'前端callee')

从打印结果我们可以看出arguments中保存我们调用函数传入的形参,arguments中也有一个callee的属性,打印argument.callee结果就是testArgument本尊。 callee一个经典的使用案例就是阶乘函数。

function factorial(num){
    if(num === 1){
        return 1;
    }else{
        return num * factorial(num - 1);
    }
}
console.log(factorial(5));
//通常如果能保证函数名不变化,我们可以这下写,函数内部和函数通过factorial耦合在意一起,一旦函数名重新定义这个函数返回结果将不再是我们想要的结果。
var testFactorial = factorial;
console.log(testFactorial(5)); //120
factorial = function(){
    return 0
};
console.log(testFactorial(5));//0
console.log(factorial(5));//0

为了消除这种耦合我们可以使用 arugments.callee这时候我们的函数可以变成如下实例:

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

重写后的函数体中没有函数名,无论引用的函数使用任何名称时,都能保证递归的正常调用。

2.caller

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

function outer() {
    inner();
}
function inner() {
    console.log(inner.caller)
}
outer();
// 打印结果结果就是outer函数本尊,为了实现更松散耦合,我们可以将inner.caller改为arguments.callee.caller.

不难看出caller可以认为那个函数调用了它,它就指向谁,如果是函数是通过window调用,这值为null。此时我们再看看图一中的这个属性。

注意: 当函数在严格模式下运行时 ,访向arguments.callee 会导致错误。 ECMAScript5还定义了arguments.caller属性,但在严格模式下访问它也会导致错误,而在非严格模式下这个属性始终是undefined,定义arguments.callee属性是为了分清arguments.caller和函数的caller属性。严格模式还有一个限制: 不能为函数的caller属性赋值,否则会导致错误。