您好,登錄后才能下訂單哦!
這篇文章主要介紹“有關(guān)前端基礎(chǔ)知識整理匯總”,在日常操作中,相信很多人在有關(guān)前端基礎(chǔ)知識整理匯總問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”有關(guān)前端基礎(chǔ)知識整理匯總”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
HTML頁面的生命周期
HTML頁面的生命周期有以下三個重要事件:
DOMContentLoaded —— 瀏覽器已經(jīng)完全加載了 HTML,DOM 樹已經(jīng)構(gòu)建完畢,但是像是
沒有 defer 或 async,所有<script>元素會按照在頁面出現(xiàn)的先后順序依次被解析,瀏覽器會立即加載并執(zhí)行指定的腳本, 只有解析完前面的script元素的內(nèi)容后,才會解析后面的代碼。
async 和 defer 屬性僅僅對外部腳本起作用,在 src 不存在時會被自動忽略。
使用<script>的兩種方式
1.頁面中嵌入script代碼, 只需指定type屬性
<script type="text/javascript"> function sayHi() { console.log('hihihi'); // 內(nèi)部不能出現(xiàn)'</script>'字符串,如果必須出現(xiàn),必須使用轉(zhuǎn)義標(biāo)簽‘\’ alert('<\/script>'); } </script>
包含在<script>元素內(nèi)的代碼會從上而下依次解釋,在解釋器對<script>元素內(nèi)的所有代碼求值完畢之前,頁面中的其余內(nèi)容都不會被瀏覽器加載或顯示
2.包含外部js文件, src屬性是必須的。
<script src="example.js"></script> // 帶有src屬性的元素不應(yīng)該在標(biāo)簽之間包含額外的js代碼,即使包含,只會下載并執(zhí)行外部文件,內(nèi)部代碼也會被忽略。
與嵌入式j(luò)s代碼一樣, 在解析外部js文件時,頁面的處理會暫時停止。
改變腳本行為的方法
1. defer: 立即下載,延遲執(zhí)行
加載和渲染后續(xù)文檔元素的過程將和腳本的加載并行進(jìn)行(異步),但是腳本的執(zhí)行會在所有元素解析完成之后。腳本總會按照聲明順序執(zhí)行。
在DOMContentLoaded事件之前執(zhí)行。
<script defer="defer" src="example.js"></script>
2. async: 異步腳本
加載和渲染后續(xù)文檔元素的過程將和腳本的加載與執(zhí)行并行進(jìn)行(異步)。但是async 在下載完畢后的執(zhí)行會阻塞HTML的解析。腳本加載后馬上執(zhí)行,不能保證異步腳本按照他們在頁面中出現(xiàn)的順序執(zhí)行。
一定會在load事件之前執(zhí)行,可能會在DOMContentLoaded事件之前或之后執(zhí)行。
<script async="async" src="example.js"></script>
區(qū)別:
meta
META標(biāo)簽是HTML標(biāo)記HEAD區(qū)的一個關(guān)鍵標(biāo)簽,它提供的信息雖然用戶不可見,但卻是文檔的最基本的元信息。<meta> 除了提供文檔字符集、使用語言、作者等網(wǎng)頁相關(guān)信息外,還可以設(shè)置信息給搜索引擎,目的是為了SEO(搜索引擎優(yōu)化)。
HTML<meta> 元素表示那些不能由其它 HTML 元相關(guān)(meta-related)元素((<base>、<link>, <script>、<style> 或 <title>)之一表示的任何元數(shù)據(jù)信息。
屬性
name
設(shè)置元數(shù)據(jù)的名稱。name 和 content 屬性可以一起使用,以名-值對的方式給文檔提供元數(shù)據(jù),content 作為元數(shù)據(jù)的值。
content
設(shè)置與 http-equiv 或 name 屬性相關(guān)的元信息。
charset
聲明了文檔的字符編碼。如果使用了這個屬性,其值必須是與ASCII大小寫無關(guān)(ASCII case-insensitive)的"utf-8"。
http-equiv
定義了一個編譯指示指令,其作用類似于http協(xié)議, 告訴瀏覽器一些關(guān)于字符設(shè)定,頁面刷新,cookie,緩存等等相關(guān)信息。屬性名叫做 http-equiv 是因為所有允許的值都是HTTP頭部的名稱??稍O(shè)置的值有:
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
content-security-policy:它允許頁面作者定義當(dāng)前頁的內(nèi)容策略。內(nèi)容策略主要指定允許的服務(wù)器源和腳本端點,這有助于防止跨站點腳本攻擊。
Expires:可以用于設(shè)定網(wǎng)頁的到期時間,一旦過期則必須到服務(wù)器上重新調(diào)用。content必須使用GMT時間格式;
content-type:如果使用這個屬性,其值必須是"text/html; charset=utf-8"。注意:該屬性只能用于 MIME type為 text/html 的文檔,不能用于MIME類型為XML的文檔。
default-style:設(shè)置默認(rèn)CSS 樣式表組的名稱。
refresh:定時讓網(wǎng)頁在指定的時間n內(nèi),刷新或跳轉(zhuǎn);
如果 content 只包含一個正整數(shù),則是n秒后, 頁面刷新。
如果 content 包含一個正整數(shù),并且后面跟著字符串 ';url=' 和一個合法的 URL,則是重定向到指定鏈接的時間間隔(秒)。
meta 元素定義的元數(shù)據(jù)的類型包括以下幾種:
如果設(shè)置了 name 屬性,meta 元素提供的是文檔級別(document-level)的元數(shù)據(jù),應(yīng)用于整個頁面。
如果設(shè)置了 http-equiv 屬性,meta 元素則是編譯指令,提供的信息與類似命名的HTTP頭部相同。
如果設(shè)置了 charset 屬性,meta 元素是一個字符集聲明,告訴文檔使用哪種字符編碼。
如果設(shè)置了 itemprop 屬性,meta 元素提供用戶定義的元數(shù)據(jù)。
注意: 全局屬性 name 在 元素中具有特殊的語義;另外, 在同一個 標(biāo)簽中,name, http-equiv 或者 charset 三者中任何一個屬性存在時,itemprop 屬性不能被使用。
使用
content值里有多個屬性通過,隔開,同時設(shè)置多個屬性。
/* name */ // 適配移動設(shè)備 <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" /> // 檢測html格式:禁止把數(shù)字轉(zhuǎn)化為撥號鏈接 <meta name="format-detection" content="telephone=no" /> /* charset */ <meta charset="utf-8"> /* http-equiv */ <meta http-equiv="refresh" content="3;url=https://www.mozilla.org"> <meta http-equiv="Expires" content="Mon,12 May 2001 00:20:00 GMT">
meta viewport元信息
什么是 viewport?
viewport 是瀏覽器的可視區(qū)域,可視區(qū)域的大小是瀏覽器自己設(shè)置的。它可能大于移動設(shè)備可視區(qū)域,也可能小于移動設(shè)備可視區(qū)域。一般來講,移動設(shè)備上的viewport都是大于移動設(shè)備可視區(qū)域。在控制臺輸出window.innerWidth查看Viewport大小。
相關(guān)概念
設(shè)備像素:設(shè)備屏幕分辨率。iphone6p 的分辨率是 1334*750;
設(shè)備獨立像素:設(shè)備上程序用來描繪數(shù)據(jù)的一個個的“點”, 在控制臺用 screen.width/height查看。iphone6p 的設(shè)備獨立像素是375*667;
設(shè)備像素比(DPR):設(shè)備像素(寬)/設(shè)備獨立像素(寬),DPR越高渲染越精致。在控制臺輸出window.devicePixelRatio查看設(shè)備像素比。iphone6s 的設(shè)備像素比就是 750 / 375 = 2;
CSS像素:瀏覽器使用的單位,用來精確度量網(wǎng)頁上的內(nèi)容。在一般情況下(頁面縮放比為 1),1 個 CSS 像素等于 1 個設(shè)備獨立像素。
屏幕尺寸:屏幕對角線的長度,以英尺為單位。
像素密度(PPI):每英寸屏幕擁有的像素數(shù)。
為什么要使用meta viewport?
通常情況下,移動設(shè)備上的瀏覽器都會把viewport設(shè)為980px或1024px,此時頁面會出現(xiàn)橫向滾動條,因為移動設(shè)備可視區(qū)域?qū)挾仁潜冗@個默認(rèn)的viewport的寬度要小。所以出現(xiàn)了meta 標(biāo)簽設(shè)置viewport 元始性進(jìn)行移動端網(wǎng)頁優(yōu)化。
meta viewport 屬性
width:控制 viewport 的大小,可以給它指定一個值(正整數(shù)),或者是一個特殊的值(如:device-width 設(shè)備獨立像素寬度,單位縮放為 1 時);
initial-scale:初始縮放比例,即當(dāng)頁面第一次加載時的縮放比例,為一個數(shù)字(可以帶小數(shù));
maximum-scale:允許用戶縮放到的最大比例,為一個數(shù)字(可以帶小數(shù));
minimum-scale:允許用戶縮放到的最小比例,為一個數(shù)字(可以帶小數(shù));
user-scalable:是否允許用戶手動縮放,值為 "no"(不允許) 或 "yes"(允許);
height:與 width 相對應(yīng)(很少使用)。
基本類型和引用類型
基本類型
基本類型:undefined、null、string、number、boolean、symbol
特點
1.基本類型的值是不可變得
// 任何方法都無法改變一個基本類型的值 let name = 'jay'; name.toUpperCase(); // 輸出 'JAY' console.log(name); // 輸出 'jay'
2.基本類型的比較是值的比較
// 只有在它們的值相等的時候它們才相等 let a = 1; let b = true; console.log(a == b); //true // 用==比較兩個不同類型的變量時會進(jìn)行一些類型轉(zhuǎn)換。 // 先會把true轉(zhuǎn)換為數(shù)字1再和數(shù)字1進(jìn)行比較,結(jié)果就是true了
3.基本類型的變量是存放在棧區(qū)的(棧區(qū)指內(nèi)存里的棧內(nèi)存)
引用類型
引用類型:Object、Array、RegExp、Date、Function等
引用類型也可以說是對象。對象是屬性和方法的集合,也就是說引用類型可以擁有屬性和方法,屬性又可以包含基本類型和引用類型。
特點
1.引用類型的值是可變的
// 我們可為為引用類型添加屬性和方法,也可以刪除其屬性和方法 let person = { name: 'pig' }; person.age = 22; person.sayName = () => console.log(person.name); person.sayName(); // 'pig' delete person.name;
2.引用類型的比較是引用的比較
let person1 = '{}'; let person2 = '{}'; console.log(person1 == person2); // 字符串值相同,true let person1 = {}; let person2 = {}; console.log(person1 == person2); // 兩個對象的堆內(nèi)存中的地址不同,false
3.引用類型的值是同時保存在棧內(nèi)存和堆內(nèi)存中的對象
javascript和其他語言不同,其不允許直接訪問內(nèi)存中的位置,也就是說不能直接操作對象的內(nèi)存空間。實際上,是操作對象的引用,所以引用類型的值是按引用訪問的。準(zhǔn)確地說,引用類型的存儲需要內(nèi)存的棧區(qū)和堆區(qū)(堆區(qū)是指內(nèi)存里的堆內(nèi)存)共同完成,棧區(qū)內(nèi)存保存變量標(biāo)識符和指向堆內(nèi)存中該對象的指針,也可以說是該對象在堆內(nèi)存的地址。
作用域和執(zhí)行上下文
JavaScript代碼的整個執(zhí)行過程,分為兩個階段,代碼編譯階段與代碼執(zhí)行階段。
編譯階段:由編譯器完成,將代碼翻譯成可執(zhí)行代碼。這個階段作用域規(guī)則會確定。
執(zhí)行階段:由引擎完成,主要任務(wù)是執(zhí)行可執(zhí)行代碼。執(zhí)行上下文在這個階段創(chuàng)建。
作用域
簡單來說作用域就是一個區(qū)域,沒有變量。作用域可以嵌套。作用域規(guī)定了如何查找變量,也就是確定當(dāng)前執(zhí)行代碼對變量的訪問權(quán)限。作用域在函數(shù)定義時就已經(jīng)確定了,不是在函數(shù)調(diào)用確定。
ES6 之前 JavaScript 只有全局作用域和函數(shù)作用域。ES6 后,增加了塊級作用域(最近大括號的作用范圍), 通過let 和 const 聲明的變量。
作用域其實由兩部分組成:
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
記錄作用域內(nèi)變量信息(假設(shè)變量,常量,函數(shù)等統(tǒng)稱為變量)和代碼結(jié)構(gòu)信息的東西,稱之為 Environment Record。
一個引用 __outer__,這個引用指向當(dāng)前作用域的父作用域。全局作用域的 __outer__ 為 null。
詞法作用域
JavaScript 采用詞法作用域(lexical scoping),也就是靜態(tài)作用域。
所謂詞法(代碼)作用域,就是代碼在編寫過程中體現(xiàn)出來的作用范圍,代碼一旦寫好了,沒有運(yùn)行之前(不用執(zhí)行),作用范圍就已經(jīng)確定好了,這個就是所謂的詞法作用域。
詞法作用域的規(guī)則:
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
函數(shù)允許訪問函數(shù)外部的數(shù)據(jù)
整個代碼結(jié)構(gòu)中只有函數(shù)才能限定作用域
作用規(guī)則首先使用變量提升規(guī)則分析
如果當(dāng)前作用規(guī)則里面有該名字,則不考慮外面的外面的名字
var a = 1; function out() { var a = 2; inner(); } function inner() { console.log(a) } out(); //====> 1
作用域鏈
當(dāng)查找變量的時候,會先從當(dāng)前上下文的變量對象中查找,如果沒有找到,就會從父級(詞法層面上的父級)執(zhí)行上下文的變量對象中查找,一直找到全局上下文的變量對象,也就是全局對象。這樣由多個執(zhí)行上下文的變量對象構(gòu)成的指針鏈表就叫做作用域鏈。
作用域鏈本質(zhì)上是一個指向當(dāng)前環(huán)境與上層環(huán)境的一系列變量對象的指針列表(它只引用但不實際包含變量對象),作用域鏈保證了當(dāng)前執(zhí)行環(huán)境對符合訪問權(quán)限的變量和函數(shù)的有序訪問。
例子:
用一個數(shù)組scopeChain來表示作用域鏈,數(shù)組的第一項scopeChain[0]為作用域鏈的最前端,而數(shù)組的最后一項,為作用域鏈的最末端,所有的最末端都為全局變量對象。
var a = 1; function out() { var b = 2; function inner() { var c = 3; console.log(a + b + c); } inner(); } out();
首先,代碼開始運(yùn)行時就創(chuàng)建了全局上下文環(huán)境,接著運(yùn)行到out()時創(chuàng)建 out函數(shù)的執(zhí)行上下文,最后運(yùn)行到inner()時創(chuàng)建 inner函數(shù)的執(zhí)行上下文,我們設(shè)定他們的變量對象分別為VO(global),VO(out), VO(inner)。
當(dāng)函數(shù)創(chuàng)建時,執(zhí)行上下文為:
// 全局上下文環(huán)境 globalEC = { VO: { out: <out reference>, // 表示 out 的地址引用 a: undefined }, scopeChain: [VO(global)], // 作用域鏈 } // out 函數(shù)的執(zhí)行上下文 outEC = { VO: { arguments: {...}, inner: <inner reference>, // 表示 inner 的地址引用 b: undefined }, scopeChain: [VO(out), VO(global)], // 作用域鏈 } // inner 函數(shù)的執(zhí)行上下文 innerEC = { VO: { arguments: {...}, c: undefined, }, scopeChain: [VO(inner), VO(out), VO(global)], // 作用域鏈 }
執(zhí)行上下文
簡單來說,當(dāng)在代碼執(zhí)行階段執(zhí)行到一個函數(shù)的時候,就會進(jìn)行準(zhǔn)備工作,這里的“準(zhǔn)備工作”,就叫做"執(zhí)行上下文(EC)",也叫執(zhí)行上下文環(huán)境,也叫執(zhí)行環(huán)境。js引擎創(chuàng)建了執(zhí)行上下文棧(Execution context stack,ECS)來管理執(zhí)行上下文。
當(dāng)調(diào)用一個函數(shù)時,一個新的執(zhí)行上下文就會被創(chuàng)建。而一個執(zhí)行上下文的生命周期可以分為兩個階段:
創(chuàng)建階段:在這個階段,執(zhí)行上下文會分別創(chuàng)建變量對象,建立作用域鏈,以及確定this的指向。
代碼執(zhí)行階段:開始執(zhí)行代碼,會完成變量賦值,函數(shù)引用,以及執(zhí)行其他代碼。
特點
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
處于活動狀態(tài)的執(zhí)行上下文環(huán)境只有一個, 只有棧頂?shù)纳舷挛奶幱诨顒訝顟B(tài),執(zhí)行其中的代碼。
函數(shù)每調(diào)用一次,都會產(chǎn)生一個新的執(zhí)行上下文環(huán)境。
全局上下文在代碼開始執(zhí)行時就創(chuàng)建,只有唯一的一個,永遠(yuǎn)在棧底,瀏覽器窗口關(guān)閉時出棧。
函數(shù)被調(diào)用的時候創(chuàng)建上下文環(huán)境。
變量對象
變量對象的創(chuàng)建過程
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
建立arguments對象。檢查當(dāng)前上下文中的參數(shù),建立該對象下的屬性與屬性值。
檢查當(dāng)前上下文的函數(shù)聲明,也就是使用function關(guān)鍵字聲明的函數(shù)。在變量對象中以函數(shù)名建立一個屬性,屬性值為指向該函數(shù)所在內(nèi)存地址的引用。如果函數(shù)名的屬性已經(jīng)存在,那么該屬性將會被新的引用所覆蓋。
檢查當(dāng)前上下文中的變量聲明,每找到一個變量聲明,就在變量對象中以變量名建立一個屬性,屬性值為undefined。如果該變量名的屬性已經(jīng)存在,為了防止同名的函數(shù)被修改為undefined,則會直接跳過,原屬性值不會被修改。
活動對象
變量對象與活動對象其實都是同一個對象,只是處于執(zhí)行上下文的不同生命周期。不過只有處于函數(shù)調(diào)用棧棧頂?shù)膱?zhí)行上下文中的變量對象,才會變成活動對象。
執(zhí)行上下文棧
執(zhí)行上下文可以理解為當(dāng)前代碼的執(zhí)行環(huán)境,JavaScript中的運(yùn)行環(huán)境大概包括三種情況:
全局環(huán)境:JavaScript代碼運(yùn)行起來會首先進(jìn)入該環(huán)境
函數(shù)環(huán)境:當(dāng)函數(shù)被調(diào)用執(zhí)行時,會進(jìn)入當(dāng)前函數(shù)中執(zhí)行代碼
eval
在代碼開始執(zhí)行時,首先會產(chǎn)生一個全局執(zhí)行上下文環(huán)境,調(diào)用函數(shù)時,會產(chǎn)生函數(shù)執(zhí)行上下文環(huán)境,函數(shù)調(diào)用完成后,它的執(zhí)行上下文環(huán)境以及其中的數(shù)據(jù)都會被銷毀,重新回到全局執(zhí)行環(huán)境,網(wǎng)頁關(guān)閉后全局執(zhí)行環(huán)境也會銷毀。其實這是一個壓棧出棧的過程,全局上下文環(huán)境永遠(yuǎn)在棧底,而當(dāng)前正在執(zhí)行的函數(shù)上下文在棧頂。
var a = 1; // 1.進(jìn)入全局上下文環(huán)境 function out() { var b = 2; function inner() { var c = 3; console.log(a + b + c); } inner(); // 3. 進(jìn)入inner函數(shù)上下文環(huán)境 } out(); // 2. 進(jìn)入out函數(shù)上下文環(huán)境
以上代碼的執(zhí)行會經(jīng)歷以下過程:
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
當(dāng)代碼開始執(zhí)行時就創(chuàng)建全局執(zhí)行上下文環(huán)境,全局上下文入棧。
全局上下文入棧后,其中的代碼開始執(zhí)行,進(jìn)行賦值、函數(shù)調(diào)用等操作,執(zhí)行到out()時,激活函數(shù)out創(chuàng)建自己的執(zhí)行上下文環(huán)境,out函數(shù)上下文入棧。
out函數(shù)上下文入棧后,其中的代碼開始執(zhí)行,進(jìn)行賦值、函數(shù)調(diào)用等操作,執(zhí)行到inner()時,激活函數(shù)inner創(chuàng)建自己的執(zhí)行上下文環(huán)境,inner函數(shù)上下文入棧。
inner函數(shù)上下文入棧后,其中的代碼開始執(zhí)行,進(jìn)行賦值、函數(shù)調(diào)用、打印等操作,由于里面沒有可以生成其他執(zhí)行上下文的需要,所有代碼執(zhí)行完畢后,inner函數(shù)上下文出棧。
inner函數(shù)上下文出棧,又回到了out函數(shù)執(zhí)行上下文環(huán)境,接著執(zhí)行out函數(shù)中后面剩下的代碼,由于后面沒有可以生成其他執(zhí)行上下文的需要,所有代碼執(zhí)行完畢后,out函數(shù)上下文出棧。
out函數(shù)上下文出棧后,又回到了全局執(zhí)行上下文環(huán)境,直到瀏覽器窗口關(guān)閉,全局上下文出棧。
作用域與執(zhí)行上下文區(qū)別
作用域只是一個“地盤”,其中沒有變量。變量是通過作用域?qū)?yīng)的執(zhí)行上下文環(huán)境中的變量對象來實現(xiàn)的。所以作用域是靜態(tài)觀念的,而執(zhí)行上下文環(huán)境是動態(tài)上的。有閉包存在時,一個作用域存在兩個上下文環(huán)境也是有的。
同一個作用域下,對同一個函數(shù)的不同的調(diào)用會產(chǎn)生不同的執(zhí)行上下文環(huán)境,繼而產(chǎn)生不同的變量的值,所以,作用域中變量的值是在執(zhí)行過程中確定的,而作用域是在函數(shù)創(chuàng)建時就確定的。
如果要查找一個作用域下某個變量的值,就需要找到這個作用域?qū)?yīng)的執(zhí)行上下文環(huán)境,再在其中找到變量的值。
變量提升
在Javascript中,函數(shù)及變量的聲明都將被提升到函數(shù)的最頂部,提升的僅僅是變量的聲明,變量的賦值并不會被提升。函數(shù)的聲明與變量的聲明是不一樣的,函數(shù)表達(dá)式和變量表達(dá)式只是其聲明被提升,函數(shù)聲明是函數(shù)的聲明和實現(xiàn)都被提升。
function foo() { console.log("global foo"); } function bar() { console.log("global bar"); } //定義全局變量 var v = "global var"; function hoistMe() { // var bar; 被提升到頂部,并未實現(xiàn) // var v; console.log(typeof foo); //function console.log(typeof bar); //undefined console.log(v); //undefined // 函數(shù)里面定義了同名的函數(shù)和變量,無論在函數(shù)的任何位置定義這些函數(shù)和和變量,它們都將被提升到函數(shù)的最頂部。 foo(); //local foo bar(); //報錯,TypeError "bar is not a function" //函數(shù)聲明,變量foo以及其實現(xiàn)被提升到hoistMe函數(shù)頂部 function foo() { alert("local foo"); } //函數(shù)表達(dá)式,僅變量bar被提升到函數(shù)頂部,實現(xiàn)沒有被提升 var bar = function() { alert("local bar"); }; //定義局部變量 var v = "local"; }
let 變量提升
console.log(a); // Uncaught ReferenceError: a is not defined let a = "I am a"; let b = "I am outside B"; if(true){ console.log(b); // Uncaught ReferenceError: b is not defined let b = " I am inside B"; }
如果b沒有變量提升,執(zhí)行到console.log時應(yīng)該是輸出全局作用域中的b,而不是出現(xiàn)錯誤。
我們可以推知,這里確實出現(xiàn)了變量提升,而我們不能夠訪問的原因事實上是因為let的死區(qū)設(shè)計:當(dāng)前作用域頂部到該變量聲明位置中間的部分,都是該let變量的死區(qū),在死區(qū)中,禁止訪問該變量。由此,我們給出結(jié)論,let聲明的變量存在變量提升, 但是由于死區(qū)我們無法在聲明前訪問這個變量。
var let 區(qū)別
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
var聲明的變量,只有函數(shù)才能為它創(chuàng)建新的作用域;
let聲明的變量,支持塊級作用域,花括號就能為它創(chuàng)建新的作用域;
相同作用域,var可以反復(fù)聲明相同標(biāo)識符的變量,而let是不允許的;
let聲明的變量禁止在聲明前訪問
// 全局變量 var i = 0 ; // 定義外部函數(shù) function outer(){ // 訪問全局變量 console.log(i); // 0 function inner1(){ console.log(i); // 0 } function inner2(){ console.log(i); // undefined var i = 1; console.log(i); // 1 } inner1(); inner2(); console.log(i); // 0 }
閉包
閉包就是指有權(quán)訪問另一個函數(shù)作用域中的變量的函數(shù)。
官方解釋:閉包是由函數(shù)以及創(chuàng)建該函數(shù)的詞法環(huán)境組合而成。這個環(huán)境包含了這個閉包創(chuàng)建時所能訪問的所有局部變量。(詞法作用域)
通俗解釋:閉包的關(guān)鍵在于:外部函數(shù)調(diào)用之后其變量對象本應(yīng)該被銷毀,但閉包的存在使我們?nèi)匀豢梢栽L問外部函數(shù)的變量對象。
當(dāng)某個函數(shù)被掉用的時候,會創(chuàng)建一個執(zhí)行環(huán)境及相應(yīng)的作用域鏈。然后使用arguments和其他命名參數(shù)的值來初始化函數(shù)的活動對象。但在作用域鏈中,外部函數(shù)的活動對象始終處于第二位,外部函數(shù)的外部函數(shù)的活動對象處于第三位...直至作為作用域鏈終點的全局執(zhí)行環(huán)境。
作用域鏈本質(zhì)上是一個指向變量對象的指針列表,他只引用但不實際包含變量對象。
無論什么時候在函數(shù)中訪問一個變量時,就會從作用域鏈中搜索具有相同名字的變量,一般來講,當(dāng)函數(shù)執(zhí)行完畢,局部活動對象就會被銷毀,內(nèi)存中僅保存全部作用域的活動對象。但是,閉包不同。
創(chuàng)建閉包: 在一個函數(shù)內(nèi)部創(chuàng)建另一個函數(shù)
function add() { let a = 1; let b = 3; function closure() { b++; return a + b; } return closure; } // 閉包的作用域鏈包含著它自己的作用域,以及包含它的函數(shù)的作用域和全局作用域。
生命周期
通常,函數(shù)的作用域及其所有變量都會在函數(shù)執(zhí)行結(jié)束后被銷毀。但是,在創(chuàng)建了一個閉包以后,這個函數(shù)的作用域就會一直保存到閉包不存在為止。
當(dāng)閉包中的函數(shù)closure從add中返回后,它的作用域鏈被初始化為包含add函數(shù)的活動對象和全局變量對象。這樣closure就可以訪問在add中定義的所有變量。
更重要的是,add函數(shù)在執(zhí)行完畢后,也不會銷毀,因為closure函數(shù)的作用域鏈仍然在引用這個活動對象。
換句話說,當(dāng)add返回后,其執(zhí)行環(huán)境的作用域鏈被銷毀,但它的活動對象仍然在內(nèi)存中,直至closure被銷毀。
function add(x) { function closure(y) { return x + y; } return closure; } let add2 = add(2); let add5 = add(5); // add2 和 add5 共享相同的函數(shù)定義,但是保存了不同的環(huán)境 // 在add2的環(huán)境中,x為5。而在add5中,x則為10 console.log(add2(3)); // 5 console.log(add5(10)); // 15 // 釋放閉包的引用 add2 = null; add5 = null;
閉包中的this對象
var name = 'window'; var obj = { name: 'object', getName: () => { return () => { return this.name; } } } console.log(obj.getName()()); // window
obj.getName()()是在全局作用域中調(diào)用了匿名函數(shù),this指向了window。
函數(shù)名與函數(shù)功能是分割開的,不要認(rèn)為函數(shù)在哪里,其內(nèi)部的this就指向哪里。
window才是匿名函數(shù)功能執(zhí)行的環(huán)境。
使用注意點
1)由于閉包會讓包含函數(shù)中的變量都被保存在內(nèi)存中,內(nèi)存消耗很大,所以不能濫用閉包,否則會造成網(wǎng)頁的性能問題,在IE中可能導(dǎo)致內(nèi)存泄露。解決方法是,在退出函數(shù)之前,將不使用的局部變量全部刪除。
2)閉包會在父函數(shù)外部,改變父函數(shù)內(nèi)部變量的值。所以,如果你把父函數(shù)當(dāng)作對象(object)使用,把閉包當(dāng)作它的公用方法(Public Method),把內(nèi)部變量當(dāng)作它的私有屬性(private value),這時一定要小心,不要隨便改變父函數(shù)內(nèi)部變量的值。
使用
模仿塊級作用域
私有變量
模塊模式
在循環(huán)中創(chuàng)建閉包:一個常見錯誤
function show(i) { console.log(i); } function showCallback(i) { return () => { show(i); }; } // 測試1【3,3,3】 const testFunc1 = () => { // var i; for (var i = 0; i < 3; i++) { setTimeout(() => show(i), 300); } } // 測試2 【0,1,2】 const testFunc2 = () => { for (var i = 0; i < 3; i++) { setTimeout(showCallback(i), 300); } } // 測試3【0,1, 2】 閉包,立即執(zhí)行函數(shù) // 在閉包函數(shù)內(nèi)部形成了局部作用域,每循環(huán)一次,形成一個自己的局部作用域 const testFunc3 = () => { for (var i = 0; i < 3; i++) { (() => { setTimeout(() => show(i), 300); })(i); } } // 測試4【0,1, 2】let const testFunc4 = () => { for (let i = 0; i < 3; i++) { setTimeout(() => show(i), 300); } }
setTimeout()函數(shù)回調(diào)屬于異步任務(wù),會出現(xiàn)在宏任務(wù)隊列中,被壓到了任務(wù)隊列的最后,在這段代碼應(yīng)該是for循環(huán)這個同步任務(wù)執(zhí)行完成后才會輪到它
測試1錯誤原因:賦值給 setTimeout 的是閉包。這些閉包是由他們的函數(shù)定義和在 testFunc1 作用域中捕獲的環(huán)境所組成的。這三個閉包在循環(huán)中被創(chuàng)建,但他們共享了同一個詞法作用域,在這個作用域中存在一個變量i。這是因為變量i使用var進(jìn)行聲明,由于變量提升,所以具有函數(shù)作用域。當(dāng)onfocus的回調(diào)執(zhí)行時,i的值被決定。由于循環(huán)在事件觸發(fā)之前早已執(zhí)行完畢,變量對象i(被三個閉包所共享)已經(jīng)指向了i的最后一個值。
測試2正確原因: 所有的回調(diào)不再共享同一個環(huán)境, showCallback 函數(shù)為每一個回調(diào)創(chuàng)建一個新的詞法環(huán)境。在這些環(huán)境中,i 指向數(shù)組中對應(yīng)的下標(biāo)。
測試4正確原因:JS中的for循環(huán)體比較特殊,每次執(zhí)行都是一個全新的獨立的塊作用域,用let聲明的變量傳入到 for循環(huán)體的作用域后,不會發(fā)生改變,不受外界的影響。
this指向問題
this 就是一個指針,指向我們調(diào)用函數(shù)的對象。
執(zhí)行上下文: 是語言規(guī)范中的一個概念,用通俗的話講,大致等同于函數(shù)的執(zhí)行“環(huán)境”。具體的有:變量作用域(和 作用域鏈條,閉包里面來自外部作用域的變量),函數(shù)參數(shù),以及 this 對象的值。
找出 this 的指向
this 的值并不是由函數(shù)定義放在哪個對象里面決定,而是函數(shù)執(zhí)行時由誰來喚起決定。
var name = "Jay Global"; var person = { name: 'Jay Person', details: { name: 'Jay Details', print: function() { return this.name; } }, print: function() { return this.name; } }; console.log(person.details.print()); // 【details對象調(diào)用的print】Jay Details console.log(person.print()); // 【person對象調(diào)用的print】Jay Person var name1 = person.print; var name2 = person.details; console.log(name1()); // 【name1前面沒有調(diào)用對象,所以是window】Jay Global console.log(name2.print()) // 【name2對象調(diào)用的print】Jay Details
this和箭頭函數(shù)
箭頭函數(shù)按詞法作用域來綁定它的上下文,所以 this 實際上會引用到原來的上下文。箭頭函數(shù)保持它當(dāng)前執(zhí)行上下文的詞法作用域不變,而普通函數(shù)則不會。換句話說,箭頭函數(shù)從包含它的詞法作用域中繼承到了 this 的值。
匿名函數(shù),它不會作為某個對象的方法被調(diào)用, 因此,this 關(guān)鍵詞指向了全局 window 對象
var object = { data: [1,2,3], dataDouble: [1,2,3], double: function() { console.log(this); // object return this.data.map(function(item) { // this是當(dāng)前object,object調(diào)用的double console.log(this); // 傳給map()的那個匿名函數(shù)沒有被任一對象調(diào)用,所以是window return item * 2; }); }, doubleArrow: function() { console.log(this); // object return this.dataDouble.map(item => { // this是當(dāng)前object,object調(diào)用的doubleArrow console.log(this); // doubleArrow是object調(diào)用的,這就是上下文,所以是window return item * 2; }); } }; object.double(); object.doubleArrow();
明確設(shè)置執(zhí)行上下文
在 JavaScript 中通過使用內(nèi)置的特性開發(fā)者就可以直接操作執(zhí)行上下文了。這些特性包括:
bind():不需要執(zhí)行函數(shù)就可以將 this 的值準(zhǔn)確設(shè)置到你選擇的一個對象上。通過逗號隔開傳遞多個參數(shù)。設(shè)置好 this 關(guān)鍵詞后不會立刻執(zhí)行函數(shù)。
apply():將 this 的值準(zhǔn)確設(shè)置到你選擇的一個對象上。apply(thisObj, argArray)接收兩個參數(shù),thisObj是函數(shù)運(yùn)行的作用域(this),argArray是參數(shù)數(shù)組,數(shù)組的每一項是你希望傳遞給函數(shù)的參數(shù)。如果沒有提供argArray和thisObj任何一個參數(shù),那么Global對象將用作thisObj。最后,會立刻執(zhí)行函數(shù)。
call():將 this 的值準(zhǔn)確設(shè)置到你選擇的一個對象上。然后像bind 一樣通過逗號分隔傳遞多個參數(shù)給函數(shù)。語法:call(thisObj,arg1,arg2,..., argn);,如果沒有提供thisObj參數(shù),那么Global對象被用于thisObj。最后,會立刻執(zhí)行函數(shù)。
this 和 bind
var bobObj = { name: "Bob" }; function print() { return this.name; } var printNameBob = print.bind(bobObj); console.log(printNameBob()); // Bob
this 和 call
function add(a, b) { return a + b; } function sum() { return Array.prototype.reduce.call(arguments, add); } console.log(sum(1,2,3,4)); // 10
this 和 apply
apply 就是接受數(shù)組版本的call。
Math.min(1,2,3,4); // 返回 1 Math.min([1,2,3,4]); // 返回 NaN。只接受數(shù)字 Math.min.apply(null, [1,2,3,4]); // 返回 1 function Person(name, age){ this.name = name; this.age = age; } function Student(name, age, grade) { Person.apply(this, arguments); //Person.call(this, name, age); this.grade = grade; } var student = new Student("sansan", 21, "一年級"); console.log("student:", student); // {name: 'sansan'; age: '21', grade: '一年級'}
如果你的參數(shù)本來就存在一個數(shù)組中,那自然就用 apply,如果參數(shù)比較散亂相互之間沒什么關(guān)聯(lián),就用 call。
對象屬性類型
數(shù)據(jù)屬性
數(shù)據(jù)屬性包含一個數(shù)據(jù)值的位置,在這個位置可以讀取和寫入值,數(shù)據(jù)屬性有4個描述其行為的特性:
Configurable: 表示是否通過delete刪除屬性從而重新定義屬性,能否修改屬性的特性,或者能否把屬性修改為訪問器屬性。默認(rèn)值是true
Enumerable: 表示能否通過for-in循環(huán)返回屬性。默認(rèn)值是true
Writable: 表述能否修改屬性。默認(rèn)值是true
Value: 包含這個屬性的數(shù)據(jù)值。默認(rèn)值是true
訪問器屬性
函數(shù)式編程
函數(shù)式編程是一種編程范式,是一種構(gòu)建計算機(jī)程序結(jié)構(gòu)和元素的風(fēng)格,它把計算看作是對數(shù)學(xué)函數(shù)的評估,避免了狀態(tài)的變化和數(shù)據(jù)的可變。
純函數(shù)
純函數(shù)是穩(wěn)定的、一致的和可預(yù)測的。給定相同的參數(shù),純函數(shù)總是返回相同的結(jié)果。
特性
1. 如果給定相同的參數(shù),則得到相同的結(jié)果
我們想要實現(xiàn)一個計算圓的面積的函數(shù)。
不是純函數(shù)會這樣做:
let PI = 3.14; const calculateArea = (radius) => radius * radius * PI; // 它使用了一個沒有作為參數(shù)傳遞給函數(shù)的全局對象 calculateArea(10); // returns 314.0
純函數(shù):
let PI = 3.14; const calculateArea = (radius, pi) => radius * radius * pi; // 現(xiàn)在把 PI 的值作為參數(shù)傳遞給函數(shù),這樣就沒有外部對象引入。 calculateArea(10, PI); // returns 314.0
2. 無明顯副作用
純函數(shù)不會引起任何可觀察到的副作用??梢姼弊饔玫睦影ㄐ薷娜謱ο蠡蛲ㄟ^引用傳遞的參數(shù)。
現(xiàn)在,實現(xiàn)一個函數(shù),接收一個整數(shù)并返對該整數(shù)進(jìn)行加1操作且返回:
let counter = 1; function increaseCounter(value) { counter = value + 1; } increaseCounter(counter); console.log(counter); // 2
該非純函數(shù)接收該值并重新分配counter,使其值增加1。
函數(shù)式編程不鼓勵可變性(修改全局對象)。
let counter = 1; const increaseCounter = (value) => value + 1; // 函數(shù)返回遞增的值,而不改變變量的值 increaseCounter(counter); // 2 console.log(counter); // 1
3. 引用透明性
如果一個函數(shù)對于相同的輸入始終產(chǎn)生相同的結(jié)果,那么它可以看作透明的。
實現(xiàn)一個square 函數(shù):
const square = (n) => n * n; square(2); // 4 將2作為square函數(shù)的參數(shù)傳遞始終會返回4
可以把square(2)換成4,我們的函數(shù)就是引用透明的。
純函數(shù)使用
單元測試
純函數(shù)代碼肯定更容易測試,不需要 mock 任何東西。因此我們可以使用不同的上下文對純函數(shù)進(jìn)行單元測試。
一個簡單的例子是接收一組數(shù)字,并對每個數(shù)進(jìn)行加 1 :
let list = [1, 2, 3, 4, 5]; const incrementNumbers = (list) => list.map(number => number + 1); incrementNumbers(list); // [2, 3, 4, 5, 6]
對于輸入[1,2,3,4,5],預(yù)期輸出是[2,3,4,5,6]。
純函數(shù)也可以被看作成值并用作數(shù)據(jù)使用
從常量和變量中引用它。
將其作為參數(shù)傳遞給其他函數(shù)。
作為其他函數(shù)的結(jié)果返回它。
其思想是將函數(shù)視為值,并將函數(shù)作為數(shù)據(jù)傳遞。通過這種方式,我們可以組合不同的函數(shù)來創(chuàng)建具有新行為的新函數(shù)。
假如我們有一個函數(shù),它對兩個值求和,然后將值加倍,如下所示:
const doubleSum = (a, b) => (a + b) * 2;
對應(yīng)兩個值求差,然后將值加倍:
const doubleSubtraction = (a, b) => (a - b) * 2
這些函數(shù)具有相似的邏輯,但區(qū)別在于運(yùn)算符的功能。如果我們可以將函數(shù)視為值并將它們作為參數(shù)傳遞,我們可以構(gòu)建一個接收運(yùn)算符函數(shù)并在函數(shù)內(nèi)部使用它的函數(shù)。
const sum = (a, b) => a + b; const subtraction = (a, b) => a - b; const doubleOperator = (f, a, b) => f(a, b) * 2; doubleOperator(sum, 3, 1); // 8 doubleOperator(subtraction, 3, 1); // 4
Promise
Promise 必須為以下三種狀態(tài)之一:等待態(tài)(Pending)、執(zhí)行態(tài)(Fulfilled)和拒絕態(tài)(Rejected)。一旦Promise 被 resolve 或 reject,不能再遷移至其他任何狀態(tài)(即狀態(tài) immutable)。
基本過程:
初始化 Promise 狀態(tài)(pending)
執(zhí)行 then(..) 注冊回調(diào)處理數(shù)組(then 方法可被同一個 promise 調(diào)用多次)
立即執(zhí)行 Promise 中傳入的 fn 函數(shù),將Promise 內(nèi)部 resolve、reject 函數(shù)作為參數(shù)傳遞給 fn ,按事件機(jī)制時機(jī)處理
Promise中要保證,then方法傳入的參數(shù) onFulfilled 和 onRejected,必須在then方法被調(diào)用的那一輪事件循環(huán)之后的新執(zhí)行棧中執(zhí)行。
真正的鏈?zhǔn)絇romise是指在當(dāng)前promise達(dá)到fulfilled狀態(tài)后,即開始進(jìn)行下一個promise.
跨域
因為瀏覽器的同源策略導(dǎo)致了跨域。同源策略是一個重要的安全策略,它用于限制一個origin的文檔或者它加載的腳本如何能與另一個源的資源進(jìn)行交互。它能幫助阻隔惡意文檔,減少可能被攻擊的媒介。
所謂同源是指"協(xié)議+域名+端口"三者相同。不同協(xié)議,不同域名,不同端口都會構(gòu)成跨域。
跨域解決方案
1. jsonp: 需要服務(wù)器配合一個callback函數(shù)
2. CORS: 需要服務(wù)器設(shè)置header :Access-Control-Allow-Origin
3. window.name + iframe: 需要目標(biāo)服務(wù)器響應(yīng)window.name。
4. document.domain : 僅限主域相同,子域不同的跨域應(yīng)用場景。
5. html5的 postMessage + iframe: 需要服務(wù)器或者目標(biāo)頁面寫一個postMessage,主要側(cè)重于前端通訊。
6. nginx反向代理: 不用服務(wù)器配合,需要搭建一個中轉(zhuǎn)nginx服務(wù)器,用于轉(zhuǎn)發(fā)請求。
jsonp跨域
在HTML標(biāo)簽里,一些標(biāo)簽比如script、img這樣的獲取資源的標(biāo)簽是沒有跨域限制的。通過動態(tài)創(chuàng)建script,再請求一個帶參網(wǎng)址實現(xiàn)跨域通信。
需要前后端配合使用。一般后端設(shè)置callback ,前端給后臺接口中傳一個callback 即可。
只能實現(xiàn)get一種請求。
栗子
前端代碼:
<script> var script = document.createElement('script'); script.type = 'text/javascript'; // 傳參一個回調(diào)函數(shù)名給后端,方便后端返回時執(zhí)行這個在前端定義的回調(diào)函數(shù) script.src = 'http://xxxxxxx:8080/login?callback=handleCallback'; document.head.appendChild(script); function handleCallback(res) { alert(JSON.stringify(res)); } </script>
后臺代碼:
<?php $callback = $_GET['callback'];//得到回調(diào)函數(shù)名 $data = array('a','b','c');//要返回的數(shù)據(jù) echo $callback.'('.json_encode($data).')';//輸出 ?>
CORS - 跨域資源共享
CORS是一個W3C標(biāo)準(zhǔn),全稱是"跨域資源共享"(Cross-origin resource sharing)。
CORS有兩種請求,簡單請求和非簡單請求。只要同時滿足以下兩大條件,就屬于簡單請求。
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
請求方法是以下三種方法之一:HEAD,GET,POST
HTTP的頭信息不超出以下幾種字段:Accept,Accept-Language,Content-Language,Last-Event-ID,Content-Type【只限于三個值application/x-www-form-urlencoded、multipart/form-data、text/plain】,沒有自定義的HTTP頭部。
簡單請求
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
瀏覽器:把客戶端腳本所在的域填充到Origin header里,向其他域的服務(wù)器請求資源。
服務(wù)器:根據(jù)資源權(quán)限配置,在響應(yīng)頭中添加Access-Control-Allow-Origin Header,返回結(jié)果。
瀏覽器:比較服務(wù)器返回的Access-Control-Allow-Origin Header和請求域的Origin。如果當(dāng)前域已經(jīng)得到授權(quán),則將結(jié)果返回給頁面。否則瀏覽器忽略此次響應(yīng)。
網(wǎng)頁:收到返回結(jié)果或者瀏覽器的錯誤提示。
對于簡單的跨域請求,只要服務(wù)器設(shè)置的Access-Control-Allow-Origin Header和請求來源匹配,瀏覽器就允許跨域。服務(wù)器端設(shè)置的`Access-Control-Allow-Methods和Access-Control-Allow-Headers對簡單跨域沒有作用。
非簡單請求
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
瀏覽器:先向服務(wù)器發(fā)送一個OPTIONS預(yù)檢請求,檢測服務(wù)器端是否支持真實請求進(jìn)行跨域資源訪問,瀏覽器會在發(fā)送OPTIONS請求時會自動添加Origin Header 、Access-Control-Request-Method Header和Access-Control-Request-Headers Header。
服務(wù)器:響應(yīng)OPTIONS請求,會在responseHead里添加Access-Control-Allow-Methods head。這其中的method的值是服務(wù)器給的默認(rèn)值,可能不同的服務(wù)器添加的值不一樣。服務(wù)器還會添加Access-Control-Allow-Origin Header和Access-Control-Allow-Headers Header。這些取決于服務(wù)器對OPTIONS請求具體如何做出響應(yīng)。如果服務(wù)器對OPTIONS響應(yīng)不合你的要求,你可以手動在服務(wù)器配置OPTIONS響應(yīng),以應(yīng)對帶預(yù)檢的跨域請求。在配置服務(wù)器OPTIONS的響應(yīng)時,可以添加Access-Control-Max-Age head告訴瀏覽器在一定時間內(nèi)無需再次發(fā)送預(yù)檢請求,但是如果瀏覽器禁用緩存則無效。
瀏覽器:接到OPTIONS的響應(yīng),比較真實請求的method是否屬于返回的Access-Control-Allow-Methods head的值之一,還有origin, head也會進(jìn)行比較是否匹配。如果通過,瀏覽器就繼續(xù)向服務(wù)器發(fā)送真實請求, 否則就會報預(yù)檢錯誤:請求來源不被options響應(yīng)允許,請求方法不被options響應(yīng)允許或請求中有自定義header不被options響應(yīng)允許。
服務(wù)器:響應(yīng)真實請求,在響應(yīng)頭中放入Access-Control-Allow-Origin Header、Access-Control-Allow-Methods和Access-Control-Allow-Headers Header,分別表示允許跨域資源請求的域、請求方法和請求頭,并返回數(shù)據(jù)。
瀏覽器:接受服務(wù)器對真實請求的返回結(jié)果,返回給網(wǎng)頁
網(wǎng)頁:收到返回結(jié)果或者瀏覽器的錯誤提示。
Access-Control-Allow-Origin在響應(yīng)options請求和響應(yīng)真實請求時都是有作用的,兩者必須同時包含要跨域的源。 Access-Control-Allow-Methods和Access-Control-Allow-Headers只在響應(yīng)options請求時有作用。
攜帶cookie
在 CORS 跨域中,瀏覽器并不會自動發(fā)送 Cookie。對于普通跨域請求只需服務(wù)端設(shè)置,而帶cookie跨域請求前后端都需要設(shè)置。
瀏覽器,對于跨域請求,需要設(shè)置withCredentials 屬性為 true。服務(wù)端的響應(yīng)中必須攜帶 Access-Control-Allow-Credentials: true 。
除了Access-Control-Allow-Credentials之外,跨域發(fā)送 Cookie 還要求 Access-Control-Allow-Origin不允許使用通配符。否則瀏覽器將會拋出The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' 錯誤。事實上不僅不允許通配符,而且只能指定單一域名。
計算 Access-Control-Allow-Origin
既然Access-Control-Allow-Origin只允許單一域名, 服務(wù)器可能需要維護(hù)一個接受 Cookie 的 Origin 列表, 驗證 Origin 請求頭字段后直接將其設(shè)置為Access-Control-Allow-Origin的值。在 CORS 請求被重定向后 Origin 頭字段會被置為 null, 此時可以選擇從Referer頭字段計算得到Origin。
具體實現(xiàn)
服務(wù)器端的響應(yīng)頭配置
Access-Control-Allow-Origin 可以設(shè)置為* ,表示可以與任意域進(jìn)行數(shù)據(jù)共享。
// 設(shè)置服務(wù)器接受跨域的域名 "Access-Control-Allow-Origin": "http://127.0.0.1:8080", // 設(shè)置服務(wù)器接受跨域的請求方法 'Access-Control-Allow-Methods': 'OPTIONS,HEAD,DELETE,GET,PUT,POST', // 設(shè)置服務(wù)器接受跨域的headers 'Access-Control-Allow-Headers': 'x-requested-with, accept, origin, content-type', // 設(shè)置服務(wù)器不用再次預(yù)檢請求時間 'Access-Control-Max-Age': 10000, // 設(shè)置服務(wù)器接受跨域發(fā)送Cookie 'Access-Control-Allow-Credentials': true
document.domain
此方案僅限主域相同,子域不同的跨域應(yīng)用場景。
實現(xiàn)原理:兩個頁面都通過js強(qiáng)制設(shè)置document.domain為基礎(chǔ)主域,就實現(xiàn)了同域。
栗子:
在父頁面 http://xxx.com/a.html 中設(shè)置document.domain
<iframe id = "iframe" src="http://xxx.com/b.html" onload = "test()"></iframe> <script type="text/javascript"> document.domain = 'xxx.com';//設(shè)置成主域 function test(){ alert(document.getElementById('?iframe').contentWindow); //contentWindow 可取得子窗口的 window 對象 } </script>
在子頁面http://xxx.com/b.html 中設(shè)置document.domain
<script type="text/javascript"> document.domain = 'xxx.com'; //在iframe載入這個頁面也設(shè)置document.domain,使之與主頁面的document.domain相同 </script>
window.postMessage
window.postMessage是html5的功能,是客戶端和客戶端直接的數(shù)據(jù)傳遞,既可以跨域傳遞,也可以同域傳遞。
postMessage(data, origin)方法接受兩個參數(shù):
data:html5規(guī)范支持任意基本類型或可復(fù)制的對象,但部分瀏覽器只支持字符串,所以傳參時最好用JSON.stringify()序列化。
origin:協(xié)議+主機(jī)+端口號,也可以設(shè)置為"*",表示可以傳遞給任意窗口,如果要指定和當(dāng)前窗口同源的話設(shè)置為"/"。
栗子:
假如有一個頁面,頁面中拿到部分用戶信息,點擊進(jìn)入另外一個頁面,另外的頁面默認(rèn)是取不到用戶信息的,你可以通過window.postMessage把部分用戶信息傳到這個頁面中。(需要考慮安全性等方面。)
發(fā)送消息:
// 彈出一個新窗口 var domain = 'http://haorooms.com'; var myPopup = window.open(`${domain}/windowPostMessageListener.html`,'myWindow'); // 發(fā)送消息 setTimeout(function(){ var message = {name:"站點",sex:"男"}; console.log('傳遞的數(shù)據(jù)是 ' + message); myPopup.postMessage(message, domain); }, 1000);
接收消息:
// 監(jiān)聽消息反饋 window.addEventListener('message', function(event) { // 判斷域名是否正確 if (event.origin !== 'http://haorooms.com') return; console.log('received response: ', event.data); }, false);
如下圖,接受頁面得到數(shù)據(jù)
如果是使用iframe,代碼應(yīng)該這樣寫:
// 捕獲iframe var domain = 'http://haorooms.com'; var iframe = document.getElementById('myIFrame').contentWindow; // 發(fā)送消息 setTimeout(function(){ var message = {name:"站點",sex:"男"}; console.log('傳遞的數(shù)據(jù)是: ' + message); iframe.postMessage(message, domain); },1000);
接收數(shù)據(jù)并反饋信息:
// 響應(yīng)事件 window.addEventListener('message',function(event) { if(event.origin !== 'http://haorooms.com') return; console.log('message received: ' + event.data, event); event.source.postMessage(event.origin); }, false);
幾個比較重要的事件屬性:
source – 消息源,消息的發(fā)送窗口/iframe。
origin – 消息源的URI(可能包含協(xié)議、域名和端口),用來驗證數(shù)據(jù)源。
data – 發(fā)送方發(fā)送給接收方的數(shù)據(jù)。
window.name
原理:
window對象有個name屬性,該屬性有個特征:即在一個窗口(window)的生命周期內(nèi),窗口載入的所有的頁面都是共享一個window.name,每個頁面對window.name都有讀寫的權(quán)限,window.name是持久存在一個窗口載入過的所有頁面中的。
栗子:
在子頁面(b.com/data.html) 設(shè)置window.name:
/* b.com/data.html */ <script type="text/javascript"> window.name = 'I was there!'; // 這里是要傳輸?shù)臄?shù)據(jù),大小一般為2M,IE和firefox下可以大至32M左右 // 數(shù)據(jù)格式可以自定義,如json、字符串 </script>
在父頁面(a.com/app.html)中創(chuàng)建一個iframe,把其src指向子頁面。在父頁面監(jiān)聽iframe的onload事件,獲取子頁面數(shù)據(jù):
/* a.com/app.html */ <script type="text/javascript"> var iframe = document.createElement('iframe'); iframe.src = 'http://b.com/data.html'; function iframelLoadFn() { var data = iframe.contentWindow.name; console.log(data); // 獲取數(shù)據(jù)以后銷毀iframe,釋放內(nèi)存;這也保證了安全(不被其他域frame js訪問)。 iframeDestoryFn(); } function iframeDestoryFn() { iframe.contentWindow.document.write(''); iframe.contentWindow.close(); document.body.removeChild(iframe); } if (iframe.attachEvent) { iframe.attachEvent('onload', iframelLoadFn); } else { iframe.onload = iframelLoadFn; } document.body.appendChild(iframe); </script>
http-proxy-middleware
http-proxy-middleware用于把請求代理轉(zhuǎn)發(fā)到其他服務(wù)器的中間件。
安裝:
npm install http-proxy-middleware --save-dev
配置如下:
module.exports = { devServer: { contentBase: path.resolve(__dirname, 'dev'), publicPath: '/', historyApiFallback: true, proxy: { // 請求到 '/device' 下的請求都會被代理到target:http://target.com中 '/device/*': { target: 'http://target.com', secure: false, // 接受運(yùn)行在https上的服務(wù) changeOrigin: true } } } }
使用如下:
fetch('/device/space').then(res => { // 被代理到 http://target.com/device/space return res.json(); }); // 使用的url 必須以/開始 否則不會代理到指定地址 fetch('device/space').then(res => { // http://localhost:8080/device/space 訪問本地服務(wù) return res.json(); });
nginx反向代理
反向代理(Reverse Proxy)方式是指以代理服務(wù)器來接受客戶端的連接請求,然后將請求轉(zhuǎn)發(fā)給內(nèi)部網(wǎng)絡(luò)上的服務(wù)器;并將從服務(wù)器上得到的結(jié)果返回給客戶端,此時代理服務(wù)器對外就表現(xiàn)為一個服務(wù)器。
反向代理服務(wù)器對于客戶端而言它就像是原始服務(wù)器,并且客戶端不需要進(jìn)行任何特別的設(shè)置。客戶端向反向代理 的命名空間(name-space)中的內(nèi)容發(fā)送普通請求,接著反向代理將判斷向何處(原始服務(wù)器)轉(zhuǎn)交請求,并將獲得的內(nèi)容返回給客戶端,就像這些內(nèi)容 原本就是它自己的一樣。
模塊化
AMD/CMD/CommonJs都是JS模塊化開發(fā)的標(biāo)準(zhǔn),目前對應(yīng)的實現(xiàn)是RequireJS,SeaJs, nodeJs;
CommonJS:服務(wù)端js
CommonJS 是以在瀏覽器環(huán)境之外構(gòu)建 javaScript 生態(tài)系統(tǒng)為目標(biāo)而產(chǎn)生的寫一套規(guī)范,主要是為了解決 javaScript 的作用域問題而定義的模塊形式,可以使每個模塊它自身的命名空間中執(zhí)行。
實現(xiàn)方法:模塊必須通過 module.exports 導(dǎo)出對外的變量或者接口,通過 require() 來導(dǎo)入其他模塊的輸出到當(dāng)前模塊的作用域中;
主要針對服務(wù)端(同步加載文件)和桌面環(huán)境中,node.js 遵循的是 CommonJS 的規(guī)范;CommonJS 加載模塊是同步的,所以只有加載完成才能執(zhí)行后面的操作。
require()用來引入外部模塊;
exports對象用于導(dǎo)出當(dāng)前模塊的方法或變量,唯一的導(dǎo)出口;
module對象就代表模塊本身。
// 定義一個module.js文件 var A = () => console.log('我是定義的模塊'); // 1.第一種返回方式 module.exports = A; // 2.第二種返回方式 module.exports.test = A // 3.第三種返回方式 exports.test = A; // 定義一個test.js文件【這兩個文件在同一個目錄下】 var module = require("./module"); //調(diào)用這個模塊,不同的返回方式用不同的方式調(diào)用 // 1.第一種調(diào)用方式 module(); // 2.第二種調(diào)用方式 module.test(); // 3.第三種調(diào)用方式 module.test(); // 執(zhí)行文件 node test.js
AMD:異步模塊定義【瀏覽器端js】
AMD 是 Asynchronous Module Definition 的縮寫,意思是異步模塊定義;采用的是異步的方式進(jìn)行模塊的加載,在加載模塊的時候不影響后邊語句的運(yùn)行。主要是為前端 js 的表現(xiàn)指定的一套規(guī)范。
實現(xiàn)方法:通過define方法去定義模塊,通過require方法去加載模塊。
define(id?,dependencies?,factory): 它要在聲明模塊的時候制定所有的依賴(dep),并且還要當(dāng)做形參傳到factory中。沒什么依賴,就定義簡單的模塊(或者叫獨立的模塊)
require([modules], callback): 第一個參數(shù)[modules],是需加載的模塊名數(shù)組;第二個參數(shù)callback,是模塊加載成功之后的回調(diào)函數(shù)
主要針對瀏覽器js,requireJs遵循的是 AMD 的規(guī)范;
// module1.js文件, 定義獨立的模塊 define({ methodA: () => console.log('我是module1的methodA'); methodB: () => console.log('我是module1的methodB'); }); // module2.js文件, 另一種定義獨立模塊的方式 define(() => { return { methodA: () => console.log('我是module2的methodA'); methodB: () => console.log('我是module2的methodB'); }; }); // module3.js文件, 定義非獨立的模塊(這個模塊依賴其他模塊) define(['module1', 'module2'], (m1, m2) => { return { methodC: () => { m1.methodA(); m2.methodB(); } }; }); //定義一個main.js,去加載這些個模塊 require(['module3'], (m3) => { m3.methodC(); }); // 為避免造成網(wǎng)頁失去響應(yīng),解決辦法有兩個,一個是把它放在網(wǎng)頁底部加載,另一個是寫成下面這樣: <script src="js/require.js" defer async="true" ></script> // async屬性表明這個文件需要異步加載,避免網(wǎng)頁失去響應(yīng)。 // IE不支持這個屬性,只支持defer,所以把defer也寫上。 // data-main屬性: 指定網(wǎng)頁程序的主模塊 <script data-main="main" src="js/require.js"></script> // 控制臺輸出結(jié)果 我是module1的methodA 我是module2的methodB
CMD:通用模塊定義【瀏覽器端js】
CMD 是 Common Module Definition 的縮寫,通過異步的方式進(jìn)行模塊的加載的,在加載的時候會把模塊變?yōu)樽址馕鲆槐椴胖酪蕾嚵四膫€模塊;
主要針對瀏覽器端(異步加載文件),按需加載文件。對應(yīng)的實現(xiàn)是seajs
AMD和CMD的區(qū)別
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
對于依賴的模塊,AMD 是提前執(zhí)行,CMD 是延遲執(zhí)行。不過 RequireJS 從 2.0 開始,也改成可以延遲執(zhí)行(根據(jù)寫法不同,處理方式不同)。CMD 推崇 as lazy as possible(盡可能的懶加載,也稱為延遲加載,即在需要的時候才加載)。
CMD 推崇依賴就近,AMD 推崇依賴前置。
// CMD define(function(require, exports, module) { var a = require('./a'); a.doSomething(); // ... var b = require('./b'); // 依賴可以就近書寫 b.doSomething(); // ... }) // AMD define(['./a', './b'], function(a, b) { // 依賴必須一開始就寫好 a.doSomething(); // ... b.doSomething(); //... })
import和require區(qū)別
import和require都是被模塊化使用。
require是CommonJs的語法(AMD規(guī)范引入方式),CommonJs的模塊是對象。import是es6的一個語法標(biāo)準(zhǔn)(瀏覽器不支持,本質(zhì)是使用node中的babel將es6轉(zhuǎn)碼為es5再執(zhí)行,import會被轉(zhuǎn)碼為require),es6模塊不是對象。
require是運(yùn)行時加載整個模塊(即模塊中所有方法),生成一個對象,再從對象上讀取它的方法(只有運(yùn)行時才能得到這個對象,不能在編譯時做到靜態(tài)化),理論上可以用在代碼的任何地方。import是編譯時調(diào)用,確定模塊的依賴關(guān)系,輸入變量(es6模塊不是對象,而是通過export命令指定輸出代碼,再通過import輸入,只加載import中導(dǎo)的方法,其他方法不加載),import具有提升效果,會提升到模塊的頭部(編譯時執(zhí)行)
export和import可以位于模塊中的任何位置,但是必須是在模塊頂層,如果在其他作用域內(nèi),會報錯(es6這樣的設(shè)計可以提高編譯器效率,但沒法實現(xiàn)運(yùn)行時加載)。
require是賦值過程,把require的結(jié)果(對象,數(shù)字,函數(shù)等),默認(rèn)是export的一個對象,賦給某個變量(復(fù)制或淺拷貝)。import是解構(gòu)過程(需要誰,加載誰)。
require/exports:
// require: 真正被require出來的是來自module.exports指向的內(nèi)存塊內(nèi)容 const a = require('a') // // exports: 只是 module.exports的引用,輔助module.exports操作內(nèi)存中的數(shù)據(jù) exports.a = a module.exports = a
import/export:
// import import a from 'a'; import { default as a } from 'a'; import * as a from 'a'; import { fun1,fun2 } from 'a'; // export export default a; export const a = 1; export functon a { ... }; export { fun1, fun2 };
http和https
Http:超文本傳輸協(xié)議(Http,HyperText Transfer Protocol)是互聯(lián)網(wǎng)上應(yīng)用最為廣泛的一種網(wǎng)絡(luò)協(xié)議。設(shè)計Http最初的目的是為了提供一種發(fā)布和接收HTML頁面的方法。它可以使瀏覽器更加高效。
Http協(xié)議是以明文方式發(fā)送信息的,如果黑客截取了Web瀏覽器和服務(wù)器之間的傳輸報文,就可以直接獲得其中的信息。
Https:是以安全為目標(biāo)的Http通道,是Http的安全版。Https的安全基礎(chǔ)是SSL。SSL協(xié)議位于TCP/IP協(xié)議與各種應(yīng)用層協(xié)議之間,為數(shù)據(jù)通訊提供安全支持。SSL協(xié)議可分為兩層:SSL記錄協(xié)議(SSL Record Protocol),它建立在可靠的傳輸協(xié)議(如TCP)之上,為高層協(xié)議提供數(shù)據(jù)封裝、壓縮、加密等基本功能的支持。
SSL握手協(xié)議(SSL Handshake Protocol),它建立在SSL記錄協(xié)議之上,用于在實際的數(shù)據(jù)傳輸開始前,通訊雙方進(jìn)行身份認(rèn)證、協(xié)商加密算法、交換加密密鑰等。
HTTP與HTTPS的區(qū)別
1、HTTP是超文本傳輸協(xié)議,信息是明文傳輸,HTTPS是具有安全性的SSL加密傳輸協(xié)議。
2、HTTPS協(xié)議需要ca申請證書,一般免費(fèi)證書少,因而需要一定費(fèi)用。
3、HTTP和HTTPS使用的是完全不同的連接方式,用的端口也不一樣。前者是80,后者是443。
4、HTTP連接是無狀態(tài)的,HTTPS協(xié)議是由SSL+HTTP協(xié)議構(gòu)建的可進(jìn)行加密傳輸、身份認(rèn)證的網(wǎng)絡(luò)協(xié)議,安全性高于HTTP協(xié)議。
https的優(yōu)點
盡管HTTPS并非絕對安全,掌握根證書的機(jī)構(gòu)、掌握加密算法的組織同樣可以進(jìn)行中間人形式的攻擊,但HTTPS仍是現(xiàn)行架構(gòu)下最安全的解決方案,主要有以下幾個好處:
1)使用HTTPS協(xié)議可認(rèn)證用戶和服務(wù)器,確保數(shù)據(jù)發(fā)送到正確的客戶機(jī)和服務(wù)器;
2)HTTPS協(xié)議是由SSL+HTTP協(xié)議構(gòu)建的可進(jìn)行加密傳輸、身份認(rèn)證的網(wǎng)絡(luò)協(xié)議,要比http協(xié)議安全,可防止數(shù)據(jù)在傳輸過程中不被竊取、改變,確保數(shù)據(jù)的完整性。
3)HTTPS是現(xiàn)行架構(gòu)下最安全的解決方案,雖然不是絕對安全,但它大幅增加了中間人攻擊的成本。
4)谷歌曾在2014年8月份調(diào)整搜索引擎算法,并稱“比起同等HTTP網(wǎng)站,采用HTTPS加密的網(wǎng)站在搜索結(jié)果中的排名將會更高”。
Https的缺點
1)Https協(xié)議握手階段比較費(fèi)時,會使頁面的加載時間延長近。
2)Https連接緩存不如Http高效,會增加數(shù)據(jù)開銷,甚至已有的安全措施也會因此而受到影響;
3)SSL證書通常需要綁定IP,不能在同一IP上綁定多個域名,IPv4資源不可能支撐這個消耗。
4)Https協(xié)議的加密范圍也比較有限。最關(guān)鍵的,SSL證書的信用鏈體系并不安全,特別是在某些國家可以控制CA根證書的情況下,中間人攻擊一樣可行。
遍歷方法
for
在for循環(huán)中,循環(huán)取得數(shù)組或是數(shù)組類似對象的值,譬如arguments和HTMLCollection對象。
不足:
在于每次循環(huán)的時候數(shù)組的長度都要去獲取;
終止條件要明確;
foreach(),map()
兩個方法都可以遍歷到數(shù)組的每個元素,而且參數(shù)一致;
forEach(): 對數(shù)組的每個元素執(zhí)行一次提供的函數(shù), 總是返回undefined;
map(): 創(chuàng)建一個新數(shù)組,其結(jié)果是該數(shù)組中的每個元素都調(diào)用一個提供的函數(shù)后返回的結(jié)果。返回值是一個新的數(shù)組;
var array1 = [1,2,3,4,5]; var x = array1.forEach((value,index) => { console.log(value); return value + 10; }); console.log(x); // undefined var y = array1.map((value,index) => { console.log(value); return value + 10; }); console.log(y); // [11, 12, 13, 14, 15]
for in
經(jīng)常用來迭代對象的屬性或數(shù)組的每個元素,它包含當(dāng)前屬性的名稱或當(dāng)前數(shù)組元素的索引。
當(dāng)遍歷一個對象的時候,變量 i 是循環(huán)計數(shù)器 為 對象的屬性名, 以任意順序遍歷一個對象的可枚舉屬性。對于每個不同的屬性,語句都會被執(zhí)行。
當(dāng)遍歷一個數(shù)組的時候,變量 i 是循環(huán)計數(shù)器 為 當(dāng)前數(shù)組元素的索引
不足:
for..in循環(huán)會把某個類型的原型(prototype)中方法與屬性給遍歷出來.
const array = ["admin","manager","db"]; array.color = 'red'; array.prototype.name= "zhangshan"; for(var i in array){ if(array.hasOwnProperty(i)){ console.log(array[i]); // admin,manager,db,color } } // hasOwnProperty(): 對象的屬性或方法是非繼承的,返回true
for … of
迭代循環(huán)可迭代對象(包括Array,Map,Set,String,TypedArray,arguments 對象)等等。不能遍歷對象。只循環(huán)集合本身的元素
var a = ['A', 'B', 'C']; var s = new Set(['A', 'B', 'C']); var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]); a.name = 'array'; for (var x of a) { console.log(x); //'A', 'B', 'C' } for (var x of s) { console.log(x);//'A', 'B', 'C' } for (var x of m) { console.log(x[0] + '=' + x[1]);//1='x',2='y',3='z' }
繼承
// 定義一個動物類 function Animal(name) { // 屬性 this.name = name || 'Animal'; // 實例方法 this.sleep = function(){ console.log(this.name + '正在睡覺!'); } } // 原型方法 Animal.prototype.eat = function(food) { console.log(this.name + '正在吃:' + food); };
原型鏈繼承
核心: 將父類的實例作為子類的原型。
function Dog(age) { this.age = age; } Dog.protoType = New Animal(); Dog.prototype.name = 'dog'; const dog = new Dog(12); console.log(dog.name); console.log(dog.eat('age')); console.log(dog instanceof Animal); //true console.log(dog instanceof Dog); //true
new 創(chuàng)建新實例對象經(jīng)過了以下幾步:
1.創(chuàng)建一個新對象
2.將新對象的_proto_指向構(gòu)造函數(shù)的prototype對象
3.將構(gòu)造函數(shù)的作用域賦值給新對象 (也就是this指向新對象)
4.執(zhí)行構(gòu)造函數(shù)中的代碼(為這個新對象添加屬性)
5.返回新的對象
// 1. 創(chuàng)建一個新對象 var Obj = {}; // 2. 將新對象的_proto_指向構(gòu)造函數(shù)的prototype對象 Obj._proto_ = Animal.prototype(); // 3. 執(zhí)行構(gòu)造函數(shù)中的代碼(為這個新對象添加屬性) Animal.call(Obj); // 4. 返回新的對象 return Obj;
特點:
1.實例可繼承的屬性有:實例的構(gòu)造函數(shù)的屬性,父類構(gòu)造函數(shù)屬性,父類原型的屬性
2.非常純粹的繼承關(guān)系,實例是子類的實例,也是父類的實例
3.父類新增原型方法/原型屬性,子類都能訪問到
缺點:
1.新實例無法向父類構(gòu)造函數(shù)傳參。
2.繼承單一。
3.所有新實例都會共享父類實例的屬性。(原型上的屬性是共享的,一個實例修改了原型屬性,另一個實例的原型屬性也會被修改!)
4.要想為子類新增原型上的屬性和方法,必須要在new Animal()這樣的語句之后執(zhí)行,不能放到構(gòu)造器中
構(gòu)造函數(shù)繼承
核心:使用父類的構(gòu)造函數(shù)來增強(qiáng)子類實例,等于是復(fù)制父類的實例屬性給子類(沒用到原型)
function Dog(name) { Animal.apply(this, 'dog'); this.name = name; } const dog = new Dog(); console.log(dog.name); console.log(dog.eat('age')); console.log(dog instanceof Animal); //false console.log(dog instanceof Dog); //true
重點:用.call()和.apply()將父類構(gòu)造函數(shù)引入子類函數(shù)(在子類函數(shù)中做了父類函數(shù)的自執(zhí)行(復(fù)制))
特點:
1.只繼承了父類構(gòu)造函數(shù)的屬性,沒有繼承父類原型的屬性。
2.解決了原型鏈繼承缺點1、2、3。
3.可以實現(xiàn)多繼承,繼承多個構(gòu)造函數(shù)屬性(call多個)。
4.在子實例中可向父實例傳參。
缺點:
1.能繼承父類構(gòu)造函數(shù)的屬性。
2.無法實現(xiàn)構(gòu)造函數(shù)的復(fù)用。(每次用每次都要重新調(diào)用)
3.每個新實例都有父類構(gòu)造函數(shù)的副本,臃腫。
4.實例并不是父類的實例,只是子類的實例
組合繼承(原型鏈繼承和構(gòu)造函數(shù)繼承)(常用)
核心:通過調(diào)用父類構(gòu)造,繼承父類的屬性并保留傳參的優(yōu)點,然后通過將父類實例作為子類原型,實現(xiàn)函數(shù)復(fù)用
function Cat(name){ Animal.call(this, name); this.name = name; } Cat.prototype = new Animal(); Cat.prototype.constructor = Cat; var cat = new Cat(); console.log(cat.name); console.log(cat.sleep()); console.log(cat instanceof Animal); // true console.log(cat instanceof Cat); // true
重點:結(jié)合了兩種模式的優(yōu)點,傳參和復(fù)用
特點:
1.可以繼承父類原型上的屬性,可以傳參,可復(fù)用。
2.每個新實例引入的構(gòu)造函數(shù)屬性是私有的。
3.既是子類的實例,也是父類的實例
缺點:
調(diào)用了兩次父類構(gòu)造函數(shù)(耗內(nèi)存),子類的構(gòu)造函數(shù)會代替原型上的那個父類構(gòu)造函數(shù)。
原型式繼承
重點:用一個函數(shù)包裝一個對象,然后返回這個函數(shù)的調(diào)用,這個函數(shù)就變成了個可以隨意增添屬性的實例或?qū)ο?。object.create()就是這個原理。
特點:
類似于復(fù)制一個對象,用函數(shù)來包裝。
缺點:
1.所有實例都會繼承原型上的屬性。
2.無法實現(xiàn)復(fù)用。(新實例屬性都是后面添加的)
寄生式繼承
重點:就是給原型式繼承外面套了個殼子。
優(yōu)點:沒有創(chuàng)建自定義類型,因為只是套了個殼子返回對象(這個),這個函數(shù)順理成章就成了創(chuàng)建的新對象。
缺點:沒用到原型,無法復(fù)用。
寄生組合式繼承(常用)
寄生:在函數(shù)內(nèi)返回對象然后調(diào)用
組合:
1、函數(shù)的原型等于另一個實例。
2、在函數(shù)中用apply或者call引入另一個構(gòu)造函數(shù),可傳參。
function Cat(name){ Animal.call(this); this.name = name || 'Tom'; } (function(){ // 創(chuàng)建一個沒有實例方法的類 var Super = function(){}; Super.prototype = Animal.prototype; //將實例作為子類的原型 Cat.prototype = new Super(); })(); var cat = new Cat(); Cat.prototype.constructor = Cat; // 需要修復(fù)下構(gòu)造函數(shù) console.log(cat.name); console.log(cat.sleep()); console.log(cat instanceof Animal); // true console.log(cat instanceof Cat); //true
到此,關(guān)于“有關(guān)前端基礎(chǔ)知識整理匯總”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。