函数的内置属性

在函数中有一些可使用的内置属性,通过这些属性可以获得与函数相关的一部分信息。下面将分别介绍这些内置属性。

arguments

通过 arguments 属性获取函数在调用时的参数值,这个参数可以用数组的形式访问。例如,arguments.length 代表传入参数的个数,arguments[0] 代表第 1 个参数,arguments[1] 代表第 2 个参数。

使用 arguments 的示例代码如下,为了表现出对不同参数的影响,test() 函数中分别定义了 4 个参数——一个必选参数,一个默认参数,一个可选参数和一个剩余参数。

function test(num1: number, num2: number = 0, num3?: number, ...restParas: string[]) {
    console.log("共传入了多少个参数:" + arguments.length);
    for (let i = 0; i < arguments.length; i++) {
        console.log(`第${i + 1}个参数值为${arguments[i]}`);
    }
}

test(1);
test(1, 2);
test(1, 2, 3);
test(1, 2, 3, "a", "b", "c");

输出结果如下,可以看到 arguments 的参数数量只与实际传入多少个参数值有关,与函数定义了多少个参数无关。

> 共传入了多少个参数:1
> 第1个参数值为1
> 共传入了多少个参数:2
> 第1个参数值为1
> 第2个参数值为2
> 共传入了多少个参数:3
> 第1个参数值为1
> 第2个参数值为2
> 第3个参数值为3
> 共传入了多少个参数:6
> 第1个参数值为1
> 第2个参数值为2
> 第3个参数值为3
> 第4个参数值为a
> 第5个参数值为b
> 第6个参数值为c

在实际编程中并不推荐使用 arguments 属性,因为这种方式的可读性和可维护性较差,只有在极其特殊的情况下才会使用。

caller

callerECMAScript 5 新增的一个属性,调用方式为 “函数名称.caller”,该属性引用可调用当前函数的函数。如果在全局作用域中调用该函数,则会返回 null。示例代码如下。

function test()
{
    console.log(test.caller);
}

function outerFunc()
{
    test();
}

test();       //由于在全局作用域中调用,没有上层函数,因此输出null
outerFunc();  //输出"ƒ outerFunc() { test(); }"

由于通过这种方式调用 caller 属性需要有函数名称,但部分函数是使用表达式形式声明的,因此还可以使用 arguments.callee.caller 属性来获取调用当前函数的函数,示例代码如下。

let test = function () {
    console.log(arguments.callee.caller);
}

let outerFunc = function () {
    test();
}

test();       //由于在全局作用域中调用,没有上层函数,因此输出null
outerFunc();  //输出"ƒ () { test(); }"

this

this 是函数在运行时,在函数体内部自动生成的一个对象,它只能在函数体内部使用。this 并不是变量,它属于关键词。this 的值通常是调用该函数的上下文对象,是该函数的 “拥有者”。

在不同的场合下,this 的值各不相同,下面将详细介绍。

  1. 直接调用函数时的 this

    当函数直接被调用时,属于全局性调用,某个全局性对象拥有并调用了这个函数,因此 this 将指向这个全局性对象 globalThis(后面会详细介绍)。

    基于不同的运行环境,全局对象 globalThis 会有所区别。示例代码如下。

    function test() {
        console.log(`${this.name}`);
        console.log(`${this}`);
    }
    
    globalThis.name = "Alina";
    test();

    浏览器运行环境中的全局对象是 window,因此 globalThis 对象将指向 window 对象,输出结果如下。

    > Alina
    > [object Window]

    可以看到在函数体中通过 this 成功读取全局对象及其属性。

    Node.js 运行环境中的全局对象是 global,因此 globalThis 对象将指向 global 对象,输出结果如下。

    > Alina
    > [object global]
  2. 以对象方法的形式调用函数时的this

    当函数作为某个对象的方法时,该对象将称为函数的拥有者,通过该对象调用函数时,this 将指向这个对象。

    以下代码声明一个名为 person 的对象,该对象拥有一个名为 name 的字符串类型的属性和一个名为 selfIntroduction () => void 类型的方法,声明之后分别为 name 属性和 selfIntroduction() 方法赋值,selfIntroduction() 方法指向 introduction() 方法。

    function introduction() {
        console.log(`Hello ${this.name}`);
    }
    
    let person: { name: string, selfIntroduction: () => void } = { name: "",
    selfIntroduction: null };
    person.name = "Rick";
    person.selfIntroduction = introduction;
    
    //以下代码输出"Hello undefined",因为当前全局对象中还没有定义name属性
    introduction();
    //以下代码输出"Hello Rick"
    person.selfIntroduction();

    可以看到,调用者不同,this 对象也不同。当在全局作用域中调用 introduction() 方法时,this 指向 globalThis 对象(浏览器运行环境中 globalThis 对象指向 window 对象,Node.js 运行环境中 globalThis 对象指向 global 对象),通过 person 对象调用 introduction() 方法时,this 指向调用者 person 对象本身,因此 this.name 将使用 person 对象的 name 属性,输出 "Hello Rick"

  3. 箭头函数中的 this

    箭头函数中的 this 是一个特例,在箭头函数中,this 引用的是声明箭头函数时的上下文对象,而非实际调用箭头函数时的上下文对象。

    如果将上一个示例中的 selfIntroduction() 方法改为箭头函数,结果将变得不同。修改后的代码及运行结果如下。

    let person: { name: string, selfIntroduction: () => void } = { name: "", selfIntroduction: null };
    person.name = "Rick";
    person.selfIntroduction = () => { console.log(`Hello ${this.name}`); };
    
    //以下代码输出"Hello undefined",因为当前全局对象中没有定义name属性
    person.selfIntroduction();
  4. 约束或禁用 this

    当函数的拥有者不是全局对象时,你可以限定函数中 this 的类型。具体方式是在声明函数时,把首个参数声明为 “this:类型描述”,如果还有其他参数,则必须放到 this 参数后面。例如,在以下代码中,sum() 函数的声明中包含 this 参数,其类型为 {num1:number}sum() 函数的拥有者是 sumCalculator 对象,因此 this 指向 sumCalculator 对象,这就要求 sumCalculator 对象至少拥有一个 num1 属性。

    function sum(this: { num1: number }, num2) {
        return this.num1 + num2;
    }
    
    let sumCalculator = { num1: 1, selfIntroduction: sum };
    let result = sumCalculator.selfIntroduction(2); //result值为3

    如果 sumCalculator 对象不具有 num1 属性,则会引起编译错误,示例代码如下。

    let num = { selfIntroduction: sum };
    //类型为"{ selfIntroduction: (this: { num1: number; }, num2: any) => any; }"
    //的 "this" 上下文不能分配给类型为"{ num1: number; }"的方法的 "this"
    let result = num.selfIntroduction(2); //result值为3

    函数也可以禁用 this,只需要将函数的首个参数声明为 “this:void” 即可,这样在函数体中就不允许使用 this,否则会引起编译错误。示例代码如下。

    function sum(this: void, num2) {
        //编译错误:类型"void"上不存在属性"num1"。ts(2339)
        return this.num1 + num2;
    }