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

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

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

我們先從 call 開始

改變this的指向

首先我們知道,對象上的方法,在調(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方法 來模擬call
Function.prototype.myCall = function(obj){
    //我們要讓傳入的obj成為, 函數(shù)調(diào)用時(shí)的this值.
    obj._fn_ = this;  //在obj上添加_fn_屬性,值是this(要調(diào)用此方法的那個(gè)函數(shù)對象)。
    obj._fn_();       //在obj上調(diào)用函數(shù),那函數(shù)的this值就是obj.
    delete obj._fn_; // 再刪除obj的_fn_屬性,去除影響.
    //_fn_ 只是個(gè)屬性名 你可以隨意起名,但是要注意可能會覆蓋obj上本來就有的屬性
}

下面測試一下

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ù)
  • 最簡單實(shí)現(xiàn),用ES6
// 只需要在原來的基礎(chǔ)上 用下拓展運(yùn)算符 剩余運(yùn)算符即可
Function.prototype.myCall = function(obj,...arg){
    obj._fn_ = this;
    obj._fn_(...arg);
    delete obj._fn_;
}
//測試
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
// 沒問題

不用ES6就比較麻煩了

  • 用eval 方法
    eval方法,會對傳入的字符串,當(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值會導(dǎo)致一些問題
        // 例如: push一個(gè)數(shù)組 [1,2,3]
        // 在下面?? eval調(diào)用時(shí),進(jìn)行字符串拼接,JS為了將數(shù)組轉(zhuǎn)換為字符串 ,
        // 會去調(diào)用數(shù)組的toString()方法,變?yōu)?'1,2,3' 就不是一個(gè)數(shù)組了,相當(dāng)于是3個(gè)參數(shù).
        // 而push這行字符串,eval方法,運(yùn)行代碼會自動去arguments里獲取值
    }
    obj._fn_ = this;
    eval( 'obj._fn_(' + arg + ')' ) // 字符串拼接,JS會調(diào)用arg數(shù)組的toString()方法,這樣就傳入了所有參數(shù)
    delete obj._fn_;
}
//測試
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
// 沒問題
返回函數(shù)值

很簡單,變量保存一下

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開始
        arg.push( 'arguments[' + i + ']' ) ;
    }
    obj._fn_ = this;
    val = eval( 'obj._fn_(' + arg + ')' ) // 字符串拼接,JS會調(diào)用arg數(shù)組的toString()方法,這樣就傳入了所有參數(shù)
    delete obj._fn_;
    return val;
}

傳參檢測

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

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

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

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

  • 利用已經(jīng)寫好的myCall來實(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
}
  • 測試
Array.apply({},{length:3});
// 返回 [undefined, undefined, undefined]
Array.myApply({},{length:3});
// 返回 [undefined, undefined, undefined]

效果沒區(qū)別


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

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

測試下

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
    };
}

謝謝閱讀,有任何問題請指出。歡迎一起討論。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容