一、前言
大家先預計一下以下四個函數調用的結果吧!
var test = function(){ console.log('hello world')
return 'fsjohnhuang' }
test.call() // ① Function.prototype.call(test) // ② Function.prototype.call.call(test) // ③ Function.prototype.call.call(Function.prototype.call, test) // ④
揭曉:①、③和④. 控制臺顯示hello world,并返回fsjohnhuang。②. 返回undefined且不會調用test函數;
那到底是啥回事呢?下面將一一道來。
二、從常用的call函數說起
還是通過代碼說事吧
var test2 = function(){ console.log(this) return 'fsjohnhuang' } test2() // 控制臺顯示window對象信息,返回值為fsjohnhuang test2.call({msg: 'hello world'}) // 控制臺顯示{msg:'hello world'}對象信息,返回值為fsjohnhuang
test2.call實際上是調用 Function.prototype.call (thisArg [ , arg1 [ , arg2, … ] ] ) ,而其作用我想大家都了解的,但其內部的工作原理是怎樣的呢? 這時我們可以參考ECMAScript5.1語言規范。以下是參照規范的偽代碼(各瀏覽器的具體實現均不盡相同)
Function.prototype.call = function(thisArg, arg1, arg2, ...) { /*** 注意:this指向調用call的那個對象或函數 ***/
// 1. 調用內部的IsCallable(this)檢查是否可調用,返回false則拋TypeError if (![[IsCallable]](this)) throw new TypeError() // 2. 創建一個空列表 // 3. 將arg1及后面的入參保存到argList中 var argList = [].slice.call(arguments, 1) // 4. 調用內部的[[Call]]函數 return [[Call]](this, thisArg, argList) }
那現在我們可以分析一下 ①test.call() ,并以其為基礎去理解后續的內容。它內部實現的偽代碼如下:
test.call = function(thisArg, arg1, arg2, ...){ if (![[IsCallable]](test)) throw new TypeError() var argList = [].slice.call(arguments, 1) return [[Call]](test, thisArg, argList) }
下面我們再來分析② Function.prototype.call(test) ,偽代碼如下:
Function.prototype.call = function(test, arg1, arg2, ...){ /*** Function.prototype是一個function Empty(){}函數 ***/ if (![[IsCallable]](Function.prototype)) throw new TypeError() var argList = [].slice.call(arguments, 1) // 實際上就是調用Empty函數而已,那返回undefined是理所當然的 return [[Call]](Function.prototype, test, argList) }
三、Function.prototype.call.call內部究竟又干嘛了?
有了上面的基礎那么Function.prototype.call.call就不難理解了。就是以最后一個call函數的thisArg作為Function.prototype.call的this值啦!偽代碼如下:
// test作為thisArg傳入 Function.prototype.call.call = function(test, arg1, arg2,...){ if ([[IsCallable]](Function.prototype.call)) throw new TypeError() var argList = [].slice.call(arguments, 1) return [[Call]](Function.prototype.call, test, argList) } // test作為函數的this值 // 注意:入參thisArg的值為Function.prototype.call.call的入參arg1 Function.prototype.call = function(thisArg, arg1, arg2,...){ if ([[IsCallable]](test)) throw new TypeError() var argList = [].slice.call(arguments, 1) return [[Call]](test, thisArg, argList) }
四、見鬼的合體技——Function.prototype.call.call(Function.prototype.call, test)
看偽代碼理解吧!
// test作為arg1傳入 Function.prototype.call.call = function(Function.prototype.call, test){ if ([[IsCallable]](Function.prototype.call)) throw new TypeError() var argList = [].slice.call(arguments, 1) return [[Call]](Function.prototype.call, Function.prototype.call, argList) } Function.prototype.call = function(test){ if ([[IsCallable]](Function.prototype.call)) throw new TypeError() var argList = [].slice.call(arguments, 1) return [[Call]](Function.prototype.call, test, argList) } Function.prototype.call = function(thisArg){ if ([[IsCallable]](test)) throw new TypeError() var argList = [].slice.call(arguments, 1) return [[Call]](test, thisArg, argList) }
這種合體技不就是比第三節的多了一個步嗎?有必有嗎?
五、新玩法——遍歷執行函數數組
Array.prototype.resolve = function(){ this.forEach(Function.prototype.call, Function.prototype.call) } var cbs = [function(){console.log(1)}, function(){console.log(2)}] cbs.resolve() // 控制臺輸出 // 1 // 2
這是為什么呢?那先要看看 Array.prototype.forEach(fn, thisArg) 的內部實現了,偽代碼如下:
Array.prototype.forEach = function(fn, thisArg){ var item for (var i = 0, len = this.length; i < len; ++i){ item = this[i] fn.call(thisArg, item, i, this) } }
大家再自行將編寫 Function.prototype.call.call(Function.prototype.call, item, i,this) 的偽代碼就明白了
六、總結
在項目中關于Function.prototype.call.call的用法確實少見,而且性能不高,本篇僅僅出于學習的目的,只希望再深入了解一下Function.prototype.call的內部原理而已。
尊重原創,轉載請注明在:http://www.cnblogs.com/fsjohnhuang/p/4160942.html ^_^肥仔John
七、參考
在JavaScript的Array數組中調用一組Function方法
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
文章列表