详解JavaScript中的this硬绑定

作者:代码拾荒时间:2026-10-25 21:18:16浏览:535

本文深入解析JavaScript中this的硬绑定机制。通过对比隐式绑定与显示绑定,阐述this绑定丢失问题,并详细介绍call、apply及ES5的bind方法。重点讲解硬绑定如何解决回调函数中的this指向问题,以及各方法在参数传递和返回值处理上的差异,帮助开发者掌握this绑定的核心技巧。

一、this显示绑定

所谓的this显示绑定,是指其机制不同于this的隐式绑定。隐式绑定要求对象内部必须包含一个指向函数的属性,或者由某个对象/上下文包含一个函数调用位置,并通过该属性间接调用函数,从而将this隐式地绑定到该对象上。然而,隐式绑定存在绑定对象丢失的风险,示例代码如下:

function afun() {
    console.log(this.a);
}

var obj = {
    a: 1,
    afun: afun
};

var a = "hello";

setTimeout(obj.afun, 100);//"hello"

令人意外的是,控制台输出的结果是全局变量a的值,而非拥有该函数属性对象的a值。这种现象被称为隐式绑定的对象丢失,在回调函数中丢失绑定对象的情况十分常见。

虽然显示绑定本身无法彻底解决绑定对象丢失的问题,但它是硬绑定的基础,而硬绑定作为显示绑定的一种变体,能够有效解决这一问题。

在深入探讨硬绑定之前,我们先明确什么是显示绑定。

利用函数的call()和apply()方法可以实现this的显示绑定,绝大多数内置函数和自定义函数均支持这两种方法。它们都接收两个参数。

具体参数说明如下:

thisArg:指定要绑定到调用者this的对象。

arg1, arg2, ...(call):指定传递给调用者的参数列表,即作为实参传给调用者函数。例如a.call(obj, args),其中args作为实参传递给函数a。

argsArray(apply):一个数组或类数组对象。若该参数为null或undefined,则表示不传入任何额外参数。

返回值:若调用者函数有返回值,则返回该值;若无返回值,则返回undefined。

参考以下代码示例:

function bfun() {
    return this.a;
}

var obj1 = {
    a: "hello"
};

console.log(bfun.apply(obj1), bfun.call(obj1));//"hello" "hello"

若传入的第一个参数是原始值,结果会如何?参考以下代码:

function bfun() {
    return this;
}


console.log(bfun.apply(3));//[Number: 2]

确实如此,原始值会被转换为其对应的对象形式,即执行new Number()操作,这一过程被称为“装箱”。

前文已提及,this显示绑定虽然功能强大,但仍无法完全解决this绑定丢失的问题。接下来我们将介绍硬绑定,即显示绑定的一个变体,它能完美解决this绑定丢失的问题。

二、硬绑定

首先查看以下代码:

function cfun() {
    console.log(this.a)//"hello"
    return this.a;
}

var obj2 = {
    a: "hello"
};

var fn = function() {
    return cfun.apply(obj2);
};
console.log(fn());//"hello"

setTimeout(fn, 100);//"hello"

可以看出,硬绑定确实解决了this绑定丢失的问题。但需要注意的是,通过apply()绑定的this对象,无法进行二次更改绑定对象:

function f() {
console.log( this.a );
}
var obj = {
a:2
};
var b = function() {
f.call( obj );
};
b(); // 2

b.call( window ); // 2

硬绑定的一个典型应用场景是创建一个包裹函数,将所有参数传递给调用者函数,并返回接收到的所有值。

function dfun(v) {
    return (v[0] + this.a);
}

var obj3 = {
    a: 10
};

var fn1 = function() {
    return dfun.call(obj3, arguments);
}

var result = fn1(6);
console.log(result);//16

对于arguments参数而言,call()和apply()的区别在于参数类型不同:

function efun(v) {
    console.log(..v)//6 10 11
    return (v);
}

var obj4 = {
    a: 10
};

var fn1 = function() {
    return efun.apply(obj3, arguments);
}

var result = fn1([6, 10, 11]);
console.log(result);//[6, 10, 11]

也就是说,call()的参数类型是Object,传入的参数列表会被转换为键为'0'(随传入参数数量递增),值为传入参数的对象;而apply()的参数类型则是数组或类数组。

function dfun(v) {
    return (v);
}

var obj3 = {
    a: 10
};

var fn1 = function() {
    return dfun.call(obj3, arguments);
}

var result = fn1(6, 19, 1, 1);
console.log(result);//[Arguments]{'0':6,'1':19,2':1,3':1}
console.log(typeof result);//'object'

最强大的一种方法是将包裹函数创建为可重复使用的辅助函数,从而封装可重复使用的硬绑定。

function ffun(v) {
    return this.a * v;
}

var obj5 = {
    a: 5,
};

var fn2 = function(fn, obj) {
    return function() {
        return fn.apply(obj, arguments);
    }
}

var bind = fn2(ffun, obj5);
console.log(bind(10));//50

由于硬绑定使用频率极高,但通过包裹函数创建可重复使用的硬绑定较为繁琐,因此ES5提供了一个实现相同功能的方法bind()。用法如下:

function hfun(v) {
    return this.a * v;
}

var obj6 = {
    a: 6
};

var bind = hfun.bind(obj6);
console.log(bind(4));//24

可以看到,我们无需再构建一个包裹函数来手动调用call或apply方法,只需提供调用者和绑定到调用者this的对象即可。

我们可能发现了一个奇怪的现象:通过apply()和call()方法绑定对象并传参给调用者时,需要设置一个参数占位,但bind()方法则无需如此。这是因为它们的返回值不同:bind()方法会返回一个经过硬编码的新函数,该函数会将传入参数设置为this的上下文并调用原始函数。可以理解bind的使用方法为bind(obj)(args)。而对于call()和apply()方法而言,一旦调用此方法,就会立刻返回调用者函数的返回值,因此此时需要同时传入参数给方法的参数占用符,然后被函数参数读取。值得注意的是,bind方法的参数和call方法类似。

到此这篇关于详解JavaScript中的this硬绑定的文章就介绍到这了,更多相关JavaScript this硬绑定内容请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!

作者最新文章