js高級-理解call()的原理


 1.call()的定義:

    call() 方法在使用一個指定的 this 值和若干個指定的參數值的前提下調用某個函數或方法。

 重點在於我們可以設置this指向另一個對象,那么這個對象中的數據和方法就可以被訪問到

如果想利用a對象中的FN1方法去處理b對象中的數據,就可以使用 FN1.call(b);

var foo = {
    value: 1
};

function bar() {
    console.log(this.value);
}

bar.call(foo); // 1
// 這里如果直接調用bar(); 因為bar()調用時創建函數執行上下文,查找this指向調用它的對象window,所以this.value就是在window中查找value
// 沒有定義value,undefined
// 而bar.call(foo);將bar中的this指向為foo對象,則調用時,首先在foo對象中查找是否有value屬性,輸出為1

2.call(obj)的作用

(1)改變了函數活動對象this的指向,指向新對象foo

(2)之后執行該函數

 

3.模擬實現call()功能

  首先,call要a對象使用b對象的方法,那么我們直接在a對象中添加一個新屬性存儲該方法,再調用a中的該方法,因為是a對象調用,所以方法this指向a對象,

得到執行結果后,再刪除該屬性,即可完成功能

  • 將函數設置為對象屬性
  • 執行該函數屬性
  • 刪除該屬性
1:
foo.fn = bar
2:
foo.fn()
3:
delete foo.fn

 

3.1 第一版的call()

實現了call()的基本功能

//因為是所有函數對象調用的call()方法,所以應該寫在函數的原型對象上
根據原型鏈 fn._proto_ === Function.prototype ,定義call方法

Function.prototype.call = function(context){
    //1.call會接收參數context,為this重新指向的對象
    //2.如何獲取調用的函數? 
    // 由bar.call(obj)可知,這是一個函數實例對象bar,調用call方法,由對象調用的函數中this將指向該對象,所以call內部的this指向bar,這就獲得了調用的函數
    
    context.fn = this; // 把bar賦給obj.fn屬性
    
    context.fn(); // 如果不用context調用,那么fn()中的this會直接指向window
    
    delete context.fn;
    //刪除fn屬性
}

但是call()本身可以接收多個參數進行處理,不止修改指向的對象,繼續修改

3.2 第二版的call()

 問題1: 因為調用call()的函數本身需要接收的參數各有不同,所以實際問題是call()要接收不定個數的參數

 obj對象 + 多個參數 , 我們可以調用call()內部的arguments對象,獲得所有接收的參數偽數組,去除第一個后,就是之后調給fn的參數數組

(1)從arguments中取索引為1后的所有數值,然后放到一個數組

// 以上個例子為例,此時的arguments為:
// arguments = {
//      0: foo,
//      1: 'kevin',
//      2: 18,
//      length: 3
// }
// 因為arguments是類數組對象,所以可以用for循環
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
// arguments是類數組對象,它的length是實時計算出來的,所以遍歷之前需要保存長度,要不每次循環又會計算,過濾出第一個傳參 args.push(
'arguments[' + i + ']'); } // 執行后 args為 [foo, 'kevin', 18]

(2)將該數組作為形參放到fn()中

調用eval()方法,構造一個js語句執行塊,將args參數變量用字符串加法實現

eval('context.fn(' + args +')') 
// 實際執行了 context.fn(args.toString())

args隱式的字符串化了

// 第二版
Function.prototype.call2 = function(context) {
    context.fn = this;
    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
    }
    eval('context.fn(' + args +')');
    delete context.fn;
}

3.3 第三版

問題1 當輸入為null時this指向window

  只需要做一個默認判斷即可,推薦用或們的短路

  var context = context || window; //如果輸入為null,空,都默認指向window 
// 這個賦值邏輯在於 有或者無(優先值) || 默認值(默認值) 可實現有取優先值,無取默認值

問題2 函數有返回值return,call()需要輸出return的內容

創建一個變量接收 eval()處理后的返回值,最后再添加return返回

  var result = eval('context.fn(' + args +')');

    delete context.fn
    return result;

 

以上是基於es3實現的call()方法

3.4 基於es6實現的call()方法

因為es6實現了擴展運算符 ...rest  所以無需使用eval()方法執行函數,而是直接將...args添加到context.fn(...args)

// ES6 call 實現
Function.prototype.es6Call = function (context) {
  var context = context || window;
  context.fn = this;
  var args = [];
  for (var i = 1, len = arguments.length; i < len; i++) {
    args.push('arguments[' + i + ']');
  }
  const result = context.fn(...args);
  return result;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM