JavaScript中的call()、apply()兴泥、bind()用法以及原理實現(xiàn)(面試篇)

面試題熱點話題工育,this指向、如何改變this指向搓彻、call() apply() bind()區(qū)別如绸,還有手寫call() apply()以及bind()

一旭贬、 this指向問題怔接,這個問題主要是分一些情況來去理解的,整體而言可以理解成this的指向在函數(shù)定義的時候是確定不了的稀轨,只有函數(shù)執(zhí)行的時候才能確定this到底指向誰扼脐,實際上this的最終指向的是那個調(diào)用它的對象,不過具體的特殊情況還需要特殊對待奋刽。更多關(guān)于this指向問題可以參考文章 《徹底理解js中this的指向》瓦侮。

二、 call() apply()bind()的區(qū)別

相同點:

都可以改變函數(shù)的this指向 第一個參數(shù)都是this要指向的對象佣谐。

不同點:

call()apply()接收參數(shù)不同:
call()接收多個參數(shù)肚吏,第一個參數(shù)為this要指向的對象,第二個參數(shù)為調(diào)用調(diào)用call方法的函數(shù)的第一個參數(shù)狭魂,第三個參數(shù)為該函數(shù)的第二個參數(shù)罚攀,依次類推。
apply()接收兩個參數(shù)雌澄,第一個參數(shù)為this要指向的對象斋泄,第二個參數(shù)為一個數(shù)組,數(shù)組的第一項為調(diào)用 apply方法的第一個參數(shù)镐牺,數(shù)組第二項為該函數(shù)的第二個參數(shù)炫掐,依次類推。
bind()第一個參數(shù)是this要指向的對象睬涧,第二個參數(shù)為調(diào)用調(diào)用bind方法的函數(shù)的第一個參數(shù)卒废,第三個參數(shù)為該函數(shù)的第二個參數(shù),依次類推宙地。

call() apply()bind()執(zhí)行時機不同:
call() apply()在被函數(shù)調(diào)用后立即執(zhí)行摔认,而bind()方法則是返回一個新的函數(shù)(未執(zhí)行)。

三宅粥、 call()参袱、apply()bind()語法

call()語法:

var obj = {
    name:"lucy"
}
function FunCall(a,b,c){
    console.log(this.name); //lucy 
    console.log("參數(shù)",a,b,c) // a b c
}
FunCall.call(obj,'a','b','c')

apply()語法:

var obj = {
    name:"lucy"
}
function FunApply(a,b,c){
    console.log(this.name); //lucy 
    console.log("參數(shù)",a,b,c) // a b c
}
Funapply.Apply(obj,["a","b","c"])

bind()語法:

var obj = {
    name:"lucy"
}
function FunBind(a,b,c){
    console.log(this.name); //lucy
    console.log(a,b,c); // a b c
}

var funbind = FunBind.bind(obj,'a','b','c')

funbind();

注意:bind()方法還可以這樣傳參

var obj = {
    name:"lucy"
}
function FunBind(a,b,c){
    console.log(this.name); //lucy
    console.log(a,b,c); // a b c
}

var funbind = FunBind.bind(obj)

funbind('a','b','c'); 

bind()除了this還接收其他參數(shù),bind()返回的函數(shù)也接收參數(shù)抹蚀。

以上是對call() ,apply(),bind()的異同以及語法使用剿牺,需要了解更多call() ,apply(),bind()的信息,可以參考 MDN 對這幾個方法更詳細的的說明环壤。

四晒来、 手寫call()apply()郑现、bind()方法湃崩。

  1. 手寫call():
    在寫之前思考一下,call()方法實際上做的事情就是改變了this的指向接箫,那如何改變一個函數(shù)的this指向呢攒读?就是改變函數(shù)的調(diào)用者
    按照這個思路來看下面代碼辛友。
Function.prototype.mycall = function (thisArg,...args){
//先明白我們的參數(shù) 都帶代表什么薄扁,thisArg代表this當前需要指向的對象,args表示該函數(shù)接收的參數(shù)废累。
//我們參照call 方法理解 foo.call(obj,"a","b") , 此時thisArg就代表obj,...args 就代表參數(shù)啊 a,b
    var fn = Symbol("fn"); 
//創(chuàng)建一個變量邓梅,使用Symbol是因為保證它的唯一性,不和thisArg其他相同fn屬性沖突
    var thisArg = thisArg || window; 
// 當thisArg不存在時也就是調(diào)用call()沒傳第一個參數(shù)時邑滨,默認為window震放。
    thisArg[fn] = this; 
//這里的這個this代表的是調(diào)用call方法的函數(shù),比如foo.call() this代表foo函數(shù)驼修,通過賦值操作,此時對象thisArg下面的fn屬性就是一個方法
    var result = thisArg[fn](...args);
//這一步是核心诈铛,調(diào)用對象thisArg下的fn方法乙各,在這一步函數(shù)foo的this已改變?yōu)閠hisArg
    delete thisArg[fn];
//當函數(shù)調(diào)用完成之后銷毀掉
    return  result;
}

var obj = {
    name:"lucy"
}

