閱讀須知:這篇文章以前還有3篇相關的文章,若是少俠你沒有閱讀過的話,建議從第一篇開始看,否則可能會對某些地方比較困惑。es6
少俠們好~性能
又是一段時間沒見了,測試
此次時間比較長,應該很想我吧,哈哈。this
最近肺炎疫情嚴重,但願各位少俠都還安好,prototype
記得老老實實待在家,不要出去玩。3d
上次結束前給你們留了兩個未完成的問題,cdn
少俠們都完成了沒?
不過我估計,大多數少俠應該都是習慣性看完就關掉了,真正認真對待並動手的應該是極少數,另外還有少部分可能會等着此次看答案。
首先要給少俠們說一下,其實上次不是故意吊你們胃口,最後這兩種狀況,看着沒有太大區別,可是卻須要咱們掌握一個重要的知識,也就是:
若是我有一個帶有 this 函數,如何徹底控制它被調用時的 this 指向?
少俠你可能會說,
天辰你最近都反覆提到了,你將它放在哪一個對象上調用,它裏面的 this 就指向它,因此咱們能夠經過將它放在一個對象上調用。
好比這裏的例子中,咱們將 fn 放在 obj1 上,而後調用 ob1.fn ,獲得的結果就是 obj1 中的 name,obj2 也是同樣的道理。
爲了方便,
咱們能夠先試着將這個過程單獨放進一個函數裏,
同時咱們給它取一個名字,叫作 call :
這個函數接受一個對象,和一個fn 函數,它會將 fn 函數放置在 obj 上調用,因此能夠實現 fn 中的 this 指向 obj 的效果,而且返回最終的結果 result。
下面是它的使用方式
不過,目前的 call 函數,實際上會有反作用,調用 call 函數後,obj 中會多了一個 fn 屬性,也就是咱們的 fn 函數,
而最開始的狀況下,它是沒有的。
因此,咱們得想辦法清除掉這個反作用,
在這裏,一些少俠的第一直覺想法多是,
完過後直接刪除掉 obj 上的 fn 屬性。
若是少俠你是這樣想的話,我只能說,
青澀!
若是 obj 上以前自己就有一個 fn 屬性呢?
因此如今就比較尷尬,
不刪除的話,咱們的 call 函數會額外增長一個 fn 屬性,刪除的話,又可能刪除掉原先對象上自己就存在的 fn 屬性。
怎麼辦呢?
有些少俠可能會說,
那能不能先檢測一下,若是obj上有一個 fn 函數,咱們就臨時放在另一個屬性上,好比 fn2呢?
固然能夠,
前提是你也必須保證 obj1 上沒有一個叫作 fn2 的屬性,否則你可能又得嘗試 fn3,fn4, fn5。。。
看着就感受很 low !
「那我不能找個稀有的屬性名稱嗎? 好比用一個很長的不容易衝突的字符串,好比 UUID。」
「對,或者就放 '天辰dreamer' 屬性上,除了你,應該沒有人會取這麼奇怪的名稱!」
呵呵,我代碼中的變量名全是 XXXdreamer 的格式難道我會告訴你?
而相似 UUID 這種很長的隨機字符串的話,
按照道理來講幾乎不可能衝突,可是,
萬一少俠你是萬中無一的隨機天才呢?
因此!
更好的辦法是什麼呢?
更好的辦法是將 obj 上原來的 fn 屬性用一個變量保存好,事後再放回去:
這裏,咱們利用了一個 savedFn 變量先緩存以前的 obj.fn,事後再根據 savedFn 是否存在決定是否要從新放回去。
好了,如今咱們的 call 函數比以前好多了,
可是,等等!!!
若是咱們的 fn 函數還須要參數怎麼辦?
因此咱們須要一種方式來經過 call 函數將參數傳遞到 fn 中。
好比,給 call 函數增長一個額外的參數:
好了,
這樣是能夠的,
咱們能夠經過給 call 增長額外的參數來傳遞到 fn 內部,
可是!問題又來了,
這裏咱們只增長了一個固定參數,
當咱們碰見其餘函數時,它可能須要接受多個參數。
咱們如何知道 fn 函數會接受多少參數呢?
這個問題咱們能夠先使用 ES6 的新語法,
展開運算符:
這樣的話,咱們的 call 函數就基本成型了,
咱們來試一試:
少俠你能夠看到,咱們的 addTitle 函數接受一個前綴和一個後綴,而後咱們經過 call 函數調用 將其中的 this 指向 user,並傳遞一個 🍄 當作前綴,一個 'dreamer' 當作後綴, 而後返回最終的字符串 "🍄天辰dreamer"
徹底ok~
少俠你可能會猜到我要說什麼了。
沒錯!
你也不須要手動實現 call 函數, JS 中有一個現成的 call 函數,
全部的函數默認都有一個叫作 call 的方法,你能夠直接使用它:
注意調用方式的區別:
好了,少俠你已經知道 JS 中的 call 函數是怎麼一回事了,也知道了如何手動模擬一個相似的 call 函數。
通常來講,文章到這裏應該就要結束了,
可是,我若是到這裏就結束了,有點對不起個人標題!
由於咱們確實還有一些問題沒有考慮到,
好比:
展開運算符是 ES6 的語法,而系統自帶的 call 函數是 ES5 的語法,能不能不用ES6 的語法實現它呢?
沒錯,展開運算符是 ES6 的語法,它時間上比 call 函數出現得更晚,也就是說,若是都能使用展開運算符的話,咱們也應該直接可使用 call 函數。
因此用 ES6 的語法實現 ES5 的語法等因而使用了將來的技術。
那麼不用展開運算符怎麼解決參數不肯定問題呢?
其實也不是很難,
好比。。。。
少俠你手動多寫一些參數。。。。
寫10個參數,應該夠用了吧?
哈哈哈哈,沒想到吧!
若是你問我碰見 10 個以上參數的函數怎麼辦?
100 個?
若是這樣的話,
對不起,這麼 low 的函數不配使用個人 call 函數!
「你本身的 call 函數還使用了 12 個參數呢!」
「就你話多!」
固然,實際上少俠你也能夠從另外的層面解決問題,
好比若是函數是少俠你本身寫的話,你能夠選擇全部函數都只採用一個對象來接受全部參數。
這樣就只須要一個額外參數了,並且更方便,可讀性也更高。
或者,若是實在要實現任意參數的話,
能夠選擇使用 eval 來動態解析代碼:
這個函數要稍微複雜一些,
並且可能也有一些須要少俠你額外瞭解的內容,
好比在函數內部能夠經過 arguments[0] 獲取第一個傳遞進來的參數,arguments[1] 獲取第二個參數等等, 數組 [1,2] 轉化成字符串後是 '1,2',以及 eval 能夠動態解析代碼等等。
若是少俠你不是很清楚的話,能夠花一些時間額外瞭解一下。
並且,少俠你可能也聽過,永遠不要使用 eval 這句話,由於在 JS 中 使用 eval 是比較危險的事,並且性能也比較低。
因此, 10 個參數的函數其實也不是很壞,對吧?
好了,
在最後的 call 函數中,咱們沒有使用任何 es6 新語法,所有換成了老的語法,同時也解決了不定參數的問題。
那麼,還有漏掉的地方嗎?
好比,
咱們目前實現的 call 函數,和系統自帶的 call 函數,除了使用方式不同以外,還有什麼區別沒有?
能直接替代它嗎?
爲了弄清楚這個問題,
咱們先對比看看咱們的 call 函數和系統內置的 call 函數:
* | 內置的 call 函數 | 咱們的 call 函數 |
---|---|---|
調用方式 | 對象方法方式調用 | 普通函數方式調用 |
會產生反作用嗎 | 不會,不會無心中刪除或覆蓋obj對象原有屬性 | 不會,不會無心中刪除或覆蓋obj對象原有屬性 |
能不能接受任意參數 | 能夠接受任意參數 | 能夠實現接受任意參數 |
主要就上面幾個關鍵的點,
少俠你能夠看到,除了調用方式稍微有點區別之外,彷佛沒有區別了。
到這裏,
少俠你也許認爲咱們的 call 函數和系統內置的 call 函數功能已經如出一轍了。
實際上,
上面最後的 call 函數,確實還算比較完善,
若是少俠你網上搜一下其餘關於 call 函數的實現,可能還不必定比咱們這裏實現的更好。
「裝什麼逼呢,我立刻就去搜一下。」
「我搜了一下,果真是這樣,天辰你真牛逼!」
------ 一位口口聲聲說不是天辰小號的人說道。
好了,仍是繼續認真說 call 函數的事。
真相是,咱們上面最後的 call 函數,看似沒有什麼問題了,可是在一些少見的邊緣狀況,仍是會有問題。
主要是咱們實現方式中的這一行代碼:
這裏咱們將 fn 函數賦值給了 obj 中,做爲一個 fn 屬性。
有什麼問題呢?
問題就是,
事實上咱們不必定老是可以成功將 fn 函數賦值給 obj.fn 屬性,也就是說,
有些狀況下,給一個對象屬性賦值可能會失敗。
好比,若是一個對象是凍結後的對象的話,咱們是不能給它添加任何屬性的,也不能改變它的任何屬性:
這個時候,若是使用咱們的 call 函數的話,就會出錯:
咱們 call 函數內部試圖向 obj 上添加 fn 屬性,可是對於一個凍結對象來講,這個過程會靜默的失敗,因此最後調用 obj.fn 時就會出錯,
可是使用系統內置的 call 函數卻不會有這個問題:
很惋惜吧?
好像就差一點。
少俠你可能會想,有沒有什麼辦法,讓咱們的 call 函數,也可以解決這個問題呢?
畢竟,咱們一步一步走了這麼遠,
有些事,在最後時刻放棄,可能比一開始就放棄更加遺憾。
若是少俠你生活中有這樣的遺憾的話,
那麼在 JS 的世界裏,
是否會有這麼一絲曙光,
讓咱們的 call 函數再也不遺憾呢?
這個問題暫時留給少俠你當作練習題~
在下一次的故事中,咱們再一塊兒想辦法解決這個問題~
好了,
少俠,
江湖路遠,有緣再見!
若是少俠你看到了這裏,
你應該也知道了,目前咱們的 call 函數比起系統的 call 函數仍是有一些差距的,
就能不能解決特殊對象好比凍結對象的話題,
下面有 4 個 dreamer 發表了他們的見解,
少俠你認爲,哪一個會是最終的正確答案呢?
「我認爲能夠,可是也許須要使用到咱們目前還沒碰見的技能,好比 prototype。」
———— 烏雲dreamer
「嗯,應該能夠,但也許咱們以前碰見的技能以及足夠解決它了,好比 mixin。」
———— 涼風dreamer
「嗯。。。不肯定,不過看起來好像不能夠呢。」
———— 秋月dreamer
「換其餘人應該不能夠,可是天辰dreamer必定能夠!」
———— 不是天辰dreamer
實際上若是可使用將來語法的話,咱們也可使用 Symbol 來保證對象屬性名稱不衝突,
可是在最後咱們已經不使用將來語法了,因此也就不考慮這種方式了~
一、爲何能夠用 savedFn 保存以前的 obj.fn? 在後面給 obj.fn 從新賦值時,savedFn 不會改變嗎?
我想少俠你困惑的地方多是這種操做:
正確答案是會返回一個土豆,
若是你不知道爲何的話,
請從新複習一下上一節的內容!
二、爲何代碼要用圖片呢?
這個問題我隔一段時間要回答一次。。。
首先,我認爲個人圖片要好看一些,
而後就是若是少俠你是手機訪問的話,直接放大查看圖片比手動左右滑來滑去看代碼要方便一些。
最後就是若是你想測試的話,親自動手敲一遍代碼比單純地複製粘貼也要好一些!
一箭三雕!
三、上次留的坑還沒解釋呢,結果此次結尾又留一個坑?
哈哈,上次留的坑還沒到填的時候,此次留坑是由於剩餘內容加上的話,文章會過於長了,因此下次單獨用一篇來講。
我保證,下次,下次必定,必定先把此次的坑先填了!
聲明:本文僅限於瀟灑有趣又很酷的天辰dreamer裝逼使用,轉載請註明原做者和出處,商業轉載請聯繫我(若是真有的話)。。。