<JS>手動(dòng)實(shí)現(xiàn)call, apply, bind

先分析下3個(gè)方法的作用

  • 改變this的指向殴胧。
  • 傳入?yún)?shù)牙躺。
  • call apply返回函數(shù)結(jié)果, bind 返回新函數(shù)

我們先從 call 開(kāi)始

改變this的指向

首先我們知道扬霜,對(duì)象上的方法,在調(diào)用時(shí)啥寇,this是指向?qū)ο蟮摹?/p>

let o = {
    fn:function(){
        console.log(this);
    }
}
o.fn() //  Object {fn: function}  

知道了這點(diǎn),我們就可以實(shí)現(xiàn)改變this指向了

// 函數(shù)原型上添加 myCall方法 來(lái)模擬call
Function.prototype.myCall = function(obj){
    //我們要讓傳入的obj成為, 函數(shù)調(diào)用時(shí)的this值.
    obj._fn_ = this;  //在obj上添加_fn_屬性,值是this(要調(diào)用此方法的那個(gè)函數(shù)對(duì)象)辑甜。
    obj._fn_();       //在obj上調(diào)用函數(shù),那函數(shù)的this值就是obj.
    delete obj._fn_; // 再刪除obj的_fn_屬性,去除影響.
    //_fn_ 只是個(gè)屬性名 你可以隨意起名衰絮,但是要注意可能會(huì)覆蓋obj上本來(lái)就有的屬性
}

下面測(cè)試一下

let test = {
    name:'test'
}
let o = {
    name:'o',
    fn:function(){
        console.log(this.name);
    }
}
o.fn() // "o"
o.fn.call(test) // "test"
o.fn.myCall(test) // "test"

現(xiàn)在,改變this的值磷醋,實(shí)現(xiàn)了

傳入?yún)?shù)
  • 最簡(jiǎn)單實(shí)現(xiàn)猫牡,用ES6
// 只需要在原來(lái)的基礎(chǔ)上 用下拓展運(yùn)算符 剩余運(yùn)算符即可
Function.prototype.myCall = function(obj,...arg){
    obj._fn_ = this;
    obj._fn_(...arg);
    delete obj._fn_;
}
//測(cè)試
let test = {
    name:'test'
}
let o = {
    name:'o',
    fn:function(){
        console.log(this.name, ...arguments);  //這里把參數(shù)顯示一下
    }
}
o.fn(1,2,3) // "o" 1 2 3
o.fn.call(test,1,2,3) // "test" 1 2 3
o.fn.myCall(test,1,2,3) // "test" 1 2 3
// 沒(méi)問(wèn)題

不用ES6就比較麻煩了

  • 用eval 方法
    eval方法,會(huì)對(duì)傳入的字符串邓线,當(dāng)做JS代碼進(jìn)行解析淌友,執(zhí)行。
Function.prototype.myCall = function(obj){
    let arg = [];
    for(let i = 1 ; i<arguments.length ; i++){
        arg.push( 'arguments[' + i + ']' ) ;
        // 這里要push 這行字符串  而不是直接push 值
        // 因?yàn)橹苯觩ush值會(huì)導(dǎo)致一些問(wèn)題
        // 例如: push一個(gè)數(shù)組 [1,2,3]
        // 在下面?? eval調(diào)用時(shí),進(jìn)行字符串拼接,JS為了將數(shù)組轉(zhuǎn)換為字符串 骇陈,
        // 會(huì)去調(diào)用數(shù)組的toString()方法,變?yōu)?'1,2,3' 就不是一個(gè)數(shù)組了震庭,相當(dāng)于是3個(gè)參數(shù).
        // 而push這行字符串,eval方法你雌,運(yùn)行代碼會(huì)自動(dòng)去arguments里獲取值
    }
    obj._fn_ = this;
    eval( 'obj._fn_(' + arg + ')' ) // 字符串拼接器联,JS會(huì)調(diào)用arg數(shù)組的toString()方法,這樣就傳入了所有參數(shù)
    delete obj._fn_;
}
//測(cè)試
let test = {
    name:'test'
}
let o = {
    name:'o',
    fn:function(){
        console.log(this.name, ...arguments);  //這里把參數(shù)顯示一下
    }
}
o.fn(1,['a','b'],3) // "o" 1 ["a","b"] 3
o.fn.call(test,1,['a','b'],3) // "test" 1 ["a","b"] 3
o.fn.myCall(test,1,['a','b'],3) // "test" 1 ["a","b"] 3
// 沒(méi)問(wèn)題
返回函數(shù)值

