不知道大家有沒有一種感覺,那就是自己寫的javascript代碼雖然能完全解決工作上的需要,但是,一眼望去,too simple!!!簡直就是一個傻子都能看懂的水平,于是,在工作之余,我開始去收集一些幾乎在日常開發中未曾用過的javascript寫法/有價值的javascript知識點,借此來提高自身javascript水平。
一、函數式編程與面向對象編程


<script>/*函數式編程*/function f1(){var n=999;nAdd=function(){n+=1;};var f2 = function(){console.log(n);};return f2;}var result=f1();result(); // 999 nAdd();result(); // 1000/*面向對象編程1之json寫法*/var obj1 = {n:999,nAdd:function(){this.n += 1;},getN:function(){console.log(this.n);}};obj1.getN();//999 obj1.nAdd();obj1.getN();//1000/*面向對象編程2之工廠模式*/var obj2 = function(){var obj = new Object;obj.n = 999;obj.nAdd = function(){this.n += 1;};obj.getN = function(){console.log(this.n);}return obj;};var objins = new obj2();objins.getN();//999 objins.nAdd();objins.getN();//1000/*面向對象編程3之原型prototype*/function obj3(n) {this.n = n;}obj3.prototype.nAdd = function() {this.n+=1;};obj3.prototype.getN = function() {console.log(this.n);}var objins2 = new obj3(999);objins2.getN();//999 objins2.nAdd();objins2.getN();//1000 </script>
針對上面所示的函數式編程,涉及了作用域和閉包的知識,這里簡要提一下;
里面的nAdd沒有用var關鍵字聲明,所以他是全局的,后面可以全局調用,其次利用閉包訪問和修改了函數作用域內的n值。
二、純函數和非純函數
1.此函數在相同的輸入值時,總是產生相同的輸出。函數的輸出和當前運行環境的上下文狀態無關。
2.此函數運行過程不影響運行環境,也就是無副作用(如觸發事件、發起http請求、打印/log等)。
簡單來說,也就是當一個函數的輸出不受外部環境影響,同時也不影響外部環境時,該函數就是純函數,也就是它只關注邏輯運算和數學運算,同一個輸入總得到同一個輸出。


var xs = [1,2,3,4,5]; // 純的 xs.slice(0,3); //=> [1,2,3] xs.slice(0,3); //=> [1,2,3] xs.slice(0,3); //=> [1,2,3]// 不純的 xs.splice(0,3); //=> [1,2,3] xs.splice(0,3); //=> [4,5] xs.splice(0,3); //=> []
純函數相對于非純函數來說,在可緩存性、可移植性、可測試性以及并行計算方面都有著巨大的優勢。
三、函數的柯里化
curry 的概念很簡單:將一個低階函數轉換為高階函數的過程就叫柯里化。
函數柯里化是一種“預加載”函數的能力,通過傳遞一到兩個參數調用函數,就能得到一個記住了這些參數的新函數。從某種意義上來講,這是一種對參數的緩存,是一種非常高效的編寫函數的方法:


/*一般寫法:兩數相加*/var add = function(x,y){return x + y;}/*2+1和2+10*/add(2,1);//3add(2,10);//12/*柯里化寫法*///es5var add = function(x) {return function(y) {return x + y;};};//es6var add = x => (y => x + y);//定義+1和+10函數var increment = add(1);//柯里化后得到的+1函數var addTen = add(10);//柯里化后得到的+10函數increment(2); // 3addTen(2); // 12
四、Array.reduce/Array.reduceRight(數組迭代)
先不看標題,來個題目:如何求數組[1,2,3,4,5]的各項累加的和?
大多數人恐怕會立即說出for循環、while循環,的確,for循環和while循環是可以解決上述問題,但是,這個答案顯然脫離了此篇博客的主題:逼格!!!
并且,實驗表明,將上述求和方法循環1W次,用for循環和while循環耗時大概在50~55ms,但是,用Array.reduce/Array.reduceRight僅僅耗時0.4ms!!!
關于該方法的詳解,請猛戳→https://www.w3cplus.com/javascript/array-part-8.html;這里給個demo:


