摘要:作用域鏈的用途,是保證對執(zhí)行環(huán)境有權訪問的所有變量和函數的有序訪問。作用域鏈的前端,始終都是當前執(zhí)行的代碼所在環(huán)境的變量對象。對語句來說,會將指定的對象添加到作用域鏈中。
前言
ps: 2018/05/13 經指正之后發(fā)現(xiàn)惰性加載函數細節(jié)有問題,已改正
在這里也補充一下,這些都是根據自己理解寫的例子,不一定說的都對,有些只能查看不能運行的要謹慎,因為我可能只是將方法思路寫出來,沒有實際跑過的.
是一種以事物為中心的編程思想,把構成問題事務分解成各個對象,建立對象的目的不是為了完成一個步驟,而是為了描敘某個事物在整個解決問題的步驟中的行為,三大特點缺一不可。
特點 | 作用 |
---|---|
封裝 | 將其說明(用戶可見的外部接口)與實現(xiàn)(用戶不可見的內部實現(xiàn))顯式地分開,其內部實現(xiàn)按其具體定義的作用域提供保護 |
繼承 | 子類自動共享父類數據結構和方法的機制 |
多態(tài) | 相同的操作或函數、過程可作用于多種類型的對象上并獲得不同的結果。不同的對象,收到同一消息可以產生不同的結果 |
是一種以過程為中心的編程思想,分析出解決問題所需要的步驟,然后用函數把這些步驟一步一步實現(xiàn),使用的時候一個一個依次調用就可以了。
百度百科有個很形象的比喻,
例如五子棋:
面向過程的設計思路步驟:
1、開始游戲,
2、黑子先走,
3、繪制畫面,
4、判斷輸贏,
5、輪到白子,
6、繪制畫面,
7、判斷輸贏,
8、返回步驟2,
9、輸出最后結果面向對象的設計思路步驟:整個五子棋可以分為
1、黑白雙方,這兩方的行為是一模一樣的,
2、棋盤系統(tǒng),負責繪制畫面,
3、規(guī)則系統(tǒng),負責判定諸如犯規(guī)、輸贏等。
第一類對象(玩家對象)負責接受用戶輸入,并告知第二類對象(棋盤對象)棋子布局的變化,棋盤對象接收到了棋子的變化就要負責在屏幕上面顯示出這種變化,同時利用第三類對象(規(guī)則系統(tǒng))來對棋局進行判定。面向對象是以功能來劃分問題,而不是步驟。同樣是繪制棋局,這樣的行為在面向過程的設計中分散在了多個步驟中,很可能出現(xiàn)不同的繪制版本,因為通常設計人員會考慮到實際情況進行各種各樣的簡化。而面向對象的設計中,繪圖只可能在棋盤對象中出現(xiàn),從而保證了繪圖的統(tǒng)一。
(更多內容請自行查閱,本節(jié)到此為止了.)
基本類型和引用類型之前已經寫過這個文章,就不復述了
詳情可以參考我之前寫的文章關於Javascript基本類型和引用類型小知識
來自Javascript高級程序設計3:
解析執(zhí)行環(huán)境定義了變量或函數有權訪問的其他數據,決定了它們各自的行為。每個執(zhí)行環(huán)境都有一個與之關聯(lián)的變量對象(variable object),環(huán)境中定義的所有變量和函數都保存在這個對象中。雖然我們編寫的代碼無法訪問這個對象,但解析器在處理數據時會在后臺使用它。延長作用域鏈全局執(zhí)行環(huán)境是最外圍的一個執(zhí)行環(huán)境。根據 ECMAScript 實現(xiàn)所在的宿主環(huán)境不同,表示執(zhí)行環(huán)境的對象也不一樣。在 Web 瀏覽器中,全局執(zhí)行環(huán)境被認為是 window 對象,因此所有全局變量和函數都是作為 window (全局)對象的屬性和方法創(chuàng)建的。某個執(zhí)行環(huán)境中的所有代碼執(zhí)行完畢后,該環(huán)境被銷毀,保存在其中的所有變量和函數定義也隨之銷毀(全局執(zhí)行環(huán)境直到應用程序退出——例如關閉網頁或瀏覽器——時才會被銷毀)。
每個函數都有自己的執(zhí)行環(huán)境。當執(zhí)行流進入一個函數時,函數的環(huán)境就會被推入一個環(huán)境棧中。而在函數執(zhí)行之后,棧將其環(huán)境彈出,把控制權返回給之前的執(zhí)行環(huán)境。 ECMAScript 程序中的執(zhí)行流正是由這個方便的機制控制著。
當代碼在一個環(huán)境中執(zhí)行時,會創(chuàng)建變量對象的一個作用域鏈(scope chain)。作用域鏈的用途,是保證對執(zhí)行環(huán)境有權訪問的所有變量和函數的有序訪問。作用域鏈的前端,始終都是當前執(zhí)行的代碼所在環(huán)境的變量對象。如果這個環(huán)境是函數,則將其活動對象(activation object)作為變量對象。活動對象在最開始時只包含一個變量,即 arguments 對象(這個對象在全局環(huán)境中是不存在的)。作用域鏈中的下一個變量對象來自包含(外部)環(huán)境,而再下一個變量對象則來自下一個包含環(huán)境。這樣,一直延續(xù)到全局執(zhí)行環(huán)境;全局執(zhí)行環(huán)境的變量對象始終都是作用域鏈中的最后一個對象。
標識符解析是沿著作用域鏈一級一級地搜索標識符的過程。搜索過程始終從作用域鏈的前端開始,然后逐級地向后回溯,直至找到標識符為止(如果找不到標識符,通常會導致錯誤發(fā)生).
try-catch 語句的 catch 塊
with 語句(不推薦)
這兩個語句都會在作用域鏈的前端添加一個變量對象。
對 with 語句來說,會將指定的對象添加到作用域鏈中。
對 catch 語句來說,會創(chuàng)建一個新的變量對象,其中包含的是被拋出的錯誤對象的聲明。
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語句執(zhí)行完畢后被銷毀,但在 JavaScript 中, if ,for語句中的變量聲明會將變量添加到當前的執(zhí)行環(huán)境中。如果向模擬塊狀作用域的話可以利用閉包等方法,下文會提到.
(更多內容請自行查閱,本節(jié)到此為止了.)
JavaScript 具有自動垃圾收集機制,也就是說,執(zhí)行環(huán)境會負責管理代碼執(zhí)行過程中使用的內存。這種垃圾收集機制的原理其實很簡單:找出那些不再繼續(xù)使用的變量,然后釋放其占用的內存。為此,垃圾收集器會按照固定的時間間隔(或代碼執(zhí)行中預定的收集時間),周期性地執(zhí)行這一操作。
下面我們來分析一下函數中局部變量的正常生命周期。
局部變量只在函數執(zhí)行的過程中存在。而在這個過程中,會為局部變量在棧(或堆)內存上分配相應的空間,以便存儲它們的值。然后在函數中使用這些變量,直至函數執(zhí)行結束。此時,局部變量就沒有存在的必要了,因此可以釋放它們的內存以供將來使用。在這種情況下,很容易判斷變量是否還有存在的必要;
但并非所有情況下都這么容易就能得出結論。垃圾收集器必須跟蹤哪個變量有用哪個變量沒用,對于不再有用的變量打上標記,以備將來收回其占用的內存。用于標識無用變量的策略可能會因實現(xiàn)而異,但具體到瀏覽器中的實現(xiàn),則通常有兩個策略。
標記清除
當變量進入環(huán)境(例如,在函數中聲明一個變量)時,就將這個變量標記為“進入環(huán)境”。從邏輯上講,永遠不能釋放進入環(huán)境的變量所占用的內存,因為只要執(zhí)行流進入相應的環(huán)境,就可能會用到它們。而當變量離開環(huán)境時,則將其標記為“離開環(huán)境”。
可以使用任何方式來標記變量。比如,可以通過翻轉某個特殊的位來記錄一個變量何時進入環(huán)境,或者使用一個“進入環(huán)境的”變量列表及一個“離開環(huán)境的”變量列表來跟蹤哪個變量發(fā)生了變化。說到底,如何標記變量其實并不重要,關鍵在于采取什么策略。
垃圾收集器在運行的時候會給存儲在內存中的所有變量都加上標記(當然,可以使用任何標記方式)。然后,它會去掉環(huán)境中的變量以及被環(huán)境中的變量引用的變量的標記。而在此之后再被加上標記的變量將被視為準備刪除的變量,原因是環(huán)境中的變量已經無法訪問到這些變量了。最后,垃圾收集器完成內存清除工作,銷毀那些帶標記的值并回收它們所占用的內存空間。
引用計數
引用計數的含義是跟蹤記錄每個值被引用的次數。當聲明了一個變量并將一個引用類型值賦給該變量時,則這個值的引用次數就是 1。如果同一個值又被賦給另一個變量,則該值的引用次數加 1。相反,如果包含對這個值引用的變量又取得了另外一個值,則這個值的引用次數減 1。當這個值的引用次數變成 0 時,則說明沒有辦法再訪問這個值了,因而就可以將其占用的內存空間回收回來。這樣,當垃圾收集器下次再運行時,它就會釋放那些引用次數為零的值所占用的內存。
問題一, 循環(huán)引用會一直無法回收;
問題二, 低版本IE有一部分對象并不是原生 JavaScript 對象;
(更多內容請自行查閱,本節(jié)到此為止了.)
JavaScript原型對象與原型鏈之前已經寫過這個文章,就不復述了,特意修改了之前的排版補充之類
詳情可以參考我之前寫的文章關於Javascript中的new運算符,構造函數與原型鏈一些理解
一種會在函數內部重復調用自身的寫法.
function factorial(num) { if (num <= 1) { return 1; } else { return num * factorial(num - 1); } } console.log(factorial(5));//120
一開始的常規(guī)寫法,但是有個問題是內部調用自身是使用函數名字,如果在將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(不推薦)
是一個指向正在執(zhí)行的函數的指針屬性.
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,訪問這個屬性會導致錯誤;
arguments是龐大且變化的,每次訪問需要消耗大量性能;
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
這種方式在嚴格模式和非嚴格模式下都行得通.
(更多內容請自行查閱,本節(jié)到此為止了.)
未知來源: 指函數變量可以保存在函數作用域內,因此看起來是函數將變量“包裹”了起來;
未知來源: 指在函數聲明時的作用域以外的地方被調用的函數;
官方: 一個擁有許多變量和綁定了這些變量的環(huán)境的表達式;
Javascript高級程序設計3: 閉包是指有權訪問其他函數作用域中的變量的函數;
JavaScript語言精粹: JavaScript中的函數運行在它們被定義的作用域里,而不是它們被執(zhí)行的作用域里;
阮一峰: 閉包就是能夠讀取其他函數內部變量的函數;
這是一個很難界定的點,每個人的說法都不同,包括各種專業(yè)資料,權威大神,但是唯一不變的是它們都有提到訪問其他作用域的能力.
例如這個例子,不僅可以在外部讀取函數內部變量,還能修改.
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, 匿名函數和閉包函數沒有必然關系;
匿名函數: 不需要函數名字,沒污染全局命名空間的風險并且執(zhí)行后自動銷毀環(huán)境.
很多人說匿名函數也是閉包的用法,但是在我看來這只不過是使用匿名函數的寫法來寫閉包,讓開發(fā)省掉多余步驟而已.例如:
//閉包寫法 function test1() { return function () { console.log(1); } } test1()(); // 1 //匿名函數寫法 var test2 = (function () { return function () { console.log(1); } })() test2(); // 1
2, 閉包所保存的是整個變量對象,而不是某個特殊的變量,所以只能取得包含函數中任何變量的最后一個值。
這就是為什么for循環(huán)返回的i永遠是最后一個的原因了
- 點我吧!!
- 點我吧!!
- 點我吧!!
- 點我吧!!