不知不覺寫到第10篇了。這篇寫起來很忐忑,終於和高級搭上邊了(呵呵),這篇咱們 主要 說一下 JS 方法的部分高級用法(我知道的),筆者水平有限,不免有錯。廢話很少少,進入正文。javascript
咱們在看一些別人寫的優秀的代碼,特別是組件時,咱們常常能發現有init或initializa這樣的方法,它通常執行的都是初始化。那初始化通常都有幾種呢,咱們來一一介紹:java
初始化對象,顧名思義,就是對帶着一堆具體邏輯的對象進行初始化。直接上代碼:編程
```javascript var Wr = { name: "WeiRan", age: 21, init: function() { // body ... console.log("初始化完成!"); } } Wr.init(); //初始化完成! ```
這種方法,有個弊端就是可能會污染全局做用域。這樣有時候就能夠採用另外一種方法來初始化:數組
```javascript ({ name: "WeiRan", age: 21, init: function() { // body ... console.log("初始化完成!"); } }).init(); //初始化完成! ```
這種方法的有點和上篇所說的即時函數的有點是相同的:能夠在執行一次性的初始化任務時保護全局命名空間,若是初始化任務更加複雜,他會使整個初始化過程顯得更有結構化。瀏覽器
值得注意的是,這種模式主要適用於一次性的任務,並且在init完畢之後也沒有該對象的訪問(若是想有,也能夠有,在init方法底部加個return this便可)緩存
初始化分支也叫作加載時分支[load-time branching] ,是一種優化模式。當知道某些屬性在整個程序生命週期中都不會發生變化時,該模式就顯得很重要了。瀏覽器特性嗅探就是一個典型的例子,一般狀況下在site上寫JavaScript要考慮到兼容性,例如addEventListener在早期的IE上就是不支持的,在IE上支持的是attachEvent(更早的甚至是on+type),而若是咱們每次綁定事件的時候都要作個判斷,這樣會產生大量的冗[rǒng]餘代碼,可是初始化分支能夠很好的解決這個問題。代碼以下:markdown
```javascript //初始化分支 if(typeof window.addEventListener === "function"){ utils.addListener = function(el, type, fn){ el.addEventListener(type, fn, false); } utils.removeListener = function(el, type, fn){ el.removeEventListener(type, fn, false); } } // if IE else if(typeof document.attachEvent === "function"){ utils.addListener = function(el, type, fn){ el.attachEvent('on' + type, fn); } utils.removeListener = function(el, type, fn){ el.detachEvent('on' + type, fn); } } //long long ago Browser else{ utils.addListener = function(el, type, fn){ el['on' + type] = fn; } utils.removeListener = function(el, type, fn){ el['on' + type] = null; } } ```
在上一篇中,我寫了一個叫作暴露接口的用法,在 @北川 的糾正下,我發現我寫的不是很嚴謹,對於用法也寫的太含糊,特地去詳細瞭解了下 Module模式。在這裏謝謝北川的指正。關於詳細的Module模式,我會在後面專門寫一篇。在回到咱們的正文,在模塊化編程中(利用r.js 或者 seaJS),咱們常常要對某個模塊進行初始化,而這時,這種模式就顯得更有用了。代碼以下:閉包
```javascript function Wr(n){ var name; return { init : function(n){ name = n; }, getName : function(){ console.log(name); } } } var wr1 = new Wr(); wr1.init("Weiran"); wr1.getName(); //WeiRan ```
上面init很簡單,可是具體到工做中,可能初始化作的就不會這麼簡單,總之它很好的提升了模塊的可擴展性。讓咱們的代碼結構更加清晰。app
對於一些操做很是很是複雜的函數,咱們不必每次都去真的執行一遍。舉個例子,咱們在百度搜索一個關鍵詞,百度並非真的去搜一遍,而是去看之前有木有查過,而後才返回結果(這就是有時候,百度的結果有點舊了)。在函數中也是同樣,對於邏輯極度複雜,可是參數變化不大的函數(結果也不常常變化),咱們能夠緩存計算結果。代碼以下:模塊化
```javascript var fun1 = function(param){ if(!fun1.cache[param]){ var result = {}; // 複雜的邏輯 ... fun1.cache[param] = result; //把計算結果緩存 } return fun1.cache[param]; } ```
上面的代碼假定該函數只須要一個參數,若是有更多及更復雜的參數,一般的作法是將參數序列化。代碼以下:
```javascript var func2 = function(){ //JSON 序列化 var cacheKey = JSON.stringify(Array.prototype.slice.call(arguments)), result; if(!func2.cache[cacheKey]){ result = {}; // 複雜的邏輯 func2.cache[cacheKey] = result; } return func2.cache[cacheKey]; }; func2.cache = {}; ```
值得注意的是,在JSON序列化的過程當中,由於本質上都是被序列化成了字符串,全部對象也被序列化成了字符串,對於兩個不一樣對象但具備相同的屬性,這兩個對象就會被當成同一個,共享一條緩存條目。
柯里化(curry),貌似很高端的樣子,那他是什麼呢,咱們先假設有一個需求(這裏我借用騰訊的案例。):
若是使用curry怎麼實現,廢話很少說,直接上代碼:
```javascript var curry = function(fn){ var args = []; return function(){ if(arguments.length === 0){ return fn.apply(this,args); } args.push.apply(args, arguments); return fn; } } var exc = curry(function(){ var nums = [].slice.call(arguments), result = 0; nums.forEach(function(val){ result += val; }); return result; }); exc(500); exc(1000); console.log(exc()); //1500 ```
初看上去仍是有點繞的,咱們切開來看,咱們傳給curry一個匿名函數,curry返回一個通過處理的函數。而重點來了,咱們來看curry是怎麼處理的,首先是個判斷,判斷這個函數接受的參數是不是空[arguments.length === 0] 若是不爲空,把當前的參數push到args裏面去,這裏要注意的是curry 返回的是一個方法,因此就造成了一個閉包,而閉包的一個特性就是能夠訪問返回他的函數的變量和方法,故這個方法能夠改變args的值(把當前參數push到返回他的函數的args裏去),若是爲空的話,他纔會執行fn並把參數數組經過apply的形式傳給fn。
這種方式的最大好處就是能夠延遲到最後一刻才一塊兒計算, 在不少場合能夠避免無謂的計算, 節省性能, 也是實現惰性求值的一種方案.
若是以上還不夠明確,咱們在來看一個例子:
```javascript //Curry var curry = function(fn){ var slice = Array.prototype.slice, storedArgs = slice.call(arguments, 1); return function(){ var newArgs = slice.call(arguments); var args = storedArgs.concat(newArgs); return fn.apply(null, args); }; } var result = curry(function(x, y){ return x + y; }, 5)(10); console.log(result); //15 ```
上面的代碼,須要注意的是 storedArgs = slice.call(arguments, 1) 這段代碼的意思是把fn以外的參數存儲起來(咱們第一次傳個curry的那個5),而後咱們調用這個通過curry處理事後的函數時,他會把咱們新傳的參數和原來咱們傳的那個5結合組成新的參數數組進行處理[var args = storedArgs.concat(newArgs)],具體處理的代碼是fn.apply(null, args)。
當咱們發現正在調用一個函數,而且傳遞的大部分參數都是相同的,那麼該函數使用curry化是一個很好的方案。能夠經過將一個curry化,從而動態產生一個新的函數,這個新的函數會保存重複的參數(所以沒必要每次都傳遞這些參數),而且還會使用預填充原始函數所指望的完整參數列表。
原本準備也把反柯里化寫了,奈何最後發現,對於這東西,本身還不知道怎麼把它完整的表訴清楚,可能仍是本身沒徹底吃透吧,因此也暫時不寫了,JS的路還很長啊。
若是你在文中發現錯誤,歡迎指正。