說到funciton,也是我對js非常吐槽的一點,封裝的讓我眼瞎,馬蛋的,哥只能大眼睜著去黑盒的使用,簡直只有完完全全的聽各類圖書對function的道聽圖說,完全沒有做到一點點的眼見為實。
一:function是什麼
在很久很久以前,我們只知道function是一個函式,用C#的話來說就是一個方法,既然是方法嘛,肯定就是掛在類下面的,但是其實呢?
在js中函式就是物件,並不是我們慣性思維中的方法。何以為證呢?看程式碼。
從圖中可以看到我宣告瞭一個say函式,但是我很奇怪的看到,為什麼say函式會有一些屬性和方法呢?比如本該屬於Object的toString,valueOf,constructor等等?然後比較好奇的去看看constructor到底是什麼,可以看到其實是個Function建構函式,那這樣我就非常清楚了,然來say只不過是Function物件的一個例項引用,知道了這個我就可以很輕鬆的把程式碼恢復如下:
二:看看function中一些屬性和方法
現在我們知道了function其實就是一個物件,我們知道所有的引用型別都是繼承自object,那為了好做比較,我定義了一個object的例項,接下來看看function中到底都有那些專屬屬性和方法。
可以看到,子類function確實還是有點自己的東西,那接下來就簡單探討下常用的屬性和方法。
1. arguments屬性
說到這個屬性就特麼的來氣,就眼瞎,眼瞎的地方在於這個arguments屬性裡面其實做了很多很多的東西,以至於下面的一些奇怪現象可能會讓你目瞪口呆!!!
<1>奇怪現象一:我的function中都沒有形參,居然還能接受到實參的值。。。
<2>奇怪現象2:形參和實參傳遞並不統一,可以多傳遞,可以少傳遞,js都不會報錯。
。
<3>奇怪現象3:形參和arguments居然可以做到同步,太神奇了。
上面的三個現象是不是讓你覺得很奇怪???這些奇怪的現象是不是讓你覺得Function中封裝的太狠,因為他們做了很多操作,而你卻只能大眼瞪小眼,啥也看不到。。。是不是非常遺憾呢???由於看不到原始碼我也無能為力,只能根據書中的講解以及自己的理解來領悟了。“高程3”中是這麼說的,當呼叫function中傳遞的實參其實是給了Function建構函式中的一個”內部陣列“,而arguments其實是對”內部陣列“的高層封裝,封裝後的arguments不再是陣列了,而是一個偽裝的陣列,之所以這麼說是因為arguments還需要一個自己的獨有屬性callee,而這個callee儲存的就是當前的物件say,所以只能把arguments做成物件,我可以讓你眼見為實。
然後最詭異的一個問題就是形參能夠和arguments實現資料同步,既然能夠做到同步,我的第一個反應就是使用同一塊記憶體地址,但是仔細想想他們怎麼可能做到共享記憶體地址呢?但是再想想的話,arguments是對”內部陣列“的封裝,我就想這個name應該也是被做過手腳的,也就是說name其實也是對”內部陣列“的封裝,就像ECMA5中對欄位提供get/set訪問器一樣,當然這是我的一種猜測,解釋程式碼如下:
從上圖中我們看到,當我對name進行賦值的時候,其實改變的是args這個陣列的值,同理當我改變arguments的值時,其實也是修改”內部陣列“的值,通過類似這種方法來達到我們上層看到的同步機制,畫個簡圖如下:
2. length屬性
如果你知道了上面的原理,那這個也好猜測,要麼取得是正真的“內部陣列”的length,要麼就是取偽類arguments的length,反正最終都是”內部陣列”的length,對不對,比較常用但是又沒什麼好說的。
3.prototype屬性
這個也是Function內部做出來的一個屬性,很有意思,我想大家也有耳聞,也不是三言兩語能說得清楚的,準備放在下篇詳細的講講。
4.caller屬性
看這個名字大概也知道個一二,就是用來獲取當前的父函式,不要小看這個caller哦,你有沒有想過它可以實現C#中的stacktrace的功能呢?有時候我們記js日誌就靠這玩意了,比如下面這樣。
5:call,apply方法
因為這兩個函式的功能都一樣,只不過call方法必須逐一引數賦值,而apply必須傳遞陣列,如果想眼見為實,可以看看它們在vs裡面的程式碼提示,一切都明白了,所以我就放在一塊說了,不過這鳥東西有什麼好處呢?它最大的好處就是可以隨便繫結物件,然後就可以實現對繫結物件動態新增方法和屬性,可能說的有點抽象,看個例子就OK啦。
我們發現,本來我的obj只是一個空物件,通過apply之後,我的obj物件具有name和age屬性了,是不是很神奇呢?