JavaScript語言本身是一個龐大而複雜的知識體系,複雜程度不低於任何一門後端語言,本文針對JavaScript語言的核心概念進行簡單的梳理,對應的每個知識點僅僅點到為止,不作詳細介紹。目的是為了方便大家快速審查自己對JavaScript的知識結構是否完善,如有遺漏或不正確的地方,還望批評指正。
JavaScript核心概念歸納整理
- 資料型別、判斷方法
- 執行上下文
- 變數物件、活動物件
- 原型、原型鏈
- 作用域、作用域鏈
- 閉包、垃圾回收機制
- this指向
- 類和模組
- 繼承
- 函數語言程式設計
- 同步非同步
- JS正規表示式
- 事件模型
- Ajax、跨域訪問
- DOM
- BOM
資料型別、判斷方法
ECMAScript的基本資料型別有5種:Undefined、Null、Boolean、Number、String。
其中Boolean、Number、String屬於原始型別,Undefined、Null屬於原始值。
原始型別代表了各自型別的所有成員,原始值則代表了各自特殊型別的唯一成員。
ECMAScript的複合資料型別有1種:Object(物件型別)。
Object是一種複合值,它將很多值(原始型別/值或者其他物件)聚合在一起,通過屬性的形式進行訪問。
ECMAScript的特殊物件型別:Array、Function、Math、Date、JSON、RegExp、Error,每種型別都各自代表一種獨立的類,不同的類例項擁有不同的類特性以及對應的操作方式。
ECMAScript常見資料型別劃分方式:
1.原始型別、物件型別
2.值型別、引用型別
3.可變型別、不可變型別
4.可擁有方法型別、不可擁有方法型別
精確區分資料型別的判斷方法:Object.prototype.toString.call
執行上下文
JS的執行上下文可以理解為當前程式碼的執行環境,在執行JS程式時,每遇到一段JS可執行程式碼,都會建立一個可執行上下文。JS當中可執行程式碼分為三種:全域性程式碼、函式程式碼、eval程式碼。所以一段JS程式必定會產生多個執行上下文,而JavaScript引擎則是以堆疊的形式來對其進行管理,也就是常說的函式呼叫棧。棧底是全域性上下文,棧頂則是當前正在執行的上下文。例如:
執行上下文在函式呼叫棧中的順序為(自底向上):globalStack => threeStack => twoStack => oneStack
特性
1.單執行緒
2.同步執行,只有棧頂的上下文處於執行中,其他上下文需要等待
3.全域性上下文只有唯一的一個,它在瀏覽器關閉時出棧
4.函式的執行上下文的個數沒有限制
5.每次某個函式被呼叫,就會有個新的執行上下文為其建立,即使是呼叫的自身函式,也是如此。
變數物件、活動物件
在介紹變數物件與活動物件前,首先我們需要更深入的理解執行上下文的生命週期,執行上下文的生命週期分為兩個階段:
第一個階段是建立階段,每當JS引擎在執行一段可執行程式碼時,都會先進入建立階段。該階段會分別建立變數物件,建立作用域鏈,以及確定this的指向,作用域鏈和this指向會在後文闡述。所謂變數物件就是用於儲存在執行上下文中定義的變數和函式宣告,在當前上下文中每找到一個變數宣告,就會在變數物件中建立一個同名的屬性,每找到一個函式宣告,就會建立一個以函式名命名的屬性,屬性值則為指向該函式所在記憶體地址的引用。這些預先建立好的屬性以及屬性值,儲存著該上下文中所有的變數資料,為後續程式碼的執行奠定基礎。
第二個階段是執行階段,當變數物件,作用域鏈,this指向都建立之後,執行上下文會進入到執行階段。在該階段中變數物件會轉化為活動物件,此時活動物件中的屬性都允許被訪問,並且可以執行其他資料性的操作。
兩者區別:
執行上下文處於建立階段時,變數物件中的屬性是不允許被訪問的,但是在進入到執行階段後,變數物件轉化為活動物件,並且裡面的屬性都允許被外界訪問。其實兩者都屬於同一個物件,只是處於執行上下文的不同生命週期而已。
原型、原型鏈
在JavaScript中,每一個物件都會和另一個物件產生關聯,從另一個物件上繼承屬性,這裡所指的另一個物件就是我們耳熟能詳的原型。原型本身也是一個物件,其他物件可以通過它實現屬性的繼承,也可以將任何一個物件作為自身物件的原型。JS中的任何物件都有原型,除了原型鏈頂端的物件:Object.prototype
所謂原型鏈,就是由物件原型所構成的訪問鏈,我稱之為“原型繼承鏈”。一個JS物件的原型指向其父類物件,而父類物件的原型又指向父類物件的父類物件,這種通過原型層層連線起來的關係就是原型鏈。
以下是幾種獲取原型物件的方法:
作用域、作用域鏈
說到作用域,就必須結合變數的訪問許可權來說明。一個變數的作用域就是在程式中定義變數的區域,它規定了執行程式如何對變數進行查詢,也就是確定當前的執行程式碼對變數的訪問許可權。在ES5中有全域性作用域、函式作用域、eval作用域,在ES6中新增了塊級作用域。
作用域鏈,則需要結合函式的巢狀來說明。當定義一個函式時,它實際上建立了一個作用域節點,該節點上儲存著當前作用域中的區域性變數,並且該節點會掛載在作用域鏈的底端。在該函式中巢狀定義另一個函式時,同樣會建立另一個函式作用域的節點,該節點同樣也儲存著當前函式作用域中的區域性變數,在作用域鏈中會將該節點掛載在外層函式的節點之下。所以在進行變數訪問時,會從自身節點開始查詢,如果未找到變數的對應值,則會繼續查詢上一個節點。而由這一系列節點所串聯起來的鏈就是我們所說的作用域鏈。
JavaScript中的函式採用靜態作用域,也稱詞法作用域。當在執行函式呼叫時,不管何時何地執行函式,其中的變數在函式定義時就已經決定了,函式會從自身作用域節點開始,沿著作用域鏈向上訪問變數的值。
注意:作用域鏈的頂端是全域性作用域,作用域鏈在函式定義時就已經建立了。
閉包、垃圾回收機制
閉包,又一個老生常談的話題,可以用一句話對之概括:有權訪問另一個函式作用域內變數的函式都是閉包。例如:
這裡返回的inner函式就是能夠訪問outer函式中變數的閉包,除inner函式之外的外部作用域都無法訪問outer函式中的變數a。
閉包特性:
1.函式返回巢狀的函式形成閉包
2.閉包內部可以訪問外部的引數和變數
3.外部引數和變數在被閉包引用時不會被垃圾回收機制回收
閉包優點:
1.可避免變數對全域性的汙染
2.允許函式私有成員的存在
3.允許變數長駐記憶體
閉包缺點:
由於變數常駐記憶體,增大記憶體使用量,使用不當很容易造成記憶體洩漏。
閉包應用場景:
1.採用函式引用方式的setTimeout呼叫
2.將函式關聯到物件的例項方法
3.封裝相關的功能集
JS垃圾回收機制原理
JavaScript中的垃圾回收,主要是一種針對程式執行環境中記憶體的管理機制,該機制最大限度的優化了JS程式對作業系統記憶體的使用。垃圾回收機制也同樣非常容易理解:就是利用垃圾收集器,週期性的回收那些程式中,不被其他引用所指向的變數的記憶體資源。不被其他引用所指向的變數就是程式中不會再用到的變數,也就是生命週期結束的變數,這種變數多為區域性變數,而全域性變數只有在關閉瀏覽器或終止當前執行環境的情況下其生命週期才會結束。所以此時垃圾收集器所要做的就是週期性的檢索程式中處於結束狀態的變數,同時回收他們所佔用的記憶體資源。
而閉包的使用則無疑會增加程式對記憶體資源的佔用,因為在閉包中儲存著對外部變數的引用,所以只要閉包中儲存的外部引用未停止使用,那麼外部變數就永遠存在,且其所佔用的記憶體資源無法被垃圾回收機制所釋放。因此合理的使用閉包,能優化程式的執行效率及降低程式的資源佔有率。
this指向
this的指向問題無疑是JavaScript語言中必須掌握的核心概念。上文提到,在執行上下文建立的階段,就會建立this指向。而更細緻的說,this的指向,是在函式被呼叫的時候確定的。
下面是this指向的四種場景:
1.如果一個函式中有this,但是它沒有以物件方法的形式呼叫,而是以函式名的形式執行,那麼this指向的就是全域性物件。
2.如果一個函式中有this,並且這個函式是以物件方法的形式呼叫,那麼this指向的就是呼叫該方法的物件。
3.如果一個函式中有this,並且包含該函式的物件也同時被另一個物件所包含,儘管這個函式是被最外層的物件所呼叫,this指向的也只是它上一級的物件。
4.如果一個建構函式或類方法中有this,那麼它指向由該建構函式或類建立出來的例項物件。
類和模組
類的概念
JavaScript是一種弱型別語言,其本身並不像Java等語言那樣對資料具有很強的型別區分,所以為了能夠具有物件導向的編碼風格,以其獨有的方式實現了類的機制。在JavaScript中,類的實現是基於原型(prototype)繼承機制的,如果兩個例項都從同一個原型物件上繼承了屬性,可以說它們是屬於同一個類的例項。類讓每一個成員物件都共享某些屬性,這種屬性共享的方式在程式設計中佔有舉足輕重的地位。
ES5中的類:在ES5中,類是由函式來定義的,定義類的函式稱之為建構函式。一般這類函式會以首字母大寫的形式出現,普通的函式和方法都是以小寫字母開頭,物件例項化時通過new關鍵字來呼叫建構函式。建構函式上掛載著一個prototype屬性,該屬性存放的是當前類的原型物件,原型物件是類的核心,用於為每一個例項物件提供公有屬性。原型物件中還擁有一個constructor屬性,用於指向當前類的建構函式。建構函式是類的“公共標識”,而原型物件是類的“唯一標識”。以下是一個類,用於表示點的座標:
ES6中的類:在ES6中,類的表示就更具語義化,寫法上更類似於傳統的面嚮物件語言。它引入了class關鍵字作為類的標識,並將ES5中prototype的constructor屬性直接作為其內部的建構函式,並且在定義類方法時不需要新增function關鍵字,類方法之間也不需要用逗號進行分隔。類中的靜態方法和靜態屬性用static關鍵字表示,一旦類函式和類屬性用static關鍵字標記後,例項物件將不會繼承這些屬性和方法,只能通過類本身來呼叫。同樣用ES6中的類來表示上述的例子:
模組化
JavaScript模組化的歷史由來已久,也並非小編用幾行的篇幅就能一語帶過,在這僅對它的特性及應用場景進行籠統的說明,如有對其原委感興趣的讀者,可以搜尋其他更詳細的相關資料。
先來說CommonJS,CommonJS模組化規範主要應用於伺服器端程式設計,載入模組的方式屬於同步載入,只有在載入完成之後才能執行後續操作。一個.js檔案就是一個CommonJS模組,在伺服器端的模組檔案一般都儲存在本地硬碟,所以載入速度較快。每一個模組都有自己的作用域,裡面定義的變數、函式、類都是私有的,對其他檔案不可見。NodeJS、webpack就是以CommonJS規範的形式來實現的。
CommonJS模組特點:
所有程式碼都執行在模組作用域,不會汙染全域性作用域。
模組可以多次載入,但是隻會在第一次載入時執行一次,然後執行結果就被快取了,以後再載入,就直接讀取快取結果。要想讓模組再次執行,必須清除快取。
模組載入的順序,按照其在程式碼中出現的順序。
在瀏覽器環境中,對於模組的下載很大程度上取決於網速的快慢,因此極有可能出現長時間等待現象,從而阻塞瀏覽器的渲染。所以就必須採用非同步模式(AMD、CMD)。AMD模組規範採用非同步載入方式,主要用於客戶端瀏覽器環境下,但既可用於瀏覽器端也可用於服務端,CMD則專注於瀏覽器端的模組化開發。
AMD和CMD的區別:兩者的區別在於對模組的載入和執行方式不同,AMD會在載入完模組的同時去執行模組,從而擁有延遲低、效率高的特性;CMD則是載入完所有依賴模組後,再進入程式,遇到需要執行的模組才會執行相應的操作。
requireJS是基於AMD規範實現的模組載入器
seaJS是基於CMD規範實現的模組載入器
繼承
上文說道,由於js本身並不像其他傳統的面嚮物件語言那樣,生來就具備類的概念。所以在實現繼承的同時,需要用到js的原型及prototype機制或apply、call、bind方法來實現。這裡直接上碼,介紹幾種常見的繼承方式:
類式繼承
原型鏈繼承
繼承
函數語言程式設計
JavaScript並不是專門的函數語言程式設計語言,但卻能夠應用函數語言程式設計技術,像物件一樣去操控函式。下面就例舉一些JS函數語言程式設計的典型應用:
使用非函式式的方式計算陣列中元素的平均值和標準差
使用函式式的程式設計方式
同步非同步
單執行緒是JavaScript語言的一大特點,也就是在同一時間只能做一件事。所謂同步,原意就是程式的執行順序與書寫順尋保持一致;而非同步,指的是程式並非按照書寫的順序來執行,會存在“跳過”執行的現象。
談及JS非同步,就不得不提兩個改變執行順序的基礎函式:setTimeout和setInterval函式。這兩個函式在執行時會被壓入事件迴圈佇列當中,在當前作用域下的所有程式都執行完成後,才會開始執行排列在事件迴圈佇列當中的函式,所以這兩個函式能夠改變程式的執行順序。來看例子:
JS正規表示式
正規表示式大家應該不會陌生,JavaScript中的正規表示式用RegExp類的例項物件來表示,可以使用RegExp()建構函式來建立,也可以用直接量表示式來建立。它的主要功能是用來描述或匹配一段符合某個語法規則的字串,多用於在一段較長的文字中檢索或替換那些符合模式的字串內容,也經常用於使用者的輸入校驗。例如,使用如下程式碼來解析一個URL:
事件模型
JavaScript的事件模型,是所有前端研發工程師必須必須必須弄清楚的一個基礎核心概念!下面為了能讓讀者迅速回憶起這部分相關的理論知識,筆者將以關鍵詞的形式進行闡述。
DOM事件流:當使用者觸發事件時,該事件首先會從最頂層的document物件開始,自頂向下沿著DOM樹的結構逐層傳播,直到觸發事件的DOM節點物件。之後會從觸發事件的DOM節點物件開始,自底向上逐層傳播,最後返回到最頂層document物件為止。這就是整個事件流傳播的過程。
事件捕獲:從最頂層的document物件開始,自頂向下沿著DOM樹的結構逐層傳播,直到觸發事件的DOM節點物件的過程。
事件冒泡:從觸發事件的DOM節點物件開始,自底向上逐層傳播,最後到達最頂層的document物件的過程。
阻止事件流傳播:既可以阻止事件捕獲階段的傳播,也可以阻止事件冒泡階段的傳播。在支援addEventListener()
方法的瀏覽器中,呼叫事件物件的stopPropagation()
方法阻止事件傳播。在IE9之前,設定事件物件的cancelBubble
屬性為true來實現阻止事件進一步傳播。
阻止事件的預設行為:在支援addEventListener()
的瀏覽器中,呼叫事件物件的preventDefault()
方法取消事件的預設操作。在IE9之前,設定事件物件的returnValue
屬性為false來阻止事件的預設行為。
事件委託/事件代理:利用事件冒泡的原理,將事件加到目標節點的父級節點上,觸發執行效果。好處就是:(1)可以減少事件繫結的次數,利於提高效能。(2)新新增的元素還會有之前的事件。
Ajax、跨域訪問
Ajax是瀏覽器專門用來和伺服器進行互動的非同步通訊技術,其核心物件是XMLHttpRequest,通過該物件可以建立一個Ajax請求。為了防止XSS攻擊,瀏覽器對Ajax做了限制,不允許Ajax跨域請求伺服器,就是隻能訪問當前域名下的url。
JS的跨域訪問,就是在不同的域名下進行HTTP請求與響應。JSONP就是一種常用的跨域通訊方式,他利用了指令碼跨域能力來模擬Ajax請求。
JSONP原理:由於Ajax請求受到同源策略的限制,所以無法跨域訪問資料。但能夠注意到引入js指令碼的`
客戶端頁面程式碼如下
服務端需要拼接的回撥函式及返回資料
注意:這裡用callback引數欄位將客戶端需要執行的回撥函式名傳給服務端,服務端只需在封裝好json資料後,根據該欄位值動態建立同名的回撥函式即可。
DOM
文件物件模型(document object model)是用來表示和操作HTML和XML文件內容的基礎API。Document型別代表了一個HTML或XML文件,document物件則是用來儲存整個web頁面的dom結構,在頁面上所有的元素最終都會對映為一個dom物件。對頁面節點的操作也是通過document物件中的方法來實現的。
document物件中常用的Dom操作方法有:getElementById();getElementsByClassName();querySelector();getAttribute();等等。
BOM
瀏覽器物件模型(browser object model)是用於和瀏覽器視窗進行互動的物件,也可用於視窗與視窗之間的通訊。它的核心物件是window,在window物件當中也提供了很多其他物件屬性用於操作和管理瀏覽器的各個部分。常用的window物件屬性有:
Location物件:表示該視窗中當前顯示的文件的URL.。
History物件:用於將視窗的歷史瀏覽記錄用文件和文件狀態列表的形式表示。
Navigator物件:該物件包含了瀏覽器廠商和版本資訊。
Screen物件:它提供了有關視窗顯示大小和可用的顏色數量資訊。
常用的對話方塊也屬於掛載在window物件上的方法:alert(); confirm(); prompt();
綜上所述的每一個核心概念,都能單獨拎出來寫一本詳細的攻略祕籍,在這裡筆者也僅僅只是輕描淡寫了一番,目的是為了能夠有助於回憶及整理那些最原始最基礎的知識概念,同時也一直非常贊同那句話:基礎的牢固與否決定未來的路能走多遠。非常感謝您的閱讀,希望共勉!
參考文獻:
《JavaScript權威指南》