很簡(jiǎn)單,變量保存一下

Function.prototype.myCall = function(obj,...arg){
    let val ;
    obj._fn_ = this;
    val = obj._fn_(...arg);  //不能直接return obj._fn_(...arg) 這樣就不delete屬性了
    delete obj._fn_;
    return val;
}
Function.prototype.myCall = function(obj){
    let arg = [];
    let val ;
    for(let i = 1 ; i<arguments.length ; i++){ // 從1開(kāi)始
        arg.push( 'arguments[' + i + ']' ) ;
    }
    obj._fn_ = this;
    val = eval( 'obj._fn_(' + arg + ')' ) // 字符串拼接婿崭,JS會(huì)調(diào)用arg數(shù)組的toString()方法主籍,這樣就傳入了所有參數(shù)
    delete obj._fn_;
    return val;
}

傳參檢測(cè)

傳入的obj如果是null, undefined 應(yīng)該改為window。如果是string逛球,number千元,boolean應(yīng)該轉(zhuǎn)換為對(duì)象。

  • 可以自己加入一下判斷颤绕,為了方便觀看幸海,我就先不加了。
if(obj === null || obj === undefined){
    obj = window;
} else {
    obj = Object(obj);
}

實(shí)現(xiàn)apply

其實(shí)apply和call差不多奥务,沒(méi)什么大區(qū)別

  • 利用已經(jīng)寫好的myCall來(lái)實(shí)現(xiàn)
// ES6
Function.prototype.myApply = function(obj,arr){
    let args = [];
    for(let i = 0 ; i<arr.length; i++){
        args.push( arr[i] );
    }
    // 其實(shí)直接 ...arr 傳參也可以 但是效果就和aplly有微小差別了
    return this.myCall(obj, ...args);
}
// ES3
Function.prototype.myApply = function(obj,arr){
    let args = [];
    for(let i = 0 ; i<arr.length; i++){
        args.push( 'arr[' + i + ']' );  // 這里也是push 字符串
    }
    return eval( 'this.myCall(obj,' + args + ')' );
}
  • 不用myCall
Function.prototype.myApply = function(obj,arr){
    let args = [];
    let val ;
    for(let i = 0 ; i<arr.length ; i++){
        args.push( 'arr[' + i + ']' ) ;
    }
    obj._fn_ = this;
    val = eval( 'obj._fn_(' + args + ')' ) 
    delete obj._fn_;
    return val
}
  • 測(cè)試
Array.apply({},{length:3});
// 返回 [undefined, undefined, undefined]
Array.myApply({},{length:3});
// 返回 [undefined, undefined, undefined]

效果沒(méi)區(qū)別


實(shí)現(xiàn)bind

  • ES6 + 寫好的myApple
Function.prototype.myBind = function(obj,...arg1){   //arg1收集剩余參數(shù)
    return (...arg2) => {   //返回箭頭函數(shù), this綁定調(diào)用這個(gè)方法(myFind)的函數(shù)對(duì)象
        return this.myApply( obj, arg1.concat(arg2) );   // 將參數(shù)合并
    }
}
  • ES6
// 其實(shí)沒(méi)什么說(shuō)的
Function.prototype.myBind = function(obj,...arg1){
    return (...arg2) => { 
        let args = arg1.concat(arg2);
        let val ;
        obj._fn_ = this;
        val = obj._fn_( ...args ); 
        delete obj._fn_;
        return val
    }
}
  • 不用ES6 , 不用myApple
Function.prototype.myBind = function(obj){
    let _this = this;
    let argArr = [];
    let arg1 = [];
    for(let i = 1 ; i<arguments.length ; i++){ // 從1開(kāi)始 
        arg1.push( arguments[i] ); // 這里用arg1數(shù)組收集下參數(shù)
        // 獲取arguments是從1開(kāi)始, 但arg1要從 0(i-1)開(kāi)始
        // 若是用Array.prototype.slice.call(argument)就方便多了
        argArr.push( 'arg1[' + (i - 1)  + ']' ) ; // 如果用arguments在返回的函數(shù)里運(yùn)行 會(huì)獲取不到這個(gè)函數(shù)里的參數(shù)了
    }
    return function(){
        let val ;
        for(let i = 0 ; i<arguments.length ; i++){ // 從0開(kāi)始
            argArr.push( 'arguments[' + i + ']' ) ;
        }
        obj._fn_ = _this;
        val = eval( 'obj._fn_(' + argArr + ')' ) ;
        delete obj._fn_;
        return val
    };
}