var arr = [1,2,3,4,5];/*ES6寫法*/var reducer = (prevalue,curvalue)=>prevalue + curvalue;console.log(arr.reduce(reducer));//15/*ES5寫法*/var reducer = function(prevalue,curvalue){return prevalue + curvalue;};console.log(arr.reduce(reducer));//15/*解析*/數組的reduce方法有兩個參數:callback,prevalue;對于第一個參數callback,里面有4個參數prevalue、curvalue、index、array;index是當前迭代時的curvalue的索引(數組中的索引),array當然是操作的數組了;著重看另外兩個參數,prevalue和curvalue(上一個值和當前值);如果reduce方法沒有第二個參數的話,那么callback回調將數組第一個值作為prevalue,第二個值作為curvalue;如果reduce方法有第二個參數,那么將第二個參數值作為prevalue,將數組第一個值作為curvalue;每一次的操作結果將變為下一次操作的prevalue,而curvalue的值教上一次往后推一位;/*實例解析*/針對上述數組相加;沒有第二個參數,只有一個回調callback,那么他的過程是這樣的;第一次運算:prevalue為1,curvalue為2,運算結果為1+2=3,并將結果作為下次運算的prevalue;第二次運算:prevalue為3(上一次運算的結果),curvalue為3,運算結果為3+3=4,以此類推;如果有第二個參數,假設為5,arr.reduce(reducer,5),那么將是這么計算的;第一次運算:prevalue為5(第二個參數),curvalue為1,運算結果為5+1=6,并將結果作為下次運算的prevalue;第二次運算:prevalue為6(上一次運算的結果),curvalue為2,運算結果為6+2=8,以此類推;reduceRight和reduce類似,只不過他是從右邊開始迭代的。
?五、函數組合(reduce高級用法)
假想一下,要將字符串“my name is echo”進行如下操作,你會怎么做?轉大寫、然后末尾加一個感嘆號;
我想你大概會定義一個函數operate,對于形參str,先toUpperCase(),然后+ '!',ok這樣做沒錯,但是現在我需求改了,我不要求轉大寫了,你是不是得刪除那行代碼?
可能你會說了,我定義兩個函數一個是轉大寫,一個是加嘆號,需要哪個用哪個,其實無論你用幾個函數來寫,還是直接用prototype或者鏈式調用的方法去寫,都只是大眾化的寫法,且不易維護,so,請看下面的寫法:


var compose = (...args) => x => args.reduceRight((prevalue, curvalue) => curvalue(prevalue), x);var toUpperCase = x => x.toUpperCase();var exclaim = x => x + '!';var shout = compose(exclaim, toUpperCase);console.log(shout("my name is echo"));
?一眼望去,是不是兩眼懵逼?!!不怕,我們用ES5還原一下,請看:


var compose = function (...args){return function (x){return args.reduceRight(function(prevalue, curvalue){return curvalue(prevalue);},x)}};var toUpperCase = function(x){return x.toUpperCase();};var exclaim = function(x){return x + '!';};var shout = compose(exclaim, toUpperCase);console.log(shout("my name is echo"));
結合上述第四例所講到的reduce,我希望此時你已經看懂了,沒看懂也沒關系,接著往下看,繼續解析:


var shout == compose(exclaim, toUpperCase)== function (x) {return [exclaim, toUpperCase].reduceRight(function(prevalue, curvalue){return curvalue(prevalue);},x)};shout("my name is echo") == [exclaim, toUpperCase].reduceRight(function(prevalue, curvalue){return curvalue(prevalue);},"my name is echo");/*前面說過,如果reduce/reduceRight傳了第二個參數,那么該第二個參數將作為prevalue給callback調用。*//*運算過程如下(reduceRight從右往左)curvalue(prevalue):*/1.toUpperCase("my name is echo");并將結果作為prevalue供下次調用;2.exclaim(toUpperCase("my name is echo"));如此實現了先轉大寫再加感嘆號。
?