function foo(a,b){
    console.log(this.name); //lucy
    console.log(a,b); //a b
}

foo.mycall(obj,'a','b');

如果上述邏輯不是很清楚,就先去看一下《徹底理解js中this的指向》幢竹。再看看this指向再回來思考這段代碼耳峦。

  1. 手寫apply():
    apply()call()其實很相似,按照上面代碼的注釋理解一下下面的代碼
Function.prototype.myapply = function(thisArgs,args){
    var fn = Symbol("fn");
    var thisArgs = thisArgs || window;
    thisArgs[fn] = this;
    var result = thisArgs[fn](...args);
    delete thisArgs[fn];
    return result;
}

var obj = {
    name:"zhangsan"
}

function foo (a,b,c){
    console.log(this.name); //zhangsan
    console.log(a,b,c);// a b c
}
foo.myapply(obj,['a','b','c']);
  1. 手寫bind()
    bind()需要思考到的地方比較多焕毫,先一點點來蹲坷。
    第一步借助實現(xiàn)bind()基本功能改變this指向。
Function.prototype.mybind = function(thisArgs){
    var args = Array.prototype.slice.call(arguments,1);
    var _this = this;
    return function(){
        _this.apply(thisArgs,[...args]) //this指向被改變到thisArgs
    }
}
var obj = {
    name:"lili"
}
function foo(a,b,c){
    console.log(this.name); // lili
    console.log(a,b,c); // a b c
}

var ffff = foo.mybind(obj,'a',"b","c")
ffff();

由代碼可知bind()的改變this功能實現(xiàn)了

這里會有個問題
看下面代碼

Function.prototype.mybind = function(thisArgs){
    var args = Array.prototype.slice.call(arguments,1);
    var _this = this;
    return function(){
        _this.apply(thisArgs,[...args]) //this指向被改變到thisArgs
    }
}
var obj = {
    name:"lili"
}
function foo(a,b,c){
    console.log(this.name); // lili
    console.log(a,b,c); //  undefined undefined undefined
}
var ffff = foo.mybind(obj)
ffff("a","b","c");//當把參數(shù)放到這里時會發(fā)生什么邑飒?
//看上面輸出循签,參數(shù)沒傳遞進去,但是人家原生bind是可以在綁定this之后再傳參的,不信可以試試疙咸。

接著來完善

Function.prototype.mybind = function(thisArgs){
    var args = Array.prototype.slice.call(arguments,1);
    var _this = this;
    return function(){
        _this.apply(thisArgs,[...args,...arguments]) //這里與上面代碼不同的地方县匠,這里獲取到當前函數(shù)的參數(shù),并一并傳遞給函數(shù)執(zhí)行。
    }
}
var obj = {
    name:"lili"
}
function foo(a,b,c){
    console.log(this.name); // lili
    console.log(a,b,c); //  a b c
}
var ffff = foo.mybind(obj)
ffff("a","b","c");//此時在這里傳參

再接著完善:
我們知道對一個函數(shù)使用new操作符乞旦,會生成一個對象贼穆,該對象稱為當前函數(shù)的實例。該函數(shù)可以稱為構(gòu)造函數(shù)兰粉,構(gòu)造函數(shù)里的this指向當前的實例化對象故痊。

function Person(name){
  this.name = "sss"
}
var person = new Person()
console.log(person) // { name:"sss" }

如果 ffff函數(shù)被當做構(gòu)造函數(shù)使用new操作符產(chǎn)生一個實例化對象,按照道理來說this應(yīng)該指向?qū)嵗瘜ο缶凉茫俏覀兪褂?code>bind()方法函數(shù)foo的this指向了對象obj,因此我們需要處理一下當ffff函數(shù)被使用new操作符生成實例化對象時愕秫,改變this指向,讓它指向當前實例化對象而非obj客峭。

Function.prototype.mybind = function(thisArgs){
    if(typeof this !== "function"){
        return new Error('not a function') 
    }
    var args = Array.prototype.slice.call(arguments,1);
    var _this = this;

    var result = function (){ //稍微做了改變將函數(shù)賦值給result變量
        _this.apply(thisArgs,[...args,...arguments])
    }

    return result;
}

var obj = {
    name:"lucy"
}

function foo(name){
    this.name = name;
}

var ffff= foo.mybind(obj);
var instance = new ffff("jack"); //實例化被bind()方法綁定this之后返回的函數(shù)

console.log(obj) //{name:"jack"}  //理想輸出應(yīng)該是 {name:"lucy"}
console.log(instance); // {}  //理想輸出應(yīng)該是 { name:"jack" }

此時可以使用原生bind()方法觀察比較

var obj = {
    name:"lucy"
}

function foo(name){
    this.name = name;
}

var ffff= foo.bind(obj); //用原生bind()方法
var instance = new ffff("jack"); //實例化被bind()方法綁定this之后返回的函數(shù)

console.log(obj) //{ name:"lucy" } 
console.log(instance); // { name:"jack" } 

這下很直觀的能觀察到我們的mybind方法有問題豫领,問題就在當ffff函數(shù)被使用new操作符生成實例化對象時,沒有改變this指向到實例化對象舔琅,而還是指向mybind()的第一個參數(shù)obj等恐。

