摘要:函數是這樣子聲明的使用了系統自己的構造函數來聲明,第一個參數是,函數體內又。構造函數調用情況正常方式調用無窮無盡當然,里還歸納了幾項比較簡單,我就不再翻譯了。
上一篇從一道面試題,到“我可能看了假源碼”中,由淺入深介紹了關于一篇經典面試題的解法。
最后在皆大歡喜的結尾中,突生變化,懸念又起。這一篇,就是為了解開這個懸念。
如果你還沒有看過前傳,可以參看前情回顧:
回顧1. 題目是模擬實現ES5中原生bind函數;
回顧2. 我們通過4種遞進實現達到了完美狀態;
回顧3. 可是ES5-shim中的實現,又讓我們大跌眼鏡...
ES5-shim實現方式源碼貼在了最后,我們看看他做了什么奇怪的事情:
1)從結果上看,返回了bound函數。
2)bound函數是這樣子聲明的:
bound = Function("binder", "return function (" + boundArgs.join(",") + "){ return binder.apply(this, arguments); }")(binder);
3)bound使用了系統自己的構造函數Function來聲明,第一個參數是binder,函數體內又binder.apply(this, arguments)。
我們知道這種動態創建函數的方式,類似eval。最好不要使用它,因為用它定義函數比用傳統方式要慢得多。
4)那么ES5-shim抽風了嗎?
答案肯定是沒抽風,他這樣做是有理由的。
神秘的函數的length屬性你可能不知道,每個函數都有length屬性。對,就像數組和字符串那樣。函數的length屬性,用于表示函數的形參個數。更重要的是函數的length屬性值是不可重寫的。我寫了個測試代碼來證明:
function test (){} test.length // 輸出0 test.hasOwnProperty("length") // 輸出true Object.getOwnPropertyDescriptor("test", "length") // 輸出: // configurable: false, // enumerable: false, // value: 4, // writable: false撥云見日
說到這里,那就好解釋了。
ES5-shim是為了最大限度的進行兼容,包括對返回函數length屬性的還原。如果按照我們之前實現的那種方式,length值始終為零。
所以:既然不能修改length的屬性值,那么在初始化時賦值總可以吧!
于是我們可通過eval和new Function的方式動態定義函數來。
同時,很有意思的是,源碼里有這樣的注釋:
// XXX Build a dynamic function with desired amount of arguments is the only // way to set the length property of a function. // In environments where Content Security Policies enabled (Chrome extensions, // for ex.) all use of eval or Function costructor throws an exception. // However in all of these environments Function.prototype.bind exists // and so this code will never be executed.
他解釋了為什么要使用動態函數,就如同我們上邊所講的那樣,是為了保證length屬性的合理值。但是在一些瀏覽器中出于安全考慮,使用eval或者Function構造器都會被拋出異常。但是,巧合也就是這些瀏覽器基本上都實現了bind函數,這些異常又不會被觸發。
So, What a coincidence!
嘆為觀止我們明白了這些,再看他的進一步實現:
if (!isCallable(target)) { throw new TypeError("Function.prototype.bind called on incompatible " + target); }
這是為了保證調用的正確性,他使用了isCallable做判斷,isCallable很好實現:
isCallable = function isCallable(value) { if (typeof value !== "function") { return false; } }
重設綁定函數的length屬性:
var boundLength = max(0, target.length - args.length);
構造函數調用情況,在binder中也有效兼容。如果你不明白什么是構造函數調用情況,可以參考上一篇。
if (this instanceof bound) { ... // 構造函數調用情況 } else { ... // 正常方式調用 } if (target.prototype) { Empty.prototype = target.prototype; bound.prototype = new Empty(); // Clean up dangling references. Empty.prototype = null; }無窮無盡
當然,ES5-shim里還歸納了幾項todo...
// TODO // 18. Set the [[Extensible]] internal property of F to true. // 19. Let thrower be the [[ThrowTypeError]] function Object (13.2.3). // 20. Call the [[DefineOwnProperty]] internal method of F with // arguments "caller", PropertyDescriptor {[[Get]]: thrower, [[Set]]: // thrower, [[Enumerable]]: false, [[Configurable]]: false}, and // false. // 21. Call the [[DefineOwnProperty]] internal method of F with // arguments "arguments", PropertyDescriptor {[[Get]]: thrower, // [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false}, // and false. // 22. Return F.
比較簡單,我就不再翻譯了。
源碼回放bind: function bind(that) { var target = this; if (!isCallable(target)) { throw new TypeError("Function.prototype.bind called on incompatible " + target); } var args = array_slice.call(arguments, 1); var bound; var binder = function () { if (this instanceof bound) { var result = target.apply( this, array_concat.call(args, array_slice.call(arguments)) ); if ($Object(result) === result) { return result; } return this; } else { return target.apply( that, array_concat.call(args, array_slice.call(arguments)) ); } }; var boundLength = max(0, target.length - args.length); var boundArgs = []; for (var i = 0; i < boundLength; i++) { array_push.call(boundArgs, "$" + i); } bound = Function("binder", "return function (" + boundArgs.join(",") + "){ return binder.apply(this, arguments); }")(binder); if (target.prototype) { Empty.prototype = target.prototype; bound.prototype = new Empty(); Empty.prototype = null; } return bound; }總結
通過學習ES5-shim的源碼實現bind方法,結合前一篇,希望讀者能對bind和JS包括閉包,原型原型鏈,this等一系列知識點能有更深刻的理解。
同時在程序設計上,尤其是邏輯的嚴密性上,有所積累。
PS:百度知識搜索部大前端繼續招兵買馬,有意向者火速聯系。。。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/81630.html
摘要:想必面試題刷的多的同學對下面這道題目不陌生,能夠立即回答出輸出個,可是你真的懂為什么嗎為什么是輸出為什么是輸出個這兩個問題在我腦邊縈繞。同步任務都好理解,一個執行完執行下一個。本文只是我對這道面試題的一點思考,有誤的地方望批評指正。 想必面試題刷的多的同學對下面這道題目不陌生,能夠立即回答出輸出10個10,可是你真的懂為什么嗎?為什么是輸出10?為什么是輸出10個10?這兩個問題在我腦...
摘要:返回的綁定函數也能使用操作符創建對象這種行為就像把原函數當成構造器。同時,將第一個參數以外的其他參數,作為提供給原函數的預設參數,這也是基本的顆粒化基礎。 今天想談談一道前端面試題,我做面試官的時候經常喜歡用它來考察面試者的基礎是否扎實,以及邏輯、思維能力和臨場表現,題目是:模擬實現ES5中原生bind函數。也許這道題目已經不再新鮮,部分讀者也會有思路來解答。社區上關于原生bind的研...
摘要:返回的綁定函數也能使用操作符創建對象這種行為就像把原函數當成構造器。同時,將第一個參數以外的其他參數,作為提供給原函數的預設參數,這也是基本的顆?;A。 今天想談談一道前端面試題,我做面試官的時候經常喜歡用它來考察面試者的基礎是否扎實,以及邏輯、思維能力和臨場表現,題目是:模擬實現ES5中原生bind函數。也許這道題目已經不再新鮮,部分讀者也會有思路來解答。社區上關于原生bind的研...
摘要:返回的綁定函數也能使用操作符創建對象這種行為就像把原函數當成構造器。同時,將第一個參數以外的其他參數,作為提供給原函數的預設參數,這也是基本的顆?;A。 今天想談談一道前端面試題,我做面試官的時候經常喜歡用它來考察面試者的基礎是否扎實,以及邏輯、思維能力和臨場表現,題目是:模擬實現ES5中原生bind函數。也許這道題目已經不再新鮮,部分讀者也會有思路來解答。社區上關于原生bind的研...
閱讀 3551·2021-10-09 09:43
閱讀 6149·2021-09-07 10:15
閱讀 2746·2019-08-30 14:03
閱讀 3074·2019-08-29 11:01
閱讀 1715·2019-08-29 10:56
閱讀 1074·2019-08-28 17:52
閱讀 3502·2019-08-26 11:42
閱讀 2547·2019-08-26 10:33