fn.call(isThis, arg1, arg2, ....)
fn.apply(isThis, [arg1, arg2, ....])
相同點:數組
區別:app
模擬實現:性能
Function.prototype.callLike = function (isThis) { //... } Function.prototype.applyLike = function (isThis) { //... }
第一個條件很簡單,判斷下 isThis 的類型便可this
isThis = typeof isThis === 'undefined' || isNull(isThis) ? window : isThis; function isNull (value) { return typeof value === 'object' && !value === true }
第二個條件和第三個條件是同樣的,call 的參數咱們須要處理下,由於咱們預期不到它的參數個數prototype
咱們從第二個參數開始遍歷一遍 arguments,而後放到一個數組裏面去code
var args = []; for (var i = 1, l = arguments.length; i< l; i++) { args.push(arguments[i]); }
可是最關鍵的調用怎麼辦呢,怎麼改變 this 的指向,通常來講誰調用誰就是 this,咱們要想改變 this,那麼就要用傳遞進來的 isThis 來調用對象
isThis.fn = this; isThis.fn(); delete isThis.fn;
這樣的話就改變了 this 的指向,以後再 delete 掉,就 ok 了,可是這裏會有一個問題,若是傳遞進來的是值類型呢,值類型咱們是不能給它添加屬性和方法的,因此 isThis.fn()
確定會提示 isThis.fn is not a function
,這裏咱們能夠想想值類型也是能夠像對象同樣有屬性和方法的,而且能夠添加屬性和方法,可是爲何賦值完後就找不到呢作用域
這裏要說下包裝類型了,值類型按理說是不可能有本身的屬性和方法的,可是考慮到有時候須要處理下雜七雜八的雜事,因此當咱們訪問或者賦值的時候,它會臨時給咱們建立一個對應的包裝對象,在咱們訪問或者賦值結束後那麼這個包裝對象就會被清理掉,那麼咱們就能夠這樣作,來模擬下包裝對象字符串
var valueType = typeof isThis; if (valueType === 'string') { isThis = new String(isThis) }else if (valueType === 'number') { isThis = new Number(isThis) }else if (valueType === 'boolean') { isThis = new Boolean(isThis) }else if (valueType === 'symbol') { isThis = Object(isThis) }
恩,這樣一來就差很少了,接下來看看傳參,args 的元素纔是咱們想要的參數,因此怎麼拆開string
var result = eval('isThis.fn(' + args.join() + ')');
使用 eval,得益於 eval 強大的能力,咱們能夠把字符串當作 js 代碼來執行,而且能夠獲得返回值,完美
可是這裏會有一個問題,若是參數是個對象,那麼 eval 對參數 toString 後咱們咱們就得不到想要的參數了,因此這裏改造下,由於 eval 能夠動態的改變做用域
var args = []; for (var i = 1, l = arguments.length; i< l; i++) { args.push('arguments[' + i + ']'); } var result = eval('isThis.fn(' + args.join() + ')');
這裏咱們用 args 存放着 arguments[1] arguments[1]... 這樣的字符串,那麼 eval 執行的時候會在上下文查找並綁定所須要的變量,這樣就能夠實現參數傳遞了
完整代碼:
Function.prototype.callLike = function (isThis) { isThis = typeof isThis === 'undefined' || isNull(isThis) ? window : isThis; var valueType = typeof isThis; if (valueType === 'string') { isThis = new String(isThis) }else if (valueType === 'number') { isThis = new Number(isThis) }else if (valueType === 'boolean') { isThis = new Boolean(isThis) }else if (valueType === 'symbol') { isThis = Object(isThis) } isThis.fn = this; var args = []; for (var i = 1, l = arguments.length; i< l; i++) { args.push('arguments[' + i + ']'); } var result = eval('isThis.fn(' + args.join() + ')'); delete isThis.fn; function isNull (value) { return typeof value === 'object' && !value === true } return result; }
Function.prototype.applyLike = function (isThis, args) { isThis = typeof isThis === 'undefined' || isNull(isThis) ? window : isThis; var valueType = typeof isThis; if (valueType === 'string') { isThis = new String(isThis) }else if (valueType === 'number') { isThis = new Number(isThis) }else if (valueType === 'boolean') { isThis = new Boolean(isThis) }else if (valueType === 'symbol') { isThis = Object(isThis) } isThis.fn = this; var args = []; for (var i = 1, l = arguments.length; i< l; i++) { args.push('arguments[' + i + ']'); } var result = eval('isThis.fn(' + args.join() + ')'); delete isThis.fn; function isNull (value) { return typeof value === 'object' && !value === true } return result; }