摘要:如果是或者,會將作為值。否則,被調用的函數,進行轉換后,作為值。又怎么操作這個很神奇。能轉換它的參數為到總共個整數中的一個,這個函數遵循以下規則。不斷加入新方法的規范也是這個初衷。
今天看element-react源碼的時候,又看到了這張似曾相識卻又異常陌生的老面孔,那就是Function.prototype.apply()...
import React from "react"; import PropTypes from "prop-types"; import classnames from "classnames"; export default class Component extends React.Component { classNames(...args) { return classnames(args); } className(...args) { return this.classNames.apply(this, args.concat([this.props.className])); } style(args) { return Object.assign({}, args, this.props.style) } } Component.propTypes = { className: PropTypes.string, style: PropTypes.object };
雖然高設,犀牛書以及你不知道的Javascrit都看過apply的原理,但依然沒什么卵用,可能是缺少實踐的原因,看完就忘。
Q:
其中這句this.classNames.apply(this, args.concat([this.props.className])),又把我搞得暈頭轉向,this到底是誰?沒猜錯的話應該是組件實例吧?
那為啥不直接 this.classNames(args.concat([this.props.className])),直接傳一個數組不就行了嗎?
為什么一定要用this.classNames.apply(this, args.concat([this.props.className]))?
同理,印象中apply用來取數組最大最小值很方便,Math.max.apply(null,[1,3,8,2]),但是Math.apply( [1,3,8,2] )卻返回傳參類型錯誤?這又是為什么?
帶著這些問題,我又一次開始了啃規范之旅。
Function.prototype.apply (thisArg, argArray)
The apply method takes two arguments, thisArg and argArray, and performs a function call using the [[Call]] property of the object. If the object does not have a [[Call]] property, a TypeError exception is thrown. If thisArg is null or undefined, the called function is passed the global object as the this value. Otherwise, the called function is passed ToObject(thisArg) as the this value. If argArray is null or undefined, the called function is passed no arguments. Otherwise, if argArray is neither an array nor an arguments object (see 10.1.8), a TypeError exception is thrown. If argArray is either an array or an arguments object, the function is passed the (ToUint32(argArray.length)) arguments argArray[0], argArray[1], …, argArray[ToUint32(argArray.length)–1].
apply有2個方法,一個是thisArg,一個是argArray,然后再執行對象的[[call]]屬性。如果對象沒有call屬性,一個TypeError會被扔出來。
如果thisArg是null或者undefined,會將global作為this值。否則,被調用的函數,進行ToObject(thisArg)轉換后,作為this值。
如果argArray是null或者undefined,被調用函數不傳入任何參數。否則,argArray如果既不是數組也不是arguments object(類數組對象),會報錯TypeError.
如果 argArray是數組或者類數組對象,被調用函數傳入ToUnit32(argArray.length) arguments argArray[0],argArray[1]一直到argArray[ToUint32(argArray.length)–1].
ToObject內部怎么操作?這個不用管。
ToUnit32又怎么操作?這個很神奇。
ToUint32: (Unsigned 32 Bit Integer)
The operator ToUint32 converts its argument to one of 232 integer values in the range 0 through 232?1,
inclusive. This operator functions as follows:Call ToNumber on the input argument.
If Result(1) is NaN, +0, ?0, +∞, or ?∞, return +0.
Compute sign(Result(1)) * floor(abs(Result(1))).
Compute Result(3) modulo 232; that is, a finite integer value k of Number type with positive sign and
less than 232 in magnitude such the mathematical difference of Result(3) and k is mathematically an
integer multiple of 232.Return Result(4).
ToUnit32能轉換它的參數為0到231總共232個整數中的一個,這個函數遵循以下規則。
1.對輸入參數調用ToNumber()函數
2.如果Result(1)是 NaN, +0, ?0, +∞, 或者 ?∞, return +0
3.計算sign(Result(1)*floor(abs(Result(1))))
4.計算Result(3) modulo 232;就是說 一個無窮的正整數值k,大于0小于232,Result(3)與k的數學差異是232的整數倍。
5.返回Result(4)
A1:
多說無益,舉個例子實在:
Math.max.apply(null,[1,3,2,""])
第一步:global值替換null,瀏覽器環境為window
Math.max.apply(window,[1,3,2,""])
第二步:[1,3,2,""].length傳入到ToUnit32()中
ToUnit32(4)
第三步:計算ToUnit32(4), 作為數組轉換成arguments類數組對象的編號
1.ToNumber(4)→Result(1)=4
2.Not pass the condition
3.Math.sign(4*Math.floor(Math.abs(4))) →Result(3)=1
4.1 Mod 232 → Result(4) =1
5.return 1
第四步:傳入arguments到Math.max中
第三步會生成(1)arguments 類數組對象(是否為純函數方式生成暫時未知),將這個arguments傳入到Math.max中,與直接這樣寫Math.max(1,3,2,"")的效果一樣,此時就會返回最大值3。
這里有一個很重要的坑,為什么arguments傳入,也可以像正常Math.max(1,3,2,"")一樣?
其實這個坑是因為Math.max(1,3,2,"")這個例子中的實參,在函數內部的實參就是arguments,且這個arguments的長度為4,這和絕大多數的函數一樣,除箭頭函數外的所有函數,都有這個arguemnts實參array-like object。
看完這個, Math.max.apply(null,[1,3,8,2])的內部原理應該也就清楚了吧。
A2:
回到源代碼中this.classNames.apply(this, args.concat([this.props.className]))。
1.為什么直接傳入["foo","bar"]數組不行?
因為當我們傳入["foo","bar"]到classNames時,此時return classnames(args);中的args會變成一個二維數組[["foo","bar"]],與classnames模塊的預期值類型一維數組相悖。
2.為什么調用Function.prototype.apply后就可以?
因為如規范中的第三步中所示, this.classNames.apply(this, args.concat([this.props.className]))中,在apply的內部算法中,會將args.concat([this.props.className])這個數組,轉換為一個實參類數組對象arguments,跳過形參賦值步驟,直接深入到 this.classNames()內部,這樣一來,就實現了一個一個傳值的效果,其中的...args就會成為一個一個獨立的參數,而args會作為實參數組傳給classnames函數。
說了這么多,總結起來其實就一句話:
callObject.method.apply(thisArg,thisArray),可以將thisArray轉化為arguments,傳入到callObject.method內部。加粗加斜的這句真的很重要!
加粗加斜的這句真的很重要!
加粗加斜的這句真的很重要!
參考:
1.The mathematical function sign(x) yields 1 if x is positive and ?1 if x is negative. The sign function is not
used in this standard for cases when x is zero
2.求模
對于整數a,b來說,取模運算或者求余運算的方法要分如下兩步:
1.求整數商:c=a/b
2.計算模或者余數:r=a-(c*b)
求模運算和求余運算在第一步不同
取余運算在計算商值向正無窮方向舍棄小數位
取模運算在計算商值向負無窮方向舍棄小數位
例如:4/(-3)約等于-1.3
在取余運算時候商值向0方向舍棄小數位為-1
在取模運算時商值向負無窮方向舍棄小數位為-2
所以
4rem(-3)=1
4mod(-3)=-2
3.C和JS中%表示取余,python中表示取模
python中%表示取模,have a try 1 Mod 232 →1
Merry Christmas ~
That"s it !
2019.8.20更新
js忍者秘籍給出的精簡解釋是:“js可以通過apply和call顯示指定任意對象作為其函數上下文。”強烈建議閱讀P52~P55。言簡意賅,通俗易懂。
主要有兩個用途:
普通函數中指定函數上下文
回調函數中強制指定函數上下文
回調函數強制指定函數上下文很好地體現了函數式編程的思想,創建一個函數接收每個元素,并且對每個元素做處理。
本質上,apply和call都是為了增強代碼的可擴展性,提升編程的效率。
我想這也是js中每一個方法或者api的初衷,提供更加便利的操作,解放初更多的生產力。不斷加入新方法的es規范也是這個初衷。
由于我使用vue比較多,所以根據以上的應用場景出1個單文件組件示例和1個普通示例供參考:
// 普通函數中指定函數上下文 // 通過Math.max()獲得數組中的最大項
// 回調函數中強制指定函數上下文 // 手動實現一個Array.prototype.filter const numbers = [1, 2, 3, 4]; function arrayFilter(array, callback) { const result = []; for (let i = 0; i < array.length; i++) { const validate = callback.call(array[i], array[i]); if (validate) { result.push(array[i]); } } return result; } const evenArrays = arrayFilter(numbers, (n) => n % 2 === 0); console.log(evenArrays);// [2, 4]
期待和大家交流,共同進步,歡迎大家加入我創建的與前端開發密切相關的技術討論小組:
SegmentFault專欄:趁你還年輕,做個優秀的前端工程師
Github博客: 趁你還年輕233的個人博客
掘金主頁:趁你還年輕233
SegmentFault技術圈: ES新規范語法糖
知乎專欄:趁你還年輕,做個優秀的前端工程師
前端開發交流群:660634678
努力成為優秀前端工程師!
加油,前端同學們!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/90394.html
摘要:抽象操作是在調用函數對象的內部的方法。指的是調用函數,指的是的值,然后是傳入到內部方法相應參數的值。切換上下文執行,為函數調用棧在尾部調用函數做準備,切換運行中執行上下文,實現上下文的動態改變。萬事具備,執行,調用函數即可。 showImg(https://segmentfault.com/img/bVbwBui?w=657&h=382); 今天在學習前端工程化的過程中,遇到一個是實驗...
摘要:眾所周知,這三個函數都是改變執行上下文的,那么我們來捋一捋,這些函數內部到底做了什么。 前言 稍微翻了一下call,apply, bind 的各種論壇上的文章, 發現講的都太淺了,大部分都只講了個用法, 對于實現的原理卻都沒有提,因此,在這里,我寫下這篇文章, 希望能讓大家認識到原理所在。 眾所周知, 這三個函數都是改變執行上下文的 , 那么我們來捋一捋,這些函數內部到底做了什么。 c...
摘要:等價與注意如果構造函數有自己的返回,那么情況有所不同。,定義了的屬性,默認是聲明的函數名,匿名函數是。匿名函數表達式和函數聲明都不會創建匿名作用域。 ECMAScript規范中對Function的文檔描述,我認為是ECMAScript規范中最復雜也是最不好理解的一部分,它涉及到了各方面。光對Function就分了Function Definitions、Arrow Function D...
摘要:關于的指向的問題請參照我的學習筆記。那么在這里事實上都改變了函數方法被調用時的指向。那么回調函數在執行的時候指向還是。大家看完之后應該已經懂了把還是不懂的話在評論區留言,我給大家解答。 先從一個小題目開始吧: 要實現一個加法函數,這個時候向函數當中傳遞個數大于0的若干個整形數據,求所有這些數據的和。 Function.prototype.call Function.prototype...
閱讀 2458·2021-09-28 09:36
閱讀 3597·2021-09-22 15:41
閱讀 4388·2021-09-04 16:45
閱讀 1957·2019-08-30 15:55
閱讀 2847·2019-08-30 13:49
閱讀 825·2019-08-29 16:34
閱讀 2370·2019-08-29 12:57
閱讀 1679·2019-08-26 18:42