測(cè)試下

let test = {
    name:'test'
}
let o = {
    name:'o',
    fn:function(){
        console.log(this.name, ...arguments);  //這里把參數(shù)顯示一下
    }
}
//myBind
b = o.fn.myBind(test,1,2)
b() // "test" 1 2
b(3,4) // "test" 1 2 3 4
// bind
b = o.fn.bind(test,1,2)
b() // "test" 1 2
b(3,4) // "test" 1 2 3 4

三個(gè)方法的我寫的代碼

  • 模擬call
Function.prototype.myCall = function(obj){
    if(obj === null || obj === undefined){
        obj = window;
    } else {
        obj = Object(obj);
    }
    let arg = [];
    let val ;
    for(let i = 1 ; i<arguments.length ; i++){
        arg.push( 'arguments[' + i + ']' ) ;
    }
    obj._fn_ = this;
    val = eval( 'obj._fn_(' + arg + ')' ) 
    delete obj._fn_;
    return val
}
  • 模擬apply
Function.prototype.myApply = function(obj,arr){
    if(obj === null || obj === undefined){
        obj = window;
    } else {
        obj = Object(obj);
    }
    let args = [];
    let val ;
    for(let i = 0 ; i<arr.length ; i++){
        args.push( 'arr[' + i + ']' ) ;
    }
    obj._fn_ = this;
    val = eval( 'obj._fn_(' + args + ')' ) 
    delete obj._fn_;
    return val
}
  • 模擬bind
Function.prototype.myFind = function(obj){
    if(obj === null || obj === undefined){
        obj = window;
    } else {
        obj = Object(obj);
    }
    let _this = this;
    let argArr = [];
    let arg1 = [];
    for(let i = 1 ; i<arguments.length ; i++){  
        arg1.push( arguments[i] );
        argArr.push( 'arg1[' + (i - 1)  + ']' ) ;
    }
    return function(){
        let val ;
        for(let i = 0 ; i<arguments.length ; i++){
            argArr.push( 'arguments[' + i + ']' ) ;
        }
        obj._fn_ = _this;
        console.log(argArr);
        val = eval( 'obj._fn_(' + argArr + ')' ) ;
        delete obj._fn_;
        return val
    };
}

謝謝閱讀物独,有任何問(wèn)題請(qǐng)指出。歡迎一起討論氯葬。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末挡篓,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子帚称,更是在濱河造成了極大的恐慌官研,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闯睹,死亡現(xiàn)場(chǎng)離奇詭異戏羽,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)楼吃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門始花,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)妄讯,“玉大人,你說(shuō)我怎么就攤上這事酷宵『ッ常” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵浇垦,是天一觀的道長(zhǎng)砌函。 經(jīng)常有香客問(wèn)我,道長(zhǎng)溜族,這世上最難降的妖魔是什么讹俊? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮煌抒,結(jié)果婚禮上仍劈,老公的妹妹穿的比我還像新娘。我一直安慰自己寡壮,他們只是感情好贩疙,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著况既,像睡著了一般这溅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上棒仍,一...
    開(kāi)封第一講書(shū)人閱讀 52,394評(píng)論 1 310
  • 那天悲靴,我揣著相機(jī)與錄音,去河邊找鬼莫其。 笑死癞尚,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的乱陡。 我是一名探鬼主播浇揩,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼憨颠!你這毒婦竟也來(lái)了胳徽?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤爽彤,失蹤者是張志新(化名)和其女友劉穎养盗,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體淫茵,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡爪瓜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了匙瘪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铆铆。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖丹喻,靈堂內(nèi)的尸體忽然破棺而出薄货,到底是詐尸還是另有隱情,我是刑警寧澤碍论,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布谅猾,位于F島的核電站,受9級(jí)特大地震影響鳍悠,放射性物質(zhì)發(fā)生泄漏税娜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一藏研、第九天 我趴在偏房一處隱蔽的房頂上張望敬矩。 院中可真熱鬧,春花似錦蠢挡、人聲如沸弧岳。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)禽炬。三九已至,卻和暖如春勤家,著一層夾襖步出監(jiān)牢的瞬間腹尖,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工伐脖, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留桐臊,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓晓殊,卻偏偏與公主長(zhǎng)得像断凶,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子巫俺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容