ps: 2018/05/13 經指正以後發現惰性加載函數細節有問題,已改正
在這裏也補充一下,這些都是根據本身理解寫的例子,不必定說的都對,有些只能查看不能運行的要謹慎,由於我可能只是將方法思路寫出來,沒有實際跑過的.html
是一種以事物爲中心的編程思想,把構成問題事務分解成各個對象,創建對象的目的不是爲了完成一個步驟,而是爲了描敘某個事物在整個解決問題的步驟中的行爲,三大特色缺一不可。前端
特色 | 做用 |
---|---|
封裝 | 將其說明(用戶可見的外部接口)與實現(用戶不可見的內部實現)顯式地分開,其內部實現按其具體定義的做用域提供保護 |
繼承 | 子類自動共享父類數據結構和方法的機制 |
多態 | 相同的操做或函數、過程可做用於多種類型的對象上並得到不一樣的結果。不一樣的對象,收到同一消息能夠產生不一樣的結果 |
是一種以過程爲中心的編程思想,分析出解決問題所須要的步驟,而後用函數把這些步驟一步一步實現,使用的時候一個一個依次調用就能夠了。程序員
百度百科有個很形象的比喻,編程
例如五子棋:
面向過程的設計思路步驟:
一、開始遊戲,
二、黑子先走,
三、繪製畫面,
四、判斷輸贏,
五、輪到白子,
六、繪製畫面,
七、判斷輸贏,
八、返回步驟2,
九、輸出最後結果面向對象的設計思路步驟:整個五子棋能夠分爲
一、黑白雙方,這兩方的行爲是如出一轍的,
二、棋盤系統,負責繪製畫面,
三、規則系統,負責斷定諸如犯規、輸贏等。
第一類對象(玩家對象)負責接受用戶輸入,並告知第二類對象(棋盤對象)棋子佈局的變化,棋盤對象接收到了棋子的變化就要負責在屏幕上面顯示出這種變化,同時利用第三類對象(規則系統)來對棋局進行斷定。 segmentfault面向對象是以功能來劃分問題,而不是步驟。一樣是繪製棋局,這樣的行爲在面向過程的設計中分散在了多個步驟中,極可能出現不一樣的繪製版本,由於一般設計人員會考慮到實際狀況進行各類各樣的簡化。而面向對象的設計中,繪圖只可能在棋盤對象中出現,從而保證了繪圖的統一。數組
(更多內容請自行查閱,本節到此爲止了.)瀏覽器
以前已經寫過這個文章,就不復述了
詳情能夠參考我以前寫的文章關於Javascript基本類型和引用類型小知識數據結構
來自Javascript高級程序設計3:閉包
執行環境定義了變量或函數有權訪問的其餘數據,決定了它們各自的行爲。每一個執行環境都有一個與之關聯的變量對象(variable object)
,環境中定義的全部變量和函數都保存在這個對象中。雖然咱們編寫的代碼沒法訪問這個對象,但解析器在處理數據時會在後臺使用它。全局執行環境是最外圍的一個執行環境。根據 ECMAScript 實現所在的宿主環境不一樣,表示執行環境的對象也不同。在 Web 瀏覽器中,全局執行環境被認爲是
window 對象
,所以全部全局變量和函數都是做爲 window (全局)對象的屬性和方法建立的。某個執行環境中的全部代碼執行完畢後,該環境被銷燬,保存在其中的全部變量和函數定義也隨之銷燬(全局執行環境直到應用程序退出——例如關閉網頁或瀏覽器——時纔會被銷燬)。app每一個函數都有本身的執行環境。當執行流進入一個函數時,函數的環境就會被推入一個環境棧中。而在函數執行以後,棧將其環境彈出,把控制權返回給以前的執行環境。 ECMAScript 程序中的執行流正是由這個方便的機制控制着。
當代碼在一個環境中執行時,會建立變量對象的一個
做用域鏈(scope chain)
。做用域鏈的用途,是保證對執行環境有權訪問的全部變量和函數的有序訪問。做用域鏈的前端,始終都是當前執行的代碼所在環境的變量對象。若是這個環境是函數,則將其活動對象(activation object)
做爲變量對象。活動對象在最開始時只包含一個變量,即arguments 對象
(這個對象在全局環境中是不存在的)。做用域鏈中的下一個變量對象來自包含(外部)環境,而再下一個變量對象則來自下一個包含環境。這樣,一直延續到全局執行環境;全局執行環境的變量對象始終都是做用域鏈中的最後一個對象。標識符解析是沿着做用域鏈一級一級地搜索標識符的過程。搜索過程始終從做用域鏈的前端開始,而後逐級地向後回溯,直至找到標識符爲止(若是找不到標識符,一般會致使錯誤發生).
這兩個語句都會在做用域鏈的前端添加一個變量對象。
對 with 語句來講,會將指定的對象添加到做用域鏈中。
對 catch 語句來講,會建立一個新的變量對象,其中包含的是被拋出的錯誤對象的聲明。
function test() { if (true) { var num = 0; } //打印if語句內聲明變量 console.log(num);//0 for (var i = 0; i < 4; i++) { } //打印for語句內聲明變量 console.log(i);//4 } test()//0 4
在其餘類 C 的語言中,由花括號封閉的代碼塊都有本身的做用域,在if ,for語句執行完畢後被銷燬,但在 JavaScript 中, if ,for語句中的變量聲明會將變量添加到當前的執行環境中。若是向模擬塊狀做用域的話能夠利用閉包等方法,下文會提到.
(更多內容請自行查閱,本節到此爲止了.)
JavaScript 具備自動垃圾收集機制,也就是說,執行環境會負責管理代碼執行過程當中使用的內存。這種垃圾收集機制的原理其實很簡單:找出那些再也不繼續使用的變量,而後釋放其佔用的內存。爲此,垃圾收集器會按照固定的時間間隔(或代碼執行中預約的收集時間),週期性地執行這一操做。
下面咱們來分析一下函數中局部變量的正常生命週期。
局部變量只在函數執行的過程當中存在。而在這個過程當中,會爲局部變量在棧(或堆)內存上分配相應的空間,以便存儲它們的值。而後在函數中使用這些變量,直至函數執行結束。此時,局部變量就沒有存在的必要了,所以能夠釋放它們的內存以供未來使用。在這種狀況下,很容易判斷變量是否還有存在的必要;
但並不是全部狀況下都這麼容易就能得出結論。垃圾收集器必須跟蹤哪一個變量有用哪一個變量沒用,對於再也不有用的變量打上標記,以備未來收回其佔用的內存。用於標識無用變量的策略可能會因實現而異,但具體到瀏覽器中的實現,則一般有兩個策略。
(更多內容請自行查閱,本節到此爲止了.)
以前已經寫過這個文章,就不復述了,特地修改了以前的排版補充之類
詳情能夠參考我以前寫的文章關於Javascript中的new運算符,構造函數與原型鏈一些理解
一種會在函數內部重複調用自身的寫法.
function factorial(num) { if (num <= 1) { return 1; } else { return num * factorial(num - 1); } } console.log(factorial(5));//120
一開始的常規寫法,可是有個問題是內部調用自身是使用函數名字,若是在將factorial賦值到一個變量以後,儘管仍是調用原factorial函數,但不是指望的調用函數自身的寫法了.
function factorial(num) { if (num <= 1) { return 1; } else { return num * factorial(num - 1); } } var another = factorial; factorial = null; console.log(another(5)); //TypeError: factorial is not a function
如上,其實是在another上調用factorial,並且若是factorial不存在以後會引發錯誤.
1, arguments.callee(不推薦)
是一個指向正在執行的函數的指針屬性.
function factorial(num) { if (num <= 1) { return 1; } else { return num * arguments.callee(num - 1); } } var another = factorial; factorial = null; console.log(another(5)); // 120
缺點:
arguments.callee
,訪問這個屬性會致使錯誤;2, 命名函數表達式
var factorial = (function f(num) { if (num <= 1) { return 1; } else { return num * f(num - 1); } }); var another = factorial; factorial = null; console.log(another(5)); // 120
這種方式在嚴格模式和非嚴格模式下都行得通.
(更多內容請自行查閱,本節到此爲止了.)
這是一個很難界定的點,每一個人的說法都不一樣,包括各類專業資料,權威大神,可是惟一不變的是它們都有提到訪問其餘做用域的能力.
例如這個例子,不只能夠在外部讀取函數內部變量,還能修改.
function test() { var num = 1; return { get: function () { console.log(num); }, add: function () { console.log(++num); } } } var result = test(); result.get(); // 1 result.add(); // 2
1, 匿名函數和閉包函數沒有必然關係;
匿名函數: 不須要函數名字,沒污染全局命名空間的風險而且執行後自動銷燬環境.
不少人說匿名函數也是閉包的用法,可是在我看來這只不過是使用匿名函數的寫法來寫閉包,讓開發省掉多餘步驟而已.例如:
//閉包寫法 function test1() { return function () { console.log(1); } } test1()(); // 1 //匿名函數寫法 var test2 = (function () { return function () { console.log(1); } })() test2(); // 1
2, 閉包所保存的是整個變量對象,而不是某個特殊的變量,因此只能取得包含函數中任何變量的最後一個值。
這就是爲何for循環返回的i永遠是最後一個的緣由了
<!DOCTYPE html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <ol> <li>點我吧!!</li> <li>點我吧!!</li> <li>點我吧!!</li> <li>點我吧!!</li> </ol> <script> var btn = document.getElementsByTagName('li'); for (var i = 0; i < 4; i++) { btn[i].onclick = function () { alert(i); }; } </script> </body> </html>
咱們能夠經過建立閉包環境模擬塊級做用域讓行爲符合預期
<!DOCTYPE html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <ol> <li>點我吧!!</li> <li>點我吧!!</li> <li>點我吧!!</li> <li>點我吧!!</li> </ol> <script> var btn = document.getElementsByTagName('li'); for (var i = 0; i < 4; i++) { btn[i].onclick = function (num) { return function () { alert(num); } }(i); } </script> </body> </html>
3, this指向問題
this 對象是在運行時基於函數的執行環境綁定的,閉包的執行環境具備全局性,所以其 this 對象一般指向 window;
function test() { return function () { console.log(this === window); } } test()(); // true
(更多內容請自行查閱,本節到此爲止了.)
在計算機科學中,柯里化(Currying)
是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數且返回結果的新函數的技術。特色是:
單說有點抽象,例如你有一個簡單的計算函數
function add(a, b) { return a + b }
若是你想再加多寫計算量怎麼辦?繼續加入參加參數,
咱們分析下上面的函數有什麼特色:
1, 計算方式固定,返回全部參數相加的結果;
2, 參數不固定,可能須要增減;
既然計算方法固定,那咱們就只須要在參數上想辦法解決,有沒一種方法可讓函數保存參數直到某個時刻再執行?天然是有的,並且仍是利用以前學過的知識點:
1, 函數做用域內的 arguments 入參變量;
2, 遞歸;
3, 閉包;
咱們能夠先看看柯里化後執行過程大概如此
add(a)(b)(c); ========至關於=========== function add(a) { return function (b) { return function (c) { return a + b + c } } }
接下來咱們就分階段寫一個方法讓原函數轉換成這種柯里化函數用法.
function toCurry(fn) { //轉入參數組 var args = [].slice.call(arguments), //提取執行方法 fn = args.shift(); return function () { //拼接上次入參和此次入參執行方法 return fn.apply(null, args.concat([].slice.call(arguments))); }; } function add(a, b) { return a + b } console.log(toCurry(add, 1)(2)); // 3
已經初步走向柯里化的道路了,而後繼續擴展延伸,
function toCurry(fn) { //保存回調函數 return function curry() { var args = [].slice.call(arguments); //判斷目前入參是否達到函數要求入參 if (args.length < fn.length) { return function () { //遞歸調用curry,拼接當前arguments和下次入參的arguments return curry.apply(null, args.concat([].slice.call(arguments))) } } else { //執行函數 return fn.apply(null, args) } } } function add(a, b, c) { return a + b + c } var fn = toCurry(add); console.log(fn(1, 2)(3)); //6 console.log(fn(1)(2)(3)); //6
目前無論怎麼傳只要達到數量就會執行,接下來咱們要把固定入參轉成自動入參方式,因此要在執行判斷那裏下功夫,咱們如今是根據函數的入參fn.length來判斷是否執行,這須要在函數預先定義好,先嚐試一下把這個改爲人手控制.
//新增入參數目限制 function toCurry(fn, len) { var len = len || fn.length; //保存回調函數 return function curry() { var args = [].slice.call(arguments); //判斷目前入參是否達到函數要求入參 if (args.length < len) { return function () { //遞歸調用curry,拼接當前arguments和下次入參的arguments return curry.apply(null, args.concat([].slice.call(arguments))) } } else { //執行函數 return fn.apply(null, args) } } } function add(a, b, c) { return a + b + c } var fn = toCurry(add, 3); console.log(fn(1)(2)(3));//6
如今已經能夠自定義配置執行時機,可是可不能夠更進一步,把配置這一步驟都免掉呢?這個就讓你們自由發揮吧,下面貼出一個網上流傳的寫法
function add() { var args = [].slice.call(arguments); var fn = function () { var newArgs = args.concat([].slice.call(arguments)); return add.apply(null, newArgs); } fn.toString = function () { return args.reduce(function (a, b) { return a + b; }) } return fn; } console.log(add(1)(2)(3));
這算是一種取巧的寫法,偷換toString
寫法達到執行的目的.由於我暫時還沒什麼想法,就不說了,
接下來還有一個問題,有些函數須要用到tihs指向的時候,咱們已經早就丟失了,因此在通用函數還要保存指針
//新增入參數目限制 function toCurry(fn, len) { var len = len || fn.length; //保存回調函數 return function curry() { var args = [].slice.call(arguments), self = this; //判斷目前入參是否達到函數要求入參 if (args.length < len) { return function () { //遞歸調用curry,拼接當前arguments和下次入參的arguments return curry.apply(self, args.concat([].slice.call(arguments))) } } else { //執行函數 return fn.apply(self, args) } } } var num = { name: 'mike', add: function add(a, b, c) { console.log(this.name); return a + b + b } } num.add = toCurry(num.add, 3); console.log(num.add(1)(2)(3));
好的,目前除了固定參數那一塊還沒其餘思路,基本方法時成型了,另外再給一個延遲執行的例子寫法你們看
var add = (function (fn) { var ary = []; return function curry() { var args = [].slice.call(arguments); if (args.length) { ary = ary.concat(args) return curry } else { return fn.apply(null, ary) } return args.length ? curry : fn.apply(null, ary) } })(function () { console.log([].slice.call(arguments).reduce((total, cur) => total += cur)); }) add(1)(2)(3)(); // 6
有參數就保存,沒參數就執行,這種使用場景也比較多,根據狀況寫法不一樣,例如這裏就沒有保存this指向.
總的來講,柯里化主要更改在於傳入的參數個數,以及它如何影響代碼的結果.
(更多內容請自行查閱,本節到此爲止了.)
若是平時有本身寫一些兼容不一樣瀏覽器差別代碼的話,確定以後中間夾雜着不少判斷分支,做爲強迫症的程序員怎麼能夠忍受這些東西,例如:
function addEvent(element, eType, handle, bol) { if (element.addEventListener) { element.addEventListener(eType, handle, bol); } else if (element.attachEvent) { element.attachEvent("on" + eType, handle); } else { element["on" + eType] = handle; } }
1, 在第一次調用的過程當中,該函數會被覆蓋爲另一個按合適方式執行的函數
function addEvent() { if (document.addEventListener) { addEvent = function (element, eType, handle, bol) { element.addEventListener(eType, handle, bol); } } else if (document.attachEvent) { addEvent = function (element, eType, handle, bol) { element.attachEvent("on" + eType, handle); } } else { addEvent = function (element, eType, handle, bol) { element["on" + eType] = handle; } } return addEvent.apply(this, arguments); }
除了第一次會執行判斷,而後原函數被覆蓋成分支流程代碼再返回函數
2, 上面提過的匿名自執行閉包寫法,比第一種好處是省去手動觸發第一次執行.
var addEvent = (function () { if (document.addEventListener) { return function (element, eType, handle, bol) { element.addEventListener(eType, handle, bol); } } else if (document.attachEvent) { return function (element, eType, handle, bol) { element.attachEvent("on" + eType, handle); } } else { return function (element, eType, handle, bol) { element["on" + eType] = handle; } } })();
函數的最後一步是調用另外一個函數,之因此有這種寫法就是由於上面說的執行環境(execution context)及做用域(scope)
,關鍵就是最後一步能不能把損耗降到最低,例如:
//沒錯,又是咱們的熟面孔函數 function add() { var a = 1, b = 2; return result(a + b) } function result(num) { return num; } console.log(add()); // 3
因爲是函數的最後一步操做,因此不須要保留外層函數的環境變量,由於都不會再用到了,只要直接只保留內層函數的環境變量(代碼8)就能夠了。
還用上面的遞歸例子,這是一個一直累積環境變量的寫法.
function factorial(num) { if (num <= 1) { return 1; } else { return num * factorial(num - 1); } } console.log(factorial(5));//120
若是咱們能利用尾調用優化寫法就可讓它一直保持一層的環境變量記錄.
function factorial(num, total) { if (num === 1) return total; return factorial((num - 1), total * num); } console.log(factorial(5, 1)); //120
缺點