首頁 > 軟體

詳解JavaScript中的this硬繫結

2022-10-10 14:00:52

一、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)/a.apply(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硬繫結 內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com