中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

有關前端基礎知識整理匯總

發布時間:2021-10-18 14:39:36 來源:億速云 閱讀:213 作者:iii 欄目:web開發

這篇文章主要介紹“有關前端基礎知識整理匯總”,在日常操作中,相信很多人在有關前端基礎知識整理匯總問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”有關前端基礎知識整理匯總”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

HTML頁面的生命周期

HTML頁面的生命周期有以下三個重要事件:

  • DOMContentLoaded —— 瀏覽器已經完全加載了 HTML,DOM 樹已經構建完畢,但是像是 有關前端基礎知識整理匯總

    沒有 defer 或 async,所有<script>元素會按照在頁面出現的先后順序依次被解析,瀏覽器會立即加載并執行指定的腳本, 只有解析完前面的script元素的內容后,才會解析后面的代碼。
    async defer 屬性僅僅對外部腳本起作用,在 src 不存在時會被自動忽略。

    使用<script>的兩種方式
    1.頁面中嵌入script代碼, 只需指定type屬性

    <script type="text/javascript">   function sayHi() {     console.log('hihihi');     // 內部不能出現'</script>'字符串,如果必須出現,必須使用轉義標簽&lsquo;\&rsquo;     alert('<\/script>');   } </script>

    包含在<script>元素內的代碼會從上而下依次解釋,在解釋器對<script>元素內的所有代碼求值完畢之前,頁面中的其余內容都不會被瀏覽器加載或顯示

    2.包含外部js文件, src屬性是必須的。

    <script src="example.js"></script> // 帶有src屬性的元素不應該在標簽之間包含額外的js代碼,即使包含,只會下載并執行外部文件,內部代碼也會被忽略。

    與嵌入式js代碼一樣, 在解析外部js文件時,頁面的處理會暫時停止。

    改變腳本行為的方法

    1. defer: 立即下載,延遲執行

    加載和渲染后續文檔元素的過程將和腳本的加載并行進行(異步),但是腳本的執行會在所有元素解析完成之后。腳本總會按照聲明順序執行。

    在DOMContentLoaded事件之前執行。

    <script defer="defer" src="example.js"></script>

    2. async: 異步腳本

    加載和渲染后續文檔元素的過程將和腳本的加載與執行并行進行(異步)。但是async  在下載完畢后的執行會阻塞HTML的解析。腳本加載后馬上執行,不能保證異步腳本按照他們在頁面中出現的順序執行。

    一定會在load事件之前執行,可能會在DOMContentLoaded事件之前或之后執行。

    <script async="async" src="example.js"></script>

    區別:

    有關前端基礎知識整理匯總

    meta

    META標簽是HTML標記HEAD區的一個關鍵標簽,它提供的信息雖然用戶不可見,但卻是文檔的最基本的元信息。<meta> 除了提供文檔字符集、使用語言、作者等網頁相關信息外,還可以設置信息給搜索引擎,目的是為了SEO(搜索引擎優化)。

    HTML<meta>  元素表示那些不能由其它 HTML 元相關(meta-related)元素((<base>、<link>, <script>、<style> 或 <title>)之一表示的任何元數據信息。

    屬性

    name

    設置元數據的名稱。name 和 content 屬性可以一起使用,以名-值對的方式給文檔提供元數據,content 作為元數據的值。

    content

    設置與 http-equiv 或 name 屬性相關的元信息。

    charset

    聲明了文檔的字符編碼。如果使用了這個屬性,其值必須是與ASCII大小寫無關(ASCII case-insensitive)的"utf-8"。

    http-equiv

    定義了一個編譯指示指令,其作用類似于http協議, 告訴瀏覽器一些關于字符設定,頁面刷新,cookie,緩存等等相關信息。屬性名叫做 http-equiv  是因為所有允許的值都是HTTP頭部的名稱。可設置的值有:

    1. 鴻蒙官方戰略合作共建——HarmonyOS技術社區

    2. content-security-policy:它允許頁面作者定義當前頁的內容策略。內容策略主要指定允許的服務器源和腳本端點,這有助于防止跨站點腳本攻擊。

    3. Expires:可以用于設定網頁的到期時間,一旦過期則必須到服務器上重新調用。content必須使用GMT時間格式;

    4. content-type:如果使用這個屬性,其值必須是"text/html; charset=utf-8"。注意:該屬性只能用于 MIME type為  text/html 的文檔,不能用于MIME類型為XML的文檔。

    5. default-style:設置默認CSS 樣式表組的名稱。

    6. refresh:定時讓網頁在指定的時間n內,刷新或跳轉;

    如果 content 只包含一個正整數,則是n秒后, 頁面刷新。

    如果 content 包含一個正整數,并且后面跟著字符串 ';url=' 和一個合法的 URL,則是重定向到指定鏈接的時間間隔(秒)。

    meta 元素定義的元數據的類型包括以下幾種:

    • 如果設置了 name 屬性,meta 元素提供的是文檔級別(document-level)的元數據,應用于整個頁面。

    • 如果設置了 http-equiv 屬性,meta 元素則是編譯指令,提供的信息與類似命名的HTTP頭部相同。

    • 如果設置了 charset 屬性,meta 元素是一個字符集聲明,告訴文檔使用哪種字符編碼。

    • 如果設置了 itemprop 屬性,meta 元素提供用戶定義的元數據。

    注意: 全局屬性 name 在  元素中具有特殊的語義;另外, 在同一個  標簽中,name, http-equiv 或者 charset 三者中任何一個屬性存在時,itemprop 屬性不能被使用。

    使用

    content值里有多個屬性通過,隔開,同時設置多個屬性。

    /* name */ // 適配移動設備 <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" /> // 檢測html格式:禁止把數字轉化為撥號鏈接 <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  是瀏覽器的可視區域,可視區域的大小是瀏覽器自己設置的。它可能大于移動設備可視區域,也可能小于移動設備可視區域。一般來講,移動設備上的viewport都是大于移動設備可視區域。在控制臺輸出window.innerWidth查看Viewport大小。

    相關概念

    設備像素:設備屏幕分辨率。iphone6p 的分辨率是 1334*750;

    設備獨立像素:設備上程序用來描繪數據的一個個的“點”, 在控制臺用 screen.width/height查看。iphone6p  的設備獨立像素是375*667;

    設備像素比(DPR):設備像素(寬)/設備獨立像素(寬),DPR越高渲染越精致。在控制臺輸出window.devicePixelRatio查看設備像素比。iphone6s  的設備像素比就是 750 / 375 = 2;

    CSS像素:瀏覽器使用的單位,用來精確度量網頁上的內容。在一般情況下(頁面縮放比為 1),1 個 CSS 像素等于 1 個設備獨立像素。

    屏幕尺寸:屏幕對角線的長度,以英尺為單位。

    像素密度(PPI):每英寸屏幕擁有的像素數。

    為什么要使用meta viewport?

    通常情況下,移動設備上的瀏覽器都會把viewport設為980px或1024px,此時頁面會出現橫向滾動條,因為移動設備可視區域寬度是比這個默認的viewport的寬度要小。所以出現了meta  標簽設置viewport 元始性進行移動端網頁優化。

    meta viewport 屬性

    width:控制 viewport 的大小,可以給它指定一個值(正整數),或者是一個特殊的值(如:device-width 設備獨立像素寬度,單位縮放為  1 時);

    initial-scale:初始縮放比例,即當頁面第一次加載時的縮放比例,為一個數字(可以帶小數);

    maximum-scale允許用戶縮放到的最大比例,為一個數字(可以帶小數);

    minimum-scale允許用戶縮放到的最小比例,為一個數字(可以帶小數);

    user-scalable是否允許用戶手動縮放,值為 "no"(不允許) 或 "yes"(允許);

    height:與 width 相對應(很少使用)。

    基本類型和引用類型

    基本類型

    基本類型: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 // 用==比較兩個不同類型的變量時會進行一些類型轉換。 // 先會把true轉換為數字1再和數字1進行比較,結果就是true了

    3.基本類型的變量是存放在棧區的(棧區指內存里的棧內存)

    引用類型

    引用類型: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); // 兩個對象的堆內存中的地址不同,false

    3.引用類型的值是同時保存在棧內存和堆內存中的對象

    javascript和其他語言不同,其不允許直接訪問內存中的位置,也就是說不能直接操作對象的內存空間。實際上,是操作對象的引用,所以引用類型的值是按引用訪問的。準確地說,引用類型的存儲需要內存的棧區和堆區(堆區是指內存里的堆內存)共同完成,棧區內存保存變量標識符和指向堆內存中該對象的指針,也可以說是該對象在堆內存的地址。

    作用域和執行上下文

    JavaScript代碼的整個執行過程,分為兩個階段,代碼編譯階段與代碼執行階段。

    • 編譯階段:由編譯器完成,將代碼翻譯成可執行代碼。這個階段作用域規則會確定。

    • 執行階段:由引擎完成,主要任務是執行可執行代碼。執行上下文在這個階段創建。

    作用域

    簡單來說作用域就是一個區域,沒有變量。作用域可以嵌套。作用域規定了如何查找變量,也就是確定當前執行代碼對變量的訪問權限。作用域在函數定義時就已經確定了,不是在函數調用確定。

    ES6 之前 JavaScript 只有全局作用域和函數作用域。ES6 后,增加了塊級作用域(最近大括號的作用范圍), 通過let 和 const  聲明的變量。

    作用域其實由兩部分組成:

    1. 鴻蒙官方戰略合作共建——HarmonyOS技術社區

    2. 記錄作用域內變量信息(假設變量,常量,函數等統稱為變量)和代碼結構信息的東西,稱之為 Environment Record。

    3. 一個引用 __outer__,這個引用指向當前作用域的父作用域。全局作用域的 __outer__ 為 null。

    詞法作用域

    JavaScript 采用詞法作用域(lexical scoping),也就是靜態作用域。

    所謂詞法(代碼)作用域,就是代碼在編寫過程中體現出來的作用范圍,代碼一旦寫好了,沒有運行之前(不用執行),作用范圍就已經確定好了,這個就是所謂的詞法作用域。

    詞法作用域的規則:

    1. 鴻蒙官方戰略合作共建——HarmonyOS技術社區

    2. 函數允許訪問函數外部的數據

    3. 整個代碼結構中只有函數才能限定作用域

    4. 作用規則首先使用變量提升規則分析

    5. 如果當前作用規則里面有該名字,則不考慮外面的外面的名字

    var a = 1; function out() {   var a = 2;   inner(); }  function inner() {   console.log(a) } out();  //====>  1

    作用域鏈

    當查找變量的時候,會先從當前上下文的變量對象中查找,如果沒有找到,就會從父級(詞法層面上的父級)執行上下文的變量對象中查找,一直找到全局上下文的變量對象,也就是全局對象。這樣由多個執行上下文的變量對象構成的指針鏈表就叫做作用域鏈。

    作用域鏈本質上是一個指向當前環境與上層環境的一系列變量對象的指針列表(它只引用但不實際包含變量對象),作用域鏈保證了當前執行環境對符合訪問權限的變量和函數的有序訪問。

    例子:

    用一個數組scopeChain來表示作用域鏈,數組的第一項scopeChain[0]為作用域鏈的最前端,而數組的最后一項,為作用域鏈的最末端,所有的最末端都為全局變量對象。

    var a = 1;              function out() {     var b = 2;     function inner() {         var c = 3;         console.log(a + b + c);     }     inner();           } out();

    首先,代碼開始運行時就創建了局上下文環境,接著運行到out()時創建 out函數的執行上下文,最后運行到inner()時創建  inner函數的執行上下文,我們設定他們的變量對象分別為VO(global),VO(out), VO(inner)。

    當函數創建時,執行上下文為:

    // 全局上下文環境 globalEC = {   VO: {     out: <out reference>,  // 表示 out 的地址引用     a: undefined   },   scopeChain: [VO(global)], // 作用域鏈 }  // out 函數的執行上下文 outEC = {   VO: {     arguments: {...},     inner: <inner reference>,  // 表示 inner 的地址引用     b: undefined   },   scopeChain: [VO(out), VO(global)], // 作用域鏈 }  // inner 函數的執行上下文 innerEC = {   VO: {     arguments: {...},       c: undefined,   },    scopeChain: [VO(inner), VO(out), VO(global)], // 作用域鏈 }

    執行上下文

    簡單來說,當在代碼執行階段執行到一個函數的時候,就會進行準備工作,這里的“準備工作”,就叫做"執行上下文(EC)",也叫執行上下文環境,也叫執行環境。js引擎創建了執行上下文棧(Execution  context stack,ECS)來管理執行上下文。

    當調用一個函數時,一個新的執行上下文就會被創建。而一個執行上下文的生命周期可以分為兩個階段:

    • 創建階段:在這個階段,執行上下文會分別創建變量對象,建立作用域鏈,以及確定this的指向。

    • 代碼執行階段:開始執行代碼,會完成變量賦值,函數引用,以及執行其他代碼。

    特點

    1. 鴻蒙官方戰略合作共建——HarmonyOS技術社區

    2. 處于活動狀態的執行上下文環境只有一個, 只有棧頂的上下文處于活動狀態,執行其中的代碼。

    3. 函數每調用一次,都會產生一個新的執行上下文環境。

    4. 全局上下文在代碼開始執行時就創建,只有唯一的一個,永遠在棧底,瀏覽器窗口關閉時出棧。

    5. 函數被調用的時候創建上下文環境。

    變量對象

    變量對象的創建過程

    1. 鴻蒙官方戰略合作共建——HarmonyOS技術社區

    2. 建立arguments對象。檢查當前上下文中的參數,建立該對象下的屬性與屬性值。

    3. 檢查當前上下文的函數聲明,也就是使用function關鍵字聲明的函數。在變量對象中以函數名建立一個屬性,屬性值為指向該函數所在內存地址的引用。如果函數名的屬性已經存在,那么該屬性將會被新的引用所覆蓋。

    4. 檢查當前上下文中的變量聲明,每找到一個變量聲明,就在變量對象中以變量名建立一個屬性,屬性值為undefined。如果該變量名的屬性已經存在,為了防止同名的函數被修改為undefined,則會直接跳過,原屬性值不會被修改。

    活動對象

    變量對象與活動對象其實都是同一個對象,只是處于執行上下文的不同生命周期。不過只有處于函數調用棧棧頂的執行上下文中的變量對象,才會變成活動對象。

    執行上下文棧

    執行上下文可以理解為當前代碼的執行環境,JavaScript中的運行環境大概包括三種情況:

    • 全局環境:JavaScript代碼運行起來會首先進入該環境

    • 函數環境:當函數被調用執行時,會進入當前函數中執行代碼

    • eval

    在代碼開始執行時,首先會產生一個全局執行上下文環境,調用函數時,會產生函數執行上下文環境,函數調用完成后,它的執行上下文環境以及其中的數據都會被銷毀,重新回到全局執行環境,網頁關閉后全局執行環境也會銷毀。其實這是一個壓棧出棧的過程,全局上下文環境永遠在棧底,而當前正在執行的函數上下文在棧頂

    var a = 1;             // 1.進入全局上下文環境 function out() {     var b = 2;     function inner() {         var c = 3;         console.log(a + b + c);     }     inner();          // 3. 進入inner函數上下文環境 } out(); // 2. 進入out函數上下文環境

    以上代碼的執行會經歷以下過程:

    1. 鴻蒙官方戰略合作共建——HarmonyOS技術社區

    2. 當代碼開始執行時就創建全局執行上下文環境,全局上下文入棧

    3. 全局上下文入棧后,其中的代碼開始執行,進行賦值、函數調用等操作,執行到out()時,激活函數out創建自己的執行上下文環境,out函數上下文入棧

    4. out函數上下文入棧后,其中的代碼開始執行,進行賦值、函數調用等操作,執行到inner()時,激活函數inner創建自己的執行上下文環境,inner函數上下文入棧。

    5. inner函數上下文入棧后,其中的代碼開始執行,進行賦值、函數調用、打印等操作,由于里面沒有可以生成其他執行上下文的需要,所有代碼執行完畢后,inner函數上下文出棧。

    6. inner函數上下文出棧,又回到了out函數執行上下文環境,接著執行out函數中后面剩下的代碼,由于后面沒有可以生成其他執行上下文的需要,所有代碼執行完畢后,out函數上下文出棧。

    7. out函數上下文出棧后,又回到了全局執行上下文環境,直到瀏覽器窗口關閉,全局上下文出棧

    作用域與執行上下文區別

    作用域只是一個“地盤”,其中沒有變量。變量是通過作用域對應的執行上下文環境中的變量對象來實現的。所以作用域是靜態觀念的,而執行上下文環境是動態上的。有閉包存在時,一個作用域存在兩個上下文環境也是有的。

    同一個作用域下,對同一個函數的不同的調用會產生不同的執行上下文環境,繼而產生不同的變量的值,所以,作用域中變量的值是在執行過程中確定的,而作用域是在函數創建時就確定的。

    如果要查找一個作用域下某個變量的值,就需要找到這個作用域對應的執行上下文環境,再在其中找到變量的值。

    變量提升

    在Javascript中,函數及變量的聲明都將被提升到函數的最頂部,提升的僅僅是變量的聲明,變量的賦值并不會被提升。函數的聲明與變量的聲明是不一樣的,函數表達式和變量表達式只是其聲明被提升,函數聲明是函數的聲明和實現都被提升。

    function foo() {     console.log("global foo");   }    function bar() {      console.log("global bar");   }    //定義全局變量   var v = "global var";    function hoistMe() {    // var bar; 被提升到頂部,并未實現    // var v;    console.log(typeof foo); //function      console.log(typeof bar); //undefined      console.log(v); //undefined       // 函數里面定義了同名的函數和變量,無論在函數的任何位置定義這些函數和和變量,它們都將被提升到函數的最頂部。     foo(); //local foo      bar(); //報錯,TypeError "bar is not a function"      //函數聲明,變量foo以及其實現被提升到hoistMe函數頂部      function foo() {        alert("local foo");      }       //函數表達式,僅變量bar被提升到函數頂部,實現沒有被提升      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沒有變量提升,執行到console.log時應該是輸出全局作用域中的b,而不是出現錯誤。

    我們可以推知,這里確實出現了變量提升,而我們不能夠訪問的原因事實上是因為let的死區設計:當前作用域頂部到該變量聲明位置中間的部分,都是該let變量的死區,在死區中,禁止訪問該變量。由此,我們給出結論,let聲明的變量存在變量提升,  但是由于死區我們無法在聲明前訪問這個變量。

    var let 區別

    1. 鴻蒙官方戰略合作共建——HarmonyOS技術社區

    2. var聲明的變量,只有函數才能為它創建新的作用域;

    3. let聲明的變量,支持塊級作用域,花括號就能為它創建新的作用域;

    4. 相同作用域,var可以反復聲明相同標識符的變量,而let是不允許的;

    5. let聲明的變量禁止在聲明前訪問

    // 全局變量 var i = 0 ; // 定義外部函數 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 }

    閉包

    閉包就是指有權訪問另一個函數作用域中的變量的函數。

    官方解釋:閉包是由函數以及創建該函數的詞法環境組合而成。這個環境包含了這個閉包創建時所能訪問的所有局部變量。(詞法作用域)

    通俗解釋:閉包的關鍵在于:外部函數調用之后其變量對象本應該被銷毀,但閉包的存在使我們仍然可以訪問外部函數的變量對象。

    當某個函數被掉用的時候,會創建一個執行環境及相應的作用域鏈。然后使用arguments和其他命名參數的值來初始化函數的活動對象。但在作用域鏈中,外部函數的活動對象始終處于第二位,外部函數的外部函數的活動對象處于第三位...直至作為作用域鏈終點的全局執行環境。

    作用域鏈本質上是一個指向變量對象的指針列表,他只引用但不實際包含變量對象。

    無論什么時候在函數中訪問一個變量時,就會從作用域鏈中搜索具有相同名字的變量,一般來講,當函數執行完畢,局部活動對象就會被銷毀,內存中僅保存全部作用域的活動對象。但是,閉包不同。

    創建閉包: 在一個函數內部創建另一個函數

    function add() {   let a = 1;   let b = 3;   function closure() {      b++;      return a + b;   }   return closure; } // 閉包的作用域鏈包含著它自己的作用域,以及包含它的函數的作用域和全局作用域。

    生命周期

    通常,函數的作用域及其所有變量都會在函數執行結束后被銷毀。但是,在創建了一個閉包以后,這個函數的作用域就會一直保存到閉包不存在為止。

    當閉包中的函數closure從add中返回后,它的作用域鏈被初始化為包含add函數的活動對象和全局變量對象。這樣closure就可以訪問在add中定義的所有變量。

    更重要的是,add函數在執行完畢后,也不會銷毀,因為closure函數的作用域鏈仍然在引用這個活動對象。

    換句話說,當add返回后,其執行環境的作用域鏈被銷毀,但它的活動對象仍然在內存中,直至closure被銷毀。

    function add(x) {   function closure(y) {      return x + y;   }   return closure; }  let add2 = add(2); let add5 = add(5); // add2 和 add5 共享相同的函數定義,但是保存了不同的環境 // 在add2的環境中,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()()是在全局作用域中調用了匿名函數,this指向了window。

    函數名與函數功能是分割開的,不要認為函數在哪里,其內部的this就指向哪里。

    window才是匿名函數功能執行的環境。

    使用注意點

    1)由于閉包會讓包含函數中的變量都被保存在內存中,內存消耗很大,所以不能濫用閉包,否則會造成網頁的性能問題,在IE中可能導致內存泄露。解決方法是,在退出函數之前,將不使用的局部變量全部刪除。

    2)閉包會在父函數外部,改變父函數內部變量的值。所以,如果你把父函數當作對象(object)使用,把閉包當作它的公用方法(Public  Method),把內部變量當作它的私有屬性(private value),這時一定要小心,不要隨便改變父函數內部變量的值。

    使用

    模仿塊級作用域

    私有變量

    模塊模式

    在循環中創建閉包:一個常見錯誤

    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】 閉包,立即執行函數 // 在閉包函數內部形成了局部作用域,每循環一次,形成一個自己的局部作用域 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()函數回調屬于異步任務,會出現在宏任務隊列中,被壓到了任務隊列的最后,在這段代碼應該是for循環這個同步任務執行完成后才會輪到它

    測試1錯誤原因:賦值給 setTimeout 的是閉包。這些閉包是由他們的函數定義和在 testFunc1  作用域中捕獲的環境所組成的。這三個閉包在循環中被創建,但他們共享了同一個詞法作用域,在這個作用域中存在一個變量i。這是因為變量i使用var進行聲明,由于變量提升,所以具有函數作用域。當onfocus的回調執行時,i的值被決定。由于循環在事件觸發之前早已執行完畢,變量對象i(被三個閉包所共享)已經指向了i的最后一個值。

    測試2正確原因: 所有的回調不再共享同一個環境, showCallback 函數為每一個回調創建一個新的詞法環境。在這些環境中,i  指向數組中對應的下標。

    測試4正確原因:JS中的for循環體比較特殊,每次執行都是一個全新的獨立的塊作用域,用let聲明的變量傳入到  for循環體的作用域后,不會發生改變,不受外界的影響。

    this指向問題

    this 就是一個指針,指向我們調用函數的對象。

    執行上下文: 是語言規范中的一個概念,用通俗的話講,大致等同于函數的執行“環境”。具體的有:變量作用域(和  作用域鏈條,閉包里面來自外部作用域的變量),函數參數,以及 this 對象的值。

    找出 this 的指向

    this 的值并不是由函數定義放在哪個對象里面決定,而是函數執行時由誰來喚起決定。

    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對象調用的print】Jay Details console.log(person.print());          // 【person對象調用的print】Jay Person  var name1 = person.print; var name2 = person.details;  console.log(name1()); // 【name1前面沒有調用對象,所以是window】Jay Global console.log(name2.print()) // 【name2對象調用的print】Jay Details

    this和箭頭函數

    箭頭函數按詞法作用域來綁定它的上下文,所以 this  實際上會引用到原來的上下文。箭頭函數保持它當前執行上下文的詞法作用域不變,而普通函數則不會。換句話說,箭頭函數從包含它的詞法作用域中繼承到了 this  的值。

    匿名函數,它不會作為某個對象的方法被調用, 因此,this 關鍵詞指向了全局 window 對象

    var object = {     data: [1,2,3],     dataDouble: [1,2,3],     double: function() {         console.log(this); // object         return this.data.map(function(item) { // this是當前object,object調用的double             console.log(this);   // 傳給map()的那個匿名函數沒有被任一對象調用,所以是window             return item * 2;         });     },     doubleArrow: function() {         console.log(this); // object         return this.dataDouble.map(item => { // this是當前object,object調用的doubleArrow             console.log(this);      // doubleArrow是object調用的,這就是上下文,所以是window             return item * 2;         });     } }; object.double(); object.doubleArrow();

    明確設置執行上下文

    在 JavaScript 中通過使用內置的特性開發者就可以直接操作執行上下文了。這些特性包括:

    • bind():不需要執行函數就可以將 this 的值準確設置到你選擇的一個對象上。通過逗號隔開傳遞多個參數。設置好 this  關鍵詞后不會立刻執行函數。

    • apply():將 this 的值準確設置到你選擇的一個對象上。apply(thisObj,  argArray)接收兩個參數,thisObj是函數運行的作用域(this),argArray是參數數組,數組的每一項是你希望傳遞給函數的參數。如果沒有提供argArray和thisObj任何一個參數,那么Global對象將用作thisObj。最后,會立刻執行函數。

    • call():將 this 的值準確設置到你選擇的一個對象上。然后像bind  一樣通過逗號分隔傳遞多個參數給函數。語法:call(thisObj,arg1,arg2,...,  argn);,如果沒有提供thisObj參數,那么Global對象被用于thisObj。最后,會立刻執行函數。

    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 就是接受數組版本的call。

    Math.min(1,2,3,4); // 返回 1 Math.min([1,2,3,4]); // 返回 NaN。只接受數字 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: '一年級'}

    如果你的參數本來就存在一個數組中,那自然就用 apply,如果參數比較散亂相互之間沒什么關聯,就用 call。

    對象屬性類型

    數據屬性

    數據屬性包含一個數據值的位置,在這個位置可以讀取和寫入值,數據屬性有4個描述其行為的特性:

    • Configurable: 表示是否通過delete刪除屬性從而重新定義屬性,能否修改屬性的特性,或者能否把屬性修改為訪問器屬性。默認值是true

    • Enumerable: 表示能否通過for-in循環返回屬性。默認值是true

    • Writable: 表述能否修改屬性。默認值是true

    • Value: 包含這個屬性的數據值。默認值是true

    訪問器屬性

    函數式編程

    函數式編程是一種編程范式,是一種構建計算機程序結構和元素的風格,它把計算看作是對數學函數的評估,避免了狀態的變化和數據的可變。

    純函數

    純函數是穩定的、一致的和可預測的。給定相同的參數,純函數總是返回相同的結果。

    特性

    1. 如果給定相同的參數,則得到相同的結果

    我們想要實現一個計算圓的面積的函數。

    不是純函數會這樣做:

    let PI = 3.14;      const calculateArea = (radius) => radius * radius * PI;   // 它使用了一個沒有作為參數傳遞給函數的全局對象  calculateArea(10); // returns 314.0

    純函數:

    let PI = 3.14;      const calculateArea = (radius, pi) => radius * radius * pi;   // 現在把 PI 的值作為參數傳遞給函數,這樣就沒有外部對象引入。  calculateArea(10, PI); // returns 314.0

    2. 無明顯副作用

    純函數不會引起任何可觀察到的副作用。可見副作用的例子包括修改全局對象或通過引用傳遞的參數。

    現在,實現一個函數,接收一個整數并返對該整數進行加1操作且返回:

    let counter = 1; function increaseCounter(value) {         counter = value + 1;    }     increaseCounter(counter);    console.log(counter); // 2

    該非純函數接收該值并重新分配counter,使其值增加1。

    函數式編程不鼓勵可變性(修改全局對象)。

    let counter = 1; const increaseCounter = (value) => value + 1;   // 函數返回遞增的值,而不改變變量的值 increaseCounter(counter); // 2     console.log(counter); // 1

    3. 引用透明性

    如果一個函數對于相同的輸入始終產生相同的結果,那么它可以看作透明的。

    實現一個square 函數:

    const square = (n) => n * n; square(2); // 4 將2作為square函數的參數傳遞始終會返回4

    可以把square(2)換成4,我們的函數就是引用透明的。

    純函數使用

    單元測試

    純函數代碼肯定更容易測試,不需要 mock 任何東西。因此我們可以使用不同的上下文對純函數進行單元測試。

    一個簡單的例子是接收一組數字,并對每個數進行加 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],預期輸出是[2,3,4,5,6]。

    純函數也可以被看作成值并用作數據使用

    • 從常量和變量中引用它。

    • 將其作為參數傳遞給其他函數。

    • 作為其他函數的結果返回它。

    其思想是將函數視為值,并將函數作為數據傳遞。通過這種方式,我們可以組合不同的函數來創建具有新行為的新函數。

    假如我們有一個函數,它對兩個值求和,然后將值加倍,如下所示:

    const doubleSum = (a, b) => (a + b) * 2;

    對應兩個值求差,然后將值加倍:

    const doubleSubtraction = (a, b) => (a - b) * 2

    這些函數具有相似的邏輯,但區別在于運算符的功能。如果我們可以將函數視為值并將它們作為參數傳遞,我們可以構建一個接收運算符函數并在函數內部使用它的函數。

    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 必須為以下三種狀態之一:等待態(Pending)、執行態(Fulfilled)和拒絕態(Rejected)。一旦Promise 被  resolve 或 reject,不能再遷移至其他任何狀態(即狀態 immutable)。

    基本過程:

    初始化 Promise 狀態(pending)

    執行 then(..) 注冊回調處理數組(then 方法可被同一個 promise 調用多次)

    立即執行 Promise 中傳入的 fn 函數,將Promise 內部 resolve、reject 函數作為參數傳遞給 fn  ,按事件機制時機處理

    Promise中要保證,then方法傳入的參數 onFulfilled 和  onRejected,必須在then方法被調用的那一輪事件循環之后的新執行棧中執行。

    真正的鏈式Promise是指在當前promise達到fulfilled狀態后,即開始進行下一個promise.

    跨域

    因為瀏覽器的同源策略導致了跨域。同源策略是一個重要的安全策略,它用于限制一個origin的文檔或者它加載的腳本如何能與另一個源的資源進行交互。它能幫助阻隔惡意文檔,減少可能被攻擊的媒介。

    所謂同源是指"協議+域名+端口"三者相同。不同協議,不同域名,不同端口都會構成跨域。

    跨域解決方案

    1. jsonp: 需要服務器配合一個callback函數

    2. CORS: 需要服務器設置header :Access-Control-Allow-Origin

    3. window.name + iframe: 需要目標服務器響應window.name。

    4. document.domain : 僅限主域相同,子域不同的跨域應用場景。

    5. html5的 postMessage + iframe: 需要服務器或者目標頁面寫一個postMessage,主要側重于前端通訊。

    6. nginx反向代理: 不用服務器配合,需要搭建一個中轉nginx服務器,用于轉發請求。

    jsonp跨域

    在HTML標簽里,一些標簽比如script、img這樣的獲取資源的標簽是沒有跨域限制的。通過動態創建script,再請求一個帶參網址實現跨域通信。

    • 需要前后端配合使用。一般后端設置callback ,前端給后臺接口中傳一個callback 即可。

    • 只能實現get一種請求。

    栗子

    前端代碼:

    <script>     var script = document.createElement('script');     script.type = 'text/javascript';      // 傳參一個回調函數名給后端,方便后端返回時執行這個在前端定義的回調函數     script.src = 'http://xxxxxxx:8080/login?callback=handleCallback';     document.head.appendChild(script);      function handleCallback(res) {         alert(JSON.stringify(res));     } </script>

    后臺代碼:

    <?php   $callback = $_GET['callback'];//得到回調函數名   $data = array('a','b','c');//要返回的數據   echo $callback.'('.json_encode($data).')';//輸出 ?>

    CORS - 跨域資源共享

    CORS是一個W3C標準,全稱是"跨域資源共享"(Cross-origin resource sharing)。

    CORS有兩種請求,簡單請求和非簡單請求。只要同時滿足以下兩大條件,就屬于簡單請求。

    1. 鴻蒙官方戰略合作共建——HarmonyOS技術社區

    2. 請求方法是以下三種方法之一:HEAD,GET,POST

    3. HTTP的頭信息不超出以下幾種字段:Accept,Accept-Language,Content-Language,Last-Event-ID,Content-Type【只限于三個值application/x-www-form-urlencoded、multipart/form-data、text/plain】,沒有自定義的HTTP頭部。

    簡單請求

    1. 鴻蒙官方戰略合作共建——HarmonyOS技術社區

    2. 瀏覽器:把客戶端腳本所在的域填充到Origin header里,向其他域的服務器請求資源。

    3. 服務器:根據資源權限配置,在響應頭中添加Access-Control-Allow-Origin Header,返回結果。

    4. 瀏覽器:比較服務器返回的Access-Control-Allow-Origin  Header和請求域的Origin。如果當前域已經得到授權,則將結果返回給頁面。否則瀏覽器忽略此次響應。

    5. 網頁:收到返回結果或者瀏覽器的錯誤提示。

    對于簡單的跨域請求,只要服務器設置的Access-Control-Allow-Origin  Header和請求來源匹配,瀏覽器就允許跨域。服務器端設置的`Access-Control-Allow-Methods和Access-Control-Allow-Headers對簡單跨域沒有作用。

    非簡單請求

    1. 鴻蒙官方戰略合作共建——HarmonyOS技術社區

    2. 瀏覽器:先向服務器發送一個OPTIONS預檢請求,檢測服務器端是否支持真實請求進行跨域資源訪問,瀏覽器會在發送OPTIONS請求時會自動添加Origin  Header 、Access-Control-Request-Method Header和Access-Control-Request-Headers  Header。

    3. 服務器:響應OPTIONS請求,會在responseHead里添加Access-Control-Allow-Methods  head。這其中的method的值是服務器給的默認值,可能不同的服務器添加的值不一樣。服務器還會添加Access-Control-Allow-Origin  Header和Access-Control-Allow-Headers  Header。這些取決于服務器對OPTIONS請求具體如何做出響應。如果服務器對OPTIONS響應不合你的要求,你可以手動在服務器配置OPTIONS響應,以應對帶預檢的跨域請求。在配置服務器OPTIONS的響應時,可以添加Access-Control-Max-Age  head告訴瀏覽器在一定時間內無需再次發送預檢請求,但是如果瀏覽器禁用緩存則無效。

    4. 瀏覽器:接到OPTIONS的響應,比較真實請求的method是否屬于返回的Access-Control-Allow-Methods  head的值之一,還有origin, head也會進行比較是否匹配。如果通過,瀏覽器就繼續向服務器發送真實請求,  否則就會報預檢錯誤:請求來源不被options響應允許,請求方法不被options響應允許或請求中有自定義header不被options響應允許。

    5. 服務器:響應真實請求,在響應頭中放入Access-Control-Allow-Origin  Header、Access-Control-Allow-Methods和Access-Control-Allow-Headers  Header,分別表示允許跨域資源請求的域、請求方法和請求頭,并返回數據。

    6. 瀏覽器:接受服務器對真實請求的返回結果,返回給網頁

    7. 網頁:收到返回結果或者瀏覽器的錯誤提示。

    Access-Control-Allow-Origin在響應options請求和響應真實請求時都是有作用的,兩者必須同時包含要跨域的源。  Access-Control-Allow-Methods和Access-Control-Allow-Headers只在響應options請求時有作用。

    攜帶cookie

    在 CORS 跨域中,瀏覽器并不會自動發送 Cookie。對于普通跨域請求只需服務端設置,而帶cookie跨域請求前后端都需要設置。

    瀏覽器,對于跨域請求,需要設置withCredentials 屬性為 true。服務端的響應中必須攜帶 Access-Control-Allow-Credentials: true 。

    除了Access-Control-Allow-Credentials之外,跨域發送 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只允許單一域名, 服務器可能需要維護一個接受 Cookie 的 Origin 列表, 驗證  Origin 請求頭字段后直接將其設置為Access-Control-Allow-Origin的值。在 CORS 請求被重定向后 Origin 頭字段會被置為  null, 此時可以選擇從Referer頭字段計算得到Origin。

    具體實現

    服務器端的響應頭配置

    Access-Control-Allow-Origin 可以設置為* ,表示可以與任意域進行數據共享。

    // 設置服務器接受跨域的域名 "Access-Control-Allow-Origin": "http://127.0.0.1:8080", // 設置服務器接受跨域的請求方法 'Access-Control-Allow-Methods': 'OPTIONS,HEAD,DELETE,GET,PUT,POST', // 設置服務器接受跨域的headers 'Access-Control-Allow-Headers': 'x-requested-with, accept, origin, content-type', // 設置服務器不用再次預檢請求時間 'Access-Control-Max-Age': 10000, // 設置服務器接受跨域發送Cookie 'Access-Control-Allow-Credentials': true

    document.domain

    此方案僅限主域相同,子域不同的跨域應用場景。

    實現原理:兩個頁面都通過js強制設置document.domain為基礎主域,就實現了同域。

    栗子:

    在父頁面 http://xxx.com/a.html 中設置document.domain

    <iframe id = "iframe" src="http://xxx.com/b.html" onload = "test()"></iframe> <script type="text/javascript">     document.domain = 'xxx.com';//設置成主域     function test(){        alert(document.getElementById('?iframe').contentWindow);        //contentWindow 可取得子窗口的 window 對象     } </script>

    在子頁面http://xxx.com/b.html 中設置document.domain

    <script type="text/javascript">     document.domain = 'xxx.com';     //在iframe載入這個頁面也設置document.domain,使之與主頁面的document.domain相同 </script>

    window.postMessage

    window.postMessage是html5的功能,是客戶端和客戶端直接的數據傳遞,既可以跨域傳遞,也可以同域傳遞。

    postMessage(data, origin)方法接受兩個參數:

    • data:html5規范支持任意基本類型或可復制的對象,但部分瀏覽器只支持字符串,所以傳參時最好用JSON.stringify()序列化。

    • origin:協議+主機+端口號,也可以設置為"*",表示可以傳遞給任意窗口,如果要指定和當前窗口同源的話設置為"/"。

    栗子:

    假如有一個頁面,頁面中拿到部分用戶信息,點擊進入另外一個頁面,另外的頁面默認是取不到用戶信息的,你可以通過window.postMessage把部分用戶信息傳到這個頁面中。(需要考慮安全性等方面。)

    發送消息:

    // 彈出一個新窗口 var domain = 'http://haorooms.com'; var myPopup = window.open(`${domain}/windowPostMessageListener.html`,'myWindow');  // 發送消息 setTimeout(function(){   var message = {name:"站點",sex:"男"};   console.log('傳遞的數據是  ' + message);   myPopup.postMessage(message, domain); }, 1000);

    接收消息:

    // 監聽消息反饋 window.addEventListener('message', function(event) {   // 判斷域名是否正確   if (event.origin !== 'http://haorooms.com') return;   console.log('received response: ', event.data); }, false);

    如下圖,接受頁面得到數據

    有關前端基礎知識整理匯總

    如果是使用iframe,代碼應該這樣寫:

    // 捕獲iframe var domain = 'http://haorooms.com'; var iframe = document.getElementById('myIFrame').contentWindow;  // 發送消息 setTimeout(function(){      var message = {name:"站點",sex:"男"};     console.log('傳遞的數據是:  ' + message);     iframe.postMessage(message, domain);  },1000);

    接收數據并反饋信息:

    // 響應事件 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 &ndash; 消息源,消息的發送窗口/iframe。

    • origin &ndash; 消息源的URI(可能包含協議、域名和端口),用來驗證數據源。

    • data &ndash; 發送方發送給接收方的數據。

    • window.name

    原理:

    window對象有個name屬性,該屬性有個特征:即在一個窗口(window)的生命周期內,窗口載入的所有的頁面都是共享一個window.name,每個頁面對window.name都有讀寫的權限,window.name是持久存在一個窗口載入過的所有頁面中的。

    栗子:

    在子頁面(b.com/data.html) 設置window.name:

    /* b.com/data.html */ <script type="text/javascript">    window.name = 'I was there!';        // 這里是要傳輸的數據,大小一般為2M,IE和firefox下可以大至32M左右    // 數據格式可以自定義,如json、字符串 </script>

    在父頁面(a.com/app.html)中創建一個iframe,把其src指向子頁面。在父頁面監聽iframe的onload事件,獲取子頁面數據:

    /* 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);       // 獲取數據以后銷毀iframe,釋放內存;這也保證了安全(不被其他域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用于把請求代理轉發到其他服務器的中間件。

    安裝:

    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, // 接受運行在https上的服務         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 訪問本地服務   return res.json(); });

    nginx反向代理

    反向代理(Reverse  Proxy)方式是指以代理服務器來接受客戶端的連接請求,然后將請求轉發給內部網絡上的服務器;并將從服務器上得到的結果返回給客戶端,此時代理服務器對外就表現為一個服務器。

    反向代理服務器對于客戶端而言它就像是原始服務器,并且客戶端不需要進行任何特別的設置。客戶端向反向代理  的命名空間(name-space)中的內容發送普通請求,接著反向代理將判斷向何處(原始服務器)轉交請求,并將獲得的內容返回給客戶端,就像這些內容  原本就是它自己的一樣。

    模塊化

    AMD/CMD/CommonJs都是JS模塊化開發的標準,目前對應的實現是RequireJS,SeaJs, nodeJs;

    CommonJS:服務端js

    CommonJS 是以在瀏覽器環境之外構建 javaScript 生態系統為目標而產生的寫一套規范,主要是為了解決 javaScript  的作用域問題而定義的模塊形式,可以使每個模塊它自身的命名空間中執行。

    實現方法:模塊必須通過 module.exports 導出對外的變量或者接口,通過 require() 來導入其他模塊的輸出到當前模塊的作用域中;

    主要針對服務端(同步加載文件)和桌面環境中,node.js 遵循的是 CommonJS 的規范;CommonJS  加載模塊是同步的,所以只有加載完成才能執行后面的操作。

    • require()用來引入外部模塊;

    • exports對象用于導出當前模塊的方法或變量,唯一的導出口;

    • 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");  //調用這個模塊,不同的返回方式用不同的方式調用 // 1.第一種調用方式 module(); // 2.第二種調用方式  module.test(); // 3.第三種調用方式  module.test();  // 執行文件 node test.js

    AMD:異步模塊定義【瀏覽器端js】

    AMD 是 Asynchronous Module Definition  的縮寫,意思是異步模塊定義;采用的是異步的方式進行模塊的加載,在加載模塊的時候不影響后邊語句的運行。主要是為前端 js 的表現指定的一套規范。

    實現方法:通過define方法去定義模塊,通過require方法去加載模塊。

    define(id?,dependencies?,factory):  它要在聲明模塊的時候制定所有的依賴(dep),并且還要當做形參傳到factory中。沒什么依賴,就定義簡單的模塊(或者叫獨立的模塊)

    require([modules], callback):  第一個參數[modules],是需加載的模塊名數組;第二個參數callback,是模塊加載成功之后的回調函數

    主要針對瀏覽器js,requireJs遵循的是 AMD 的規范;

    // 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(); });   // 為避免造成網頁失去響應,解決辦法有兩個,一個是把它放在網頁底部加載,另一個是寫成下面這樣: <script src="js/require.js" defer async="true" ></script> // async屬性表明這個文件需要異步加載,避免網頁失去響應。 // IE不支持這個屬性,只支持defer,所以把defer也寫上。  // data-main屬性: 指定網頁程序的主模塊 <script data-main="main" src="js/require.js"></script>  // 控制臺輸出結果 我是module1的methodA 我是module2的methodB

    CMD:通用模塊定義【瀏覽器端js】

    CMD 是 Common Module Definition  的縮寫,通過異步的方式進行模塊的加載的,在加載的時候會把模塊變為字符串解析一遍才知道依賴了哪個模塊;

    主要針對瀏覽器端(異步加載文件),按需加載文件。對應的實現是seajs

    AMD和CMD的區別

    1. 鴻蒙官方戰略合作共建——HarmonyOS技術社區

    2. 對于依賴的模塊,AMD 是提前執行,CMD 是延遲執行。不過 RequireJS 從 2.0  開始,也改成可以延遲執行(根據寫法不同,處理方式不同)。CMD 推崇 as lazy as  possible(盡可能的懶加載,也稱為延遲加載,即在需要的時候才加載)。

    3. 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區別

    import和require都是被模塊化使用。

    • require是CommonJs的語法(AMD規范引入方式),CommonJs的模塊是對象。import是es6的一個語法標準(瀏覽器不支持,本質是使用node中的babel將es6轉碼為es5再執行,import會被轉碼為require),es6模塊不是對象。

    • require是運行時加載整個模塊(即模塊中所有方法),生成一個對象,再從對象上讀取它的方法(只有運行時才能得到這個對象,不能在編譯時做到靜態化),理論上可以用在代碼的任何地方。import是編譯時調用,確定模塊的依賴關系,輸入變量(es6模塊不是對象,而是通過export命令指定輸出代碼,再通過import輸入,只加載import中導的方法,其他方法不加載),import具有提升效果,會提升到模塊的頭部(編譯時執行)

    export和import可以位于模塊中的任何位置,但是必須是在模塊頂層,如果在其他作用域內,會報錯(es6這樣的設計可以提高編譯器效率,但沒法實現運行時加載)。

    require是賦值過程,把require的結果(對象,數字,函數等),默認是export的一個對象,賦給某個變量(復制或淺拷貝)。import是解構過程(需要誰,加載誰)。

    require/exports:

    // require: 真正被require出來的是來自module.exports指向的內存塊內容 const a = require('a') //  // exports: 只是 module.exports的引用,輔助module.exports操作內存中的數據 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:超文本傳輸協議(Http,HyperText Transfer  Protocol)是互聯網上應用最為廣泛的一種網絡協議。設計Http最初的目的是為了提供一種發布和接收HTML頁面的方法。它可以使瀏覽器更加高效。

    Http協議是以明文方式發送信息的,如果黑客截取了Web瀏覽器和服務器之間的傳輸報文,就可以直接獲得其中的信息。

    Https:是以安全為目標的Http通道,是Http的安全版。Https的安全基礎是SSL。SSL協議位于TCP/IP協議與各種應用層協議之間,為數據通訊提供安全支持。SSL協議可分為兩層:SSL記錄協議(SSL  Record Protocol),它建立在可靠的傳輸協議(如TCP)之上,為高層協議提供數據封裝、壓縮、加密等基本功能的支持。

    SSL握手協議(SSL Handshake  Protocol),它建立在SSL記錄協議之上,用于在實際的數據傳輸開始前,通訊雙方進行身份認證、協商加密算法、交換加密密鑰等。

    HTTP與HTTPS的區別

    1、HTTP是超文本傳輸協議,信息是明文傳輸,HTTPS是具有安全性的SSL加密傳輸協議。

    2、HTTPS協議需要ca申請證書,一般免費證書少,因而需要一定費用。

    3、HTTP和HTTPS使用的是完全不同的連接方式,用的端口也不一樣。前者是80,后者是443。

    4、HTTP連接是無狀態的,HTTPS協議是由SSL+HTTP協議構建的可進行加密傳輸、身份認證的網絡協議,安全性高于HTTP協議。

    https的優點

    盡管HTTPS并非絕對安全,掌握根證書的機構、掌握加密算法的組織同樣可以進行中間人形式的攻擊,但HTTPS仍是現行架構下最安全的解決方案,主要有以下幾個好處:

    1)使用HTTPS協議可認證用戶和服務器,確保數據發送到正確的客戶機和服務器;

    2)HTTPS協議是由SSL+HTTP協議構建的可進行加密傳輸、身份認證的網絡協議,要比http協議安全,可防止數據在傳輸過程中不被竊取、改變,確保數據的完整性。

    3)HTTPS是現行架構下最安全的解決方案,雖然不是絕對安全,但它大幅增加了中間人攻擊的成本。

    4)谷歌曾在2014年8月份調整搜索引擎算法,并稱“比起同等HTTP網站,采用HTTPS加密的網站在搜索結果中的排名將會更高”。

    Https的缺點

    1)Https協議握手階段比較費時,會使頁面的加載時間延長近。

    2)Https連接緩存不如Http高效,會增加數據開銷,甚至已有的安全措施也會因此而受到影響;

    3)SSL證書通常需要綁定IP,不能在同一IP上綁定多個域名,IPv4資源不可能支撐這個消耗。

    4)Https協議的加密范圍也比較有限。最關鍵的,SSL證書的信用鏈體系并不安全,特別是在某些國家可以控制CA根證書的情況下,中間人攻擊一樣可行。

    遍歷方法

    for

    在for循環中,循環取得數組或是數組類似對象的值,譬如arguments和HTMLCollection對象。

    不足:

    • 在于每次循環的時候數組的長度都要去獲取;

    • 終止條件要明確;

    foreach(),map()

    兩個方法都可以遍歷到數組的每個元素,而且參數一致;

    forEach(): 對數組的每個元素執行一次提供的函數, 總是返回undefined;

    map(): 創建一個新數組,其結果是該數組中的每個元素都調用一個提供的函數后返回的結果。返回值是一個新的數組;

    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

    經常用來迭代對象的屬性或數組的每個元素,它包含當前屬性的名稱或當前數組元素的索引。

    當遍歷一個對象的時候,變量 i 是循環計數器 為 對象的屬性名, 以任意順序遍歷一個對象的可枚舉屬性。對于每個不同的屬性,語句都會被執行。

    當遍歷一個數組的時候,變量 i 是循環計數器 為 當前數組元素的索引

    不足:

    for..in循環會把某個類型的原型(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 &hellip; of

    迭代循環可迭代對象(包括Array,Map,Set,String,TypedArray,arguments  對象)等等。不能遍歷對象。只循環集合本身的元素

    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 創建新實例對象經過了以下幾步:

    1.創建一個新對象

    2.將新對象的_proto_指向構造函數的prototype對象

    3.將構造函數的作用域賦值給新對象 (也就是this指向新對象)

    4.執行構造函數中的代碼(為這個新對象添加屬性)

    5.返回新的對象

    // 1. 創建一個新對象 var Obj = {}; // 2. 將新對象的_proto_指向構造函數的prototype對象 Obj._proto_ =  Animal.prototype(); // 3. 執行構造函數中的代碼(為這個新對象添加屬性)  Animal.call(Obj); // 4. 返回新的對象 return Obj;

    特點:

    1.實例可繼承的屬性有:實例的構造函數的屬性,父類構造函數屬性,父類原型的屬性

    2.非常純粹的繼承關系,實例是子類的實例,也是父類的實例

    3.父類新增原型方法/原型屬性,子類都能訪問到

    缺點:

    1.新實例無法向父類構造函數傳參。

    2.繼承單一。

    3.所有新實例都會共享父類實例的屬性。(原型上的屬性是共享的,一個實例修改了原型屬性,另一個實例的原型屬性也會被修改!)

    4.要想為子類新增原型上的屬性和方法,必須要在new Animal()這樣的語句之后執行,不能放到構造器中

    構造函數繼承

    核心:使用父類的構造函數來增強子類實例,等于是復制父類的實例屬性給子類(沒用到原型)

    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()將父類構造函數引入子類函數(在子類函數中做了父類函數的自執行(復制))

    特點:

    1.只繼承了父類構造函數的屬性,沒有繼承父類原型的屬性。

    2.解決了原型鏈繼承缺點1、2、3。

    3.可以實現多繼承,繼承多個構造函數屬性(call多個)。

    4.在子實例中可向父實例傳參。

    缺點:

    1.能繼承父類構造函數的屬性。

    2.無法實現構造函數的復用。(每次用每次都要重新調用)

    3.每個新實例都有父類構造函數的副本,臃腫。

    4.實例并不是父類的實例,只是子類的實例

    組合繼承(原型鏈繼承和構造函數繼承)(常用)

    核心:通過調用父類構造,繼承父類的屬性并保留傳參的優點,然后通過將父類實例作為子類原型,實現函數復用

    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

    重點:結合了兩種模式的優點,傳參和復用

    特點:

    1.可以繼承父類原型上的屬性,可以傳參,可復用。

    2.每個新實例引入的構造函數屬性是私有的。

    3.既是子類的實例,也是父類的實例

    缺點:

    調用了兩次父類構造函數(耗內存),子類的構造函數會代替原型上的那個父類構造函數。

    原型式繼承

     有關前端基礎知識整理匯總

    重點:用一個函數包裝一個對象,然后返回這個函數的調用,這個函數就變成了個可以隨意增添屬性的實例或對象。object.create()就是這個原理。

    特點:

    類似于復制一個對象,用函數來包裝。

    缺點:

    1.所有實例都會繼承原型上的屬性。

    2.無法實現復用。(新實例屬性都是后面添加的)

    寄生式繼承

    有關前端基礎知識整理匯總

    重點:就是給原型式繼承外面套了個殼子。

    優點:沒有創建自定義類型,因為只是套了個殼子返回對象(這個),這個函數順理成章就成了創建的新對象。

    缺點:沒用到原型,無法復用。

    寄生組合式繼承(常用)

    寄生:在函數內返回對象然后調用

    組合:

    1、函數的原型等于另一個實例。

    2、在函數中用apply或者call引入另一個構造函數,可傳參。

    function Cat(name){   Animal.call(this);   this.name = name || 'Tom'; } (function(){   // 創建一個沒有實例方法的類   var Super = function(){};   Super.prototype = Animal.prototype;   //將實例作為子類的原型   Cat.prototype = new Super(); })();  var cat = new Cat(); Cat.prototype.constructor = Cat; // 需要修復下構造函數 console.log(cat.name); console.log(cat.sleep()); console.log(cat instanceof Animal); // true console.log(cat instanceof Cat); //true

    到此,關于“有關前端基礎知識整理匯總”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

成武县| 平远县| 会东县| 巴林右旗| 临沂市| 丁青县| 儋州市| 荣昌县| 隆回县| 龙游县| 迭部县| 祁东县| 齐齐哈尔市| 九寨沟县| 南平市| 五原县| 华坪县| 洪洞县| 五莲县| 吉隆县| 务川| 金湖县| 茶陵县| 岑溪市| 清涧县| 大关县| 定日县| 晋宁县| 盐池县| 山丹县| 玉溪市| 灌阳县| 永泰县| 康保县| 泸水县| 鹿邑县| 淳安县| 独山县| 武义县| 开江县| 齐河县|