摘要:那么,它到底是如何工作的呢讓我們從一種更簡(jiǎn)單的實(shí)現(xiàn)開始實(shí)際上這種實(shí)現(xiàn)代碼更短,并且更易讀是函數(shù)原型中的一個(gè)函數(shù),它調(diào)用函數(shù),使用第一個(gè)參數(shù)作為參數(shù),并傳遞剩余參數(shù)作為被調(diào)用函數(shù)的參數(shù)。
原文原文:The Most Clever Line of JavaScript
作者:Seva Zaikov
最近 一個(gè)朋友 發(fā)給我一段非常有趣的 JavaScript 代碼,是他在某個(gè) 開源庫(kù)中 看到的:
addressParts.map(Function.prototype.call, String.prototype.trim);
一開始,我覺得這是一個(gè)“不錯(cuò)的嘗試”。但是,印象中 map 好像只接受一個(gè)參數(shù),這里卻出現(xiàn)第二個(gè)參數(shù),所以去查看了 MDN文檔,才知道可以傳一個(gè)上下文(context)作為第二個(gè)參數(shù)。在這時(shí)候,我還無(wú)法解釋這段代碼,運(yùn)行完之后感到更加困惑了,因?yàn)樗谷荒苋珙A(yù)期那樣工作。
我花了至少半個(gè)小時(shí)來(lái)嘗試?yán)斫膺@段代碼,這是一個(gè)很有趣的例子,可以用來(lái)說(shuō)明 JavaScript 是一門多么魔幻的語(yǔ)言,即使已經(jīng)寫了好幾年的 JS。當(dāng)然,你可以選擇自己去弄清楚,如果想看看我的理解,請(qǐng)繼續(xù)閱讀。
那么,它到底是如何工作的呢?讓我們從一種更簡(jiǎn)單的實(shí)現(xiàn)開始(實(shí)際上這種實(shí)現(xiàn)代碼更短,并且更易讀:)):
addressParts.map(str => str.trim());
Function.prototype.call 是 JavaScript 函數(shù)原型中的一個(gè)函數(shù),它調(diào)用函數(shù),使用第一個(gè)參數(shù)作為 this 參數(shù),并傳遞剩余參數(shù)作為被調(diào)用函數(shù)的參數(shù)。舉個(gè)例子:
// 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 第二個(gè)參數(shù)的典型用法如下所示,假設(shè)有一個(gè)基于類的 React 組件,其功能是渲染一個(gè)按鈕列表:
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); } }
但是,以我的經(jīng)驗(yàn)來(lái)看,這種使用第二個(gè)參數(shù)的做法并不常見,更常見的做法是使用類屬性或裝飾器來(lái)避免綁定。
譯者注:map 第二個(gè)參數(shù)的用法等同于
const buttonsMarkup = buttons.map(this.renderButton.bind(this);
還有一個(gè)類似的方法 -- Function.prototype.apply,工作原理與 call 相同,只是第二個(gè)參數(shù)應(yīng)該是一個(gè)數(shù)組(譯者注:或者是一個(gè)類數(shù)組),它將被轉(zhuǎn)換成一個(gè)參數(shù)列表,用逗號(hào)分隔。所以,讓我們看看如何使用它來(lái)計(jì)算最大值:
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]);
現(xiàn)在,我們重新創(chuàng)建一個(gè)可以解決問(wèn)題的函數(shù)調(diào)用方式。我們想刪除字符串兩端的空白字符,這個(gè)方法位于 String.prototype ,所以我們使用 . 操作符來(lái)調(diào)用它(雖然,字符串是原始值(primitive),但是當(dāng)我們進(jìn)行方法調(diào)用時(shí),會(huì)在內(nèi)部被轉(zhuǎn)換成對(duì)象)。我們繼續(xù):
// 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 ");
我們現(xiàn)在距離答案更近一步,但是仍然沒有解釋清楚最初那段代碼:
addressParts.map(Function.prototype.call, String.prototype.trim);
讓我們自己來(lái)實(shí)現(xiàn) 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); };
現(xiàn)在,我們可以來(lái)理一理所有的東西。當(dāng)我們?cè)?.map 里面聲明函數(shù)的時(shí)候,我們給 Function.prototype.call 綁定String.prototype.trim 作為 this 上下文,然后我們?cè)跀?shù)組中的每個(gè)元素上調(diào)用這個(gè)函數(shù),把每個(gè)字符串作為 thisArg 參數(shù)的值傳遞給 call。這意味著,String.prototype.trim 將使用字符串作為 this 上下文來(lái)調(diào)用。我們已經(jīng)知道這樣做是有效的,看看下面的例子:
String.prototype.trim.call(" aa "); // "aa"
問(wèn)題解決了!但是,我認(rèn)為這并不是一個(gè)好的做法,至于應(yīng)該如何使用一種好的方式來(lái)完成這件事, 很簡(jiǎn)單,只需傳遞一個(gè)匿名函數(shù)就能搞定:
addressParts.map(str => str.trim()); // same effect也談?wù)?JavaScript 中的 call、apply 和 bind
作者在最后這一段可能講得有些簡(jiǎn)略,尤其是對(duì)于 bind 的用法,談?wù)勎业睦斫馑悸罚?/p>
// 我們從常用的 slice 說(shuō)起 // 相信很多人都寫過(guò)這樣的代碼 // 我們稱之為方法借用 Array.prototype.slice.call([1, 2, 3], 1) // [ 2, 3] // 也會(huì)有人這樣寫 [].slice.call([1, 2, 3], 1) // [2, 3] // 但上面的例子其實(shí)不是其真實(shí)的使用場(chǎng)景,因?yàn)?[1, 2, 3] 本身就是一個(gè) array,可以直接調(diào)用 slice [1, 2, 3].slice(1) // [2, 3] // 之前比較常見的場(chǎng)景是處理 argumnents,通過(guò)這種方式將這種類數(shù)組轉(zhuǎn)換成真正的數(shù)組 Array.prototype.slice.call(arguments) // 回到最上面的例子,我們已經(jīng)知道使用 call 可以讓你在某個(gè)特定上下文(context)調(diào)用函數(shù)(fn) // fn.call(context [, ...args]) // 而對(duì) call 來(lái)說(shuō),它的上下文就是 fn // 所以 call 本身也是有上下文的,那我們?yōu)槭裁床豢梢灾苯咏o call 指定一個(gè)上下文,就像這樣: Function.prototype.call.call(Array.prototype.slice, [1, 2, 3], 1) // [2, 3] // 或者是這樣,apply 接受一個(gè)數(shù)組 Function.prototype.call.apply(Array.prototype.slice, [[1, 2, 3], 1]) // [2, 3] // 當(dāng)然,也可以使用一下 bind,這樣會(huì)返回一個(gè)新的函數(shù) // 我們直接將 slice 綁定到 call 的上下文 var slice = Function.prototype.call.bind(Array.prototype.slice) slice([1, 2, 3], 1) // [2, 3] // 我們來(lái)稍微改動(dòng)一下,跟上述 slice 的例子一致 var trim = Function.prototype.call.bind(String.prototype.trim) // 上述 slice 等同于 Array.prototype.slice.call // 所以這里的 trim,等同于 String.prototype.trim.call // 那么 trim(" node") // "node" // 現(xiàn)在,在 map 里使用 trim addressParts.map(Function.prototype.call.bind(String.prototype.trim)) // 回到最初的那段代碼,這里面包含一個(gè)隱式的 bind 操作,與上面的代碼等效 // 問(wèn)題到這里就已經(jīng)解決 addressParts.map(Function.prototype.call, String.prototype.trim) // 如作者所言,這樣的代碼確實(shí)不容易閱讀,不過(guò)對(duì)于我們理解 call、bind 以及 context 的概念仍是個(gè)很好的例子 // 我們還可以寫得更復(fù)雜 // 不用擔(dān)心,下面這段代碼什么新東西都沒有,不過(guò)是給 map 綁定到 call 而已 Function.prototype.call.bind(Array.prototype.map)(addressParts, Function.prototype.call, String.prototype.trim)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/89632.html
摘要:騰訊空間超分辨率技術(shù)為用戶節(jié)省流量,處理效果和速度超谷歌技術(shù)在的標(biāo)準(zhǔn)下,處理速度在提升了,處理效果也有明顯提升。此外,也是業(yè)界首次實(shí)現(xiàn)移動(dòng)端使用深度神經(jīng)網(wǎng)絡(luò)進(jìn)行超分辨率,并保證圖片能夠?qū)崟r(shí)進(jìn)行處理。值得一提的是的對(duì)應(yīng)指標(biāo)也在名單里。 團(tuán)隊(duì)分享 魔幻語(yǔ)言 JavaScript 系列之 call、bind 以及上下文 從一行代碼來(lái)看看 JavaScript 是一門多么魔幻的語(yǔ)言,順便談?wù)?...
摘要:通過(guò)使用其構(gòu)造函數(shù),可以將一個(gè)值的類型轉(zhuǎn)換為另一種類型。如果使用兩次,可用于將該值轉(zhuǎn)換為相應(yīng)的布爾值。 編譯自:[1] + [2] – [3] === 9!? Looking into assembly code of coercion.全文從兩個(gè)題目來(lái)介紹類型轉(zhuǎn)換、寬松相等以及原始值的概念: [1] + [2] – [3] === 9 如果讓 a == true && a == fa...
摘要:稍后我們?cè)僭敿?xì)剖析,接下來(lái)先看一個(gè)問(wèn)題。還內(nèi)建了一些在之前沒有暴露給開發(fā)者的,它們代表了內(nèi)部語(yǔ)言行為。使用,可能有不少朋友一開始就想到這種方式,簡(jiǎn)單貼一下閱讀更多 在 JavaScript 環(huán)境下,可以讓表達(dá)式 a == true && a == false 為 true 嗎? 就像下面這樣,可以在控制臺(tái)打印出 ’yeah: // code here if (a == true && ...
摘要:寫在前面深入系列共計(jì)篇已經(jīng)正式完結(jié),這是一個(gè)旨在幫助大家,其實(shí)也是幫助自己捋順底層知識(shí)的系列。深入系列自月日發(fā)布第一篇文章,到月日發(fā)布最后一篇,感謝各位朋友的收藏點(diǎn)贊,鼓勵(lì)指正。 寫在前面 JavaScript 深入系列共計(jì) 15 篇已經(jīng)正式完結(jié),這是一個(gè)旨在幫助大家,其實(shí)也是幫助自己捋順 JavaScript 底層知識(shí)的系列。重點(diǎn)講解了如原型、作用域、執(zhí)行上下文、變量對(duì)象、this、...
摘要:參考鏈接在中,和是對(duì)象自帶的三個(gè)方法,都是為了改變函數(shù)體內(nèi)部的指向。返回值是函數(shù)方法不會(huì)立即執(zhí)行,而是返回一個(gè)改變了上下文后的函數(shù)。而原函數(shù)中的并沒有被改變,依舊指向全局對(duì)象。原因是,在中,多次是無(wú)效的。 參考鏈接:https://juejin.im/post/59bfe8... 在JavaScript中,call、apply和bind是Function對(duì)象自帶的三個(gè)方法,都是為了改變...
閱讀 2823·2021-10-08 10:04
閱讀 3285·2021-09-10 11:20
閱讀 535·2019-08-30 10:54
閱讀 3328·2019-08-29 17:25
閱讀 2310·2019-08-29 16:24
閱讀 895·2019-08-29 12:26
閱讀 1453·2019-08-23 18:35
閱讀 1944·2019-08-23 17:53