如何改變this呢?看一下代碼

Function.prototype.mybind = function(thisArgs){
    if(typeof this !== "function"){
        return new Error('not a function') 
    }
    var args = Array.prototype.slice.call(arguments,1);
    var _this = this;

    var result = function (){
        var thisArgs = this instanceof result ? this : thisArgs;
        /*
        在這里對即將傳入apply的第一個參數(shù)進行判斷,
        判斷當前this是否是result函數(shù)的實例备蚓,
        如果是那就將當前的this傳遞進去课蔬,
        如果不是那就還是使用傳遞進來的第一個參數(shù)。
        */
        _this.apply(thisArgs,[...args,...arguments])
    }
    return result;
}
var obj = {
    name:"lucy"
}
function foo(name){
    this.name = name;
}
foo.prototype.age = 18;  // 第26行
var ffff = foo.mybind(obj);
var instance = new ffff("jack");

console.log(obj) //{name:"lucy"}
console.log(instance); // { name:"jack" }

console.log(instance.age);// undefined(先不用看這個) // 第33行

我們在第9行加了一個判斷郊尝,判斷這里的this是不是指向當前函數(shù)的實例二跋,如果是,那么我們就需要改變傳入apply的第一個參數(shù)了流昏。

最終實現(xiàn):
看上面代碼26行我們給foo函數(shù)原型對象上寫入了一個屬性age,理論上我們通過instance.age就能訪問到扎即,但是通過第33行代碼我們發(fā)現(xiàn),instance的原型鏈上沒有age屬性况凉。
再看一下代碼:

Function.prototype.mybind = function(thisArgs){
    if(typeof this !== "function"){
        return new Error('not a function') 
    }
    var args = Array.prototype.slice.call(arguments,1);
    var _this = this;

    var result = function (){
        console.log(this);//result{}
        var alignThis = _this instanceof result ? _this : thisArgs;
        _this.apply(alignThis,[...args,...arguments])
    }

    //將調(diào)用mybind方法的函數(shù)的原型對象利用原型繼承的方式將該函數(shù)的原型繼承到result的原型對象上谚鄙。
    var FUN = function(){} //第15行
    FUN.prototype = _this.prototype;
    result.prototype = new FUN() //第17行

    return result;
}


var obj = {
    name:"lucy"
}

function foo(name){
    this.name = name;
}
foo.prototype.age = 18;
var ffff = foo.mybind(obj);
var instance = new ffff("jack");

console.log(obj)//{name:"lucy"}
console.log(instance);//{name:"jack"}
console.log(instance.age); // 18

通過15-17行,完成原型對象的繼承刁绒,這樣就完成了手寫bind()闷营。
到此為止,call() apply() bind()方法的手寫實現(xiàn)也完成了知市。


寫的有些啰嗦傻盟,但是邊寫邊怕自己寫不清楚,又怕寫錯了嫂丙,只能慢慢一步步來娘赴,希望看到此文的伙伴有疑惑或者發(fā)現(xiàn)錯誤請不吝賜教。謝謝跟啤。

寫在最后:文中內(nèi)容大多為自己平時從各種途徑學(xué)習(xí)總結(jié)筝闹,文中參考文章大多收錄在我的個人博客里媳叨,歡迎閱覽http://www.tianleilei.cn

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市关顷,隨后出現(xiàn)的幾起案子糊秆,更是在濱河造成了極大的恐慌,老刑警劉巖议双,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件痘番,死亡現(xiàn)場離奇詭異,居然都是意外死亡平痰,警方通過查閱死者的電腦和手機汞舱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宗雇,“玉大人昂芜,你說我怎么就攤上這事∨馄眩” “怎么了泌神?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長舞虱。 經(jīng)常有香客問我欢际,道長,這世上最難降的妖魔是什么矾兜? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任损趋,我火速辦了婚禮,結(jié)果婚禮上椅寺,老公的妹妹穿的比我還像新娘浑槽。我一直安慰自己,他們只是感情好返帕,可當我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布桐玻。 她就那樣靜靜地躺著,像睡著了一般溉旋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上嫉髓,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天观腊,我揣著相機與錄音,去河邊找鬼算行。 笑死梧油,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的州邢。 我是一名探鬼主播儡陨,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼褪子,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了骗村?” 一聲冷哼從身側(cè)響起嫌褪,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎胚股,沒想到半個月后笼痛,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡滓鸠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年详瑞,在試婚紗的時候發(fā)現(xiàn)自己被綠了皱卓。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡刻坊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出党晋,到底是詐尸還是另有隱情谭胚,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布隶校,位于F島的核電站漏益,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏深胳。R本人自食惡果不足惜绰疤,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望舞终。 院中可真熱鬧轻庆,春花似錦、人聲如沸敛劝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽夸盟。三九已至蛾方,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間上陕,已是汗流浹背桩砰。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留释簿,地道東北人亚隅。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像庶溶,于是被迫代替她去往敵國和親煮纵。 傳聞我的和親對象是個殘疾皇子懂鸵,可洞房花燭夜當晚...
    茶點故事閱讀 44,901評論 2 355