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