摘要:那么,它到底是如何工作的呢讓我們從一種更簡單的實現開始實際上這種實現代碼更短,并且更易讀是函數原型中的一個函數,它調用函數,使用第一個參數作為參數,并傳遞剩余參數作為被調用函數的參數。
原文原文:The Most Clever Line of JavaScript
作者:Seva Zaikov
最近 一個朋友 發給我一段非常有趣的 JavaScript 代碼,是他在某個 開源庫中 看到的:
addressParts.map(Function.prototype.call, String.prototype.trim);
一開始,我覺得這是一個“不錯的嘗試”。但是,印象中 map 好像只接受一個參數,這里卻出現第二個參數,所以去查看了 MDN文檔,才知道可以傳一個上下文(context)作為第二個參數。在這時候,我還無法解釋這段代碼,運行完之后感到更加困惑了,因為它竟然能如預期那樣工作。
我花了至少半個小時來嘗試理解這段代碼,這是一個很有趣的例子,可以用來說明 JavaScript 是一門多么魔幻的語言,即使已經寫了好幾年的 JS。當然,你可以選擇自己去弄清楚,如果想看看我的理解,請繼續閱讀。
那么,它到底是如何工作的呢?讓我們從一種更簡單的實現開始(實際上這種實現代碼更短,并且更易讀:)):
addressParts.map(str => str.trim());
Function.prototype.call 是 JavaScript 函數原型中的一個函數,它調用函數,使用第一個參數作為 this 參數,并傳遞剩余參數作為被調用函數的參數。舉個例子:
// this function has `Function` in prototype chain // so `call` is available function multiply(x, y) { return x * y; } multiply.call(null, 3, 5); // 15 multiply(3, 5); // same, 15
map 第二個參數的典型用法如下所示,假設有一個基于類的 React 組件,其功能是渲染一個按鈕列表:
class ExampleComponent extends Component { renderButton({ title, name }) { // without proper `this` it will fail const { isActive } = this.props; return ( ); } render() { const { buttons } = this.props; // without second param our function won"t be able // to access `this` inside const buttonsMarkup = buttons.map(this.renderButton, this); } }
但是,以我的經驗來看,這種使用第二個參數的做法并不常見,更常見的做法是使用類屬性或裝飾器來避免綁定。
譯者注:map 第二個參數的用法等同于
const buttonsMarkup = buttons.map(this.renderButton.bind(this);
還有一個類似的方法 -- Function.prototype.apply,工作原理與 call 相同,只是第二個參數應該是一個數組(譯者注:或者是一個類數組),它將被轉換成一個參數列表,用逗號分隔。所以,讓我們看看如何使用它來計算最大值:
Math.max(1, 2, 3); // if we know all numbers upfront // we can call it like that Math.max([1, 2, 3]); // won"t work! Math.max.apply(null, [1, 2, 3]); // will work! // however, ES2015 array destructuring works as well: Math.max(...[1, 2, 3]);
現在,我們重新創建一個可以解決問題的函數調用方式。我們想刪除字符串兩端的空白字符,這個方法位于 String.prototype ,所以我們使用 . 操作符來調用它(雖然,字符串是原始值(primitive),但是當我們進行方法調用時,會在內部被轉換成對象)。我們繼續:
// let"s try to imagine how trim method is implemented // on String.prototype String.prototype.trim = function() { // string itself is contained inside `this`! const str = this; // this is a very naive implementation return str.replace(/(^s+)|(s+$)/g, ""); }; // let"s try to use `.call` method to invoke `trim` " aa ".trim.call(thisArg); // but `this` is our string itself! // so, next two calls are equivalent: " aa ".trim.call(" aa "); String.prototype.trim.call(" aa ");
我們現在距離答案更近一步,但是仍然沒有解釋清楚最初那段代碼:
addressParts.map(Function.prototype.call, String.prototype.trim);
讓我們自己來實現 Function.prototype.call:
Function.prototype.call = function(thisArg, ...args) { // `this` in our case is actually our function! const fn = this; // also, pretty naive implementation return fn.bind(thisArg)(...args); };
現在,我們可以來理一理所有的東西。當我們在 .map 里面聲明函數的時候,我們給 Function.prototype.call 綁定String.prototype.trim 作為 this 上下文,然后我們在數組中的每個元素上調用這個函數,把每個字符串作為 thisArg 參數的值傳遞給 call。這意味著,String.prototype.trim 將使用字符串作為 this 上下文來調用。我們已經知道這樣做是有效的,看看下面的例子:
String.prototype.trim.call(" aa "); // "aa"
問題解決了!但是,我認為這并不是一個好的做法,至于應該如何使用一種好的方式來完成這件事, 很簡單,只需傳遞一個匿名函數就能搞定:
addressParts.map(str => str.trim()); // same effect也談談 JavaScript 中的 call、apply 和 bind
作者在最后這一段可能講得有些簡略,尤其是對于 bind 的用法,談談我的理解思路:
// 我們從常用的 slice 說起 // 相信很多人都寫過這樣的代碼 // 我們稱之為方法借用 Array.prototype.slice.call([1, 2, 3], 1) // [ 2, 3] // 也會有人這樣寫 [].slice.call([1, 2, 3], 1) // [2, 3] // 但上面的例子其實不是其真實的使用場景,因為 [1, 2, 3] 本身就是一個 array,可以直接調用 slice [1, 2, 3].slice(1) // [2, 3] // 之前比較常見的場景是處理 argumnents,通過這種方式將這種類數組轉換成真正的數組 Array.prototype.slice.call(arguments) // 回到最上面的例子,我們已經知道使用 call 可以讓你在某個特定上下文(context)調用函數(fn) // fn.call(context [, ...args]) // 而對 call 來說,它的上下文就是 fn // 所以 call 本身也是有上下文的,那我們為什么不可以直接給 call 指定一個上下文,就像這樣: Function.prototype.call.call(Array.prototype.slice, [1, 2, 3], 1) // [2, 3] // 或者是這樣,apply 接受一個數組 Function.prototype.call.apply(Array.prototype.slice, [[1, 2, 3], 1]) // [2, 3] // 當然,也可以使用一下 bind,這樣會返回一個新的函數 // 我們直接將 slice 綁定到 call 的上下文 var slice = Function.prototype.call.bind(Array.prototype.slice) slice([1, 2, 3], 1) // [2, 3] // 我們來稍微改動一下,跟上述 slice 的例子一致 var trim = Function.prototype.call.bind(String.prototype.trim) // 上述 slice 等同于 Array.prototype.slice.call // 所以這里的 trim,等同于 String.prototype.trim.call // 那么 trim(" node") // "node" // 現在,在 map 里使用 trim addressParts.map(Function.prototype.call.bind(String.prototype.trim)) // 回到最初的那段代碼,這里面包含一個隱式的 bind 操作,與上面的代碼等效 // 問題到這里就已經解決 addressParts.map(Function.prototype.call, String.prototype.trim) // 如作者所言,這樣的代碼確實不容易閱讀,不過對于我們理解 call、bind 以及 context 的概念仍是個很好的例子 // 我們還可以寫得更復雜 // 不用擔心,下面這段代碼什么新東西都沒有,不過是給 map 綁定到 call 而已 Function.prototype.call.bind(Array.prototype.map)(addressParts, Function.prototype.call, String.prototype.trim)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/89632.html
摘要:騰訊空間超分辨率技術為用戶節省流量,處理效果和速度超谷歌技術在的標準下,處理速度在提升了,處理效果也有明顯提升。此外,也是業界首次實現移動端使用深度神經網絡進行超分辨率,并保證圖片能夠實時進行處理。值得一提的是的對應指標也在名單里。 團隊分享 魔幻語言 JavaScript 系列之 call、bind 以及上下文 從一行代碼來看看 JavaScript 是一門多么魔幻的語言,順便談談 ...
摘要:通過使用其構造函數,可以將一個值的類型轉換為另一種類型。如果使用兩次,可用于將該值轉換為相應的布爾值。 編譯自:[1] + [2] – [3] === 9!? Looking into assembly code of coercion.全文從兩個題目來介紹類型轉換、寬松相等以及原始值的概念: [1] + [2] – [3] === 9 如果讓 a == true && a == fa...
摘要:稍后我們再詳細剖析,接下來先看一個問題。還內建了一些在之前沒有暴露給開發者的,它們代表了內部語言行為。使用,可能有不少朋友一開始就想到這種方式,簡單貼一下閱讀更多 在 JavaScript 環境下,可以讓表達式 a == true && a == false 為 true 嗎? 就像下面這樣,可以在控制臺打印出 ’yeah: // code here if (a == true && ...
摘要:寫在前面深入系列共計篇已經正式完結,這是一個旨在幫助大家,其實也是幫助自己捋順底層知識的系列。深入系列自月日發布第一篇文章,到月日發布最后一篇,感謝各位朋友的收藏點贊,鼓勵指正。 寫在前面 JavaScript 深入系列共計 15 篇已經正式完結,這是一個旨在幫助大家,其實也是幫助自己捋順 JavaScript 底層知識的系列。重點講解了如原型、作用域、執行上下文、變量對象、this、...
摘要:參考鏈接在中,和是對象自帶的三個方法,都是為了改變函數體內部的指向。返回值是函數方法不會立即執行,而是返回一個改變了上下文后的函數。而原函數中的并沒有被改變,依舊指向全局對象。原因是,在中,多次是無效的。 參考鏈接:https://juejin.im/post/59bfe8... 在JavaScript中,call、apply和bind是Function對象自帶的三個方法,都是為了改變...
閱讀 2825·2021-10-08 10:04
閱讀 3285·2021-09-10 11:20
閱讀 536·2019-08-30 10:54
閱讀 3331·2019-08-29 17:25
閱讀 2314·2019-08-29 16:24
閱讀 896·2019-08-29 12:26
閱讀 1455·2019-08-23 18:35
閱讀 1946·2019-08-23 17:53