模擬call和apply

前言

上篇文章寫的是js的繼承,其中就提到了經過調用call實現繼承,那麼call是怎麼作到的了?git

call,apply在咱們日常的開發中使用的頻率不高不低,可是它們倒是一個很是重要的知識點github

實例

咱們經過一個實例來看一下它們的做用數組

var name = '小明'  //這裏不能使用let,const由於它們聲明的變量沒有掛載到window
    function fn() {
        console.log(this.name)
    }
    fn() // 小明
複製代碼
const person = {
       name: '張三'
    }
    fn.call(person)  // 張三
    fn.apply(person) // 張三
複製代碼

從上面代碼咱們能夠獲得三條結論bash

  • 調用apply/call方法的時候會當即執行(區別於bind)
  • apply/call方法調用者的this會改變指向,上面的例子this從指向window改爲指向參數obj
  • apply/call的第一個參數對象會暫時生成一個方法,輸出結果後被刪除

這裏面我着重講一下第三點,其實經過實例已近看的很清楚了,對象person實際上是沒有打印本身名字的方法的,可是經過apply/call調用後臨時生成了一個打印名字方法,得到返回值後,刪除掉這個方法,再把返回值returnapp

apply規範

有了上面的理解以後咱們再來看一下ES5對apply的描述和規範函數

當以 thisArg 和 argArray 爲參數在一個 func 對象上調用 apply 方法,採用以下步驟:
Function.prototype.apply (thisArg, argArray)

1. 若是 IsCallable(func) 是 false, 則拋出一個 TypeError 異常 .
2. 若是 argArray 是 null 或 undefined, 則返回提供 thisArg 做爲 this 值並以空參數列表調用 func 的 [[Call]] 內部方法的結果。
3. 若是 Type(argArray) 不是 Object, 則拋出一個 TypeError 異常 .
4. 令 len 爲以 "length" 做爲參數調用 argArray 的 [[Get]] 內部方法的結果。
5. 令 n 爲 ToUint32(len).
6. 令 argList 爲一個空列表 .
7. 令 index 爲 0.
8. 只要 index < n 就重複
    a. 令 indexName 爲 ToString(index).
    b. 令 nextArg 爲以 indexName 做爲參數調用 argArray 的 [[Get]] 內部方法的結果。
    c. 將 nextArg 做爲最後一個元素插入到 argList 裏。
    d. 設定 index 爲 index + 1.
9. 提供 thisArg 做爲 this 值並以 argList 做爲參數列表,調用 func 的 [[Call]] 內部方法,返回結果。
 apply 方法的 length 屬性是 2。
 注意: 在外面傳入的 thisArg 值會修改併成爲 this 值。thisArg 是 undefined 或 null
        時它會被替換成全局對象,全部其餘值會被應用 ToObject 並將結果做爲 this 值,這是第三版引入的更改。
複製代碼

ECMAScript 5.1中文文檔
ECMAScript 英文文文檔測試

第四條到第八條我看了一下中文文檔沒有看懂,去看英文文檔也沒有看懂,因此就跑去參考了一下大佬的文章,他們也都忽略了,因此咱們着重看這幾條以外的ui

實現一下apply

function getWindow(){
        return this
    }

     Function.prototype.newApply = function apply(thisArg,argArray) {
        //第一條:若是調用者不是一個方法
         if(typeof this !== "function"){
             throw new TypeError("調用者不是一個方法")
         }
         //第二條:判斷參數是null或者undefind
         if(typeof argArray ==="undefined" || typeof argArray === null){
             argArray = []
         }
         //第三條:判斷參數是否是一個對象
         if(!(argArray instanceof Object)){
             throw new TypeError("參數要是對象")
         }
         // 在外面傳入的 thisArg 值會修改併成爲 this 值
         if(typeof thisArg === 'undefined' || thisArg === null){
             thisArg = getWindow();
         }
         //參考開篇的案例咱們爲第一個參數增長一個調用者方法,上述的判斷裏面this就是調用者,而且是一個方法
         let __fn = 'fn'
         thisArg[__fn] = this
         //第九條:提供 thisArg 做爲 this 值並以 argList 做爲參數列表,調用 func 的 [[Call]] 內部方法,返回結果
         let result = thisArg[__fn](...argArray)
         delete thisArg[__fn]
         return result
     }
複製代碼

###測試一下this

var name = '小明'
    function fn() {
        console.log(this.name)
    }
    const person = {
        name: '李四'
    }
    fn.newApply(person)  // 李四
    fn.newApply(person) // 李四
複製代碼

固然這個實現比較粗糙,由於咱們能取得對象得名字__fn可能與thisArg上面得函數名重複,咱們就再也不處理了,有興趣得同窗能夠本身去試一下ES6得symbol,來改進一下es5

###實現一下 call

Function.prototype.callFn = function call(thisArg){
    var argsArray = [];
    var argumentsLength = arguments.length;
    for(var i = 0; i < argumentsLength - 1; i++){
        // argsArray.push(arguments[i + 1]);
        argsArray[i] = arguments[i + 1];
    }
    console.log('argsArray:', argsArray);
    return this.applyFn(thisArg, argsArray);
}

複製代碼

call,apply區別

其實call和apply功能徹底是同樣得

惟一得區別是apply的第二個參數是數組,而且只有兩個參數

相關文章
相關標籤/搜索