最近在整理 JavaScript 的時候發現遇到了不少面試中常見的面試題,本部分主要是做者在 Github 等各大論壇收錄的 JavaScript 相關知識和一些相關面試題時所作的筆記,分享這份總結給你們,對你們對 JavaScript 的能夠來一次全方位的檢漏和排查,感謝原做者 CavsZhouyou 的付出,原文連接放在文章最下方,若是出現錯誤,但願你們共同指出!更多往期面試整理可移步:javascript
若是文章和筆記能帶您一絲幫助或者啓發,請不要吝嗇你的贊和收藏,你的確定是我前進的最大動力 😁css
js 一共有六種基本數據類型,分別是 Undefined、Null、Boolean、Number、String,還有在 ES6 中新增的 Symbol 類型, 表明建立後獨一無二且不可變的數據類型,它的出現我認爲主要是爲了解決可能出現的全局變量衝突的問題。
涉及知識點:html
兩種類型的區別是:存儲位置不一樣。 原始數據類型直接存儲在棧(stack)中的簡單數據段,佔據空間小、大小固定,屬於被頻繁使用數據,因此放入棧中存儲。 引用數據類型存儲在堆(heap)中的對象,佔據空間大、大小不固定。若是存儲在棧中,將會影響程序運行的性能;引用數據類型在 棧中存儲了指針,該指針指向堆中該實體的起始地址。當解釋器尋找引用值時,會首先檢索其在棧中的地址,取得地址後從堆中得到實 體。
回答:前端
js 能夠分爲兩種類型的值,一種是基本數據類型,一種是複雜數據類型。 基本數據類型....(參考1) 複雜數據類型指的是 Object 類型,全部其餘的如 Array、Date 等數據類型均可以理解爲 Object 類型的子類。 兩種類型間的主要區別是它們的存儲位置不一樣,基本數據類型的值直接保存在棧中,而複雜數據類型的值保存在堆中,經過使用在棧中 保存對應的指針來獲取堆中的值。
詳細資料能夠參考:
《JavaScript 有幾種類型的值?》
《JavaScript 有幾種類型的值?可否畫一下它們的內存圖;》vue
堆和棧的概念存在於數據結構中和操做系統內存中。 在數據結構中,棧中數據的存取方式爲先進後出。而堆是一個優先隊列,是按優先級來進行排序的,優先級能夠按照大小來規定。徹底 二叉樹是堆的一種實現方式。 在操做系統中,內存被分爲棧區和堆區。 棧區內存由編譯器自動分配釋放,存放函數的參數值,局部變量的值等。其操做方式相似於數據結構中的棧。 堆區內存通常由程序員分配釋放,若程序員不釋放,程序結束時可能由垃圾回收機制回收。
詳細資料能夠參考:
《什麼是堆?什麼是棧?他們之間有什麼區別和聯繫?》html5
全部 typeof 返回值爲 "object" 的對象(如數組)都包含一個內部屬性 [[Class]](咱們能夠把它看做一個內部的分類,而非 傳統的面向對象意義上的類)。這個屬性沒法直接訪問,通常經過 Object.prototype.toString(..) 來查看。例如: Object.prototype.toString.call( [1,2,3] ); // "[object Array]" Object.prototype.toString.call( /regex-literal/i ); // "[object RegExp]"
涉及知識點:java
全局的對象( global objects )或稱標準內置對象,不要和 "全局對象(global object)" 混淆。這裏說的全局的對象是說在 全局做用域裏的對象。全局做用域中的其餘對象能夠由用戶的腳本建立或由宿主程序提供。 標準內置對象的分類 (1)值屬性,這些全局屬性返回一個簡單值,這些值沒有本身的屬性和方法。 例如 Infinity、NaN、undefined、null 字面量 (2)函數屬性,全局函數能夠直接調用,不須要在調用時指定所屬對象,執行結束後會將結果直接返回給調用者。 例如 eval()、parseFloat()、parseInt() 等 (3)基本對象,基本對象是定義或使用其餘對象的基礎。基本對象包括通常對象、函數對象和錯誤對象。 例如 Object、Function、Boolean、Symbol、Error 等 (4)數字和日期對象,用來表示數字、日期和執行數學計算的對象。 例如 Number、Math、Date (5)字符串,用來表示和操做字符串的對象。 例如 String、RegExp (6)可索引的集合對象,這些對象表示按照索引值來排序的數據集合,包括數組和類型數組,以及類數組結構的對象。例如 Array (7)使用鍵的集合對象,這些集合對象在存儲數據時會使用到鍵,支持按照插入順序來迭代元素。 例如 Map、Set、WeakMap、WeakSet (8)矢量集合,SIMD 矢量集合中的數據會被組織爲一個數據序列。 例如 SIMD 等 (9)結構化數據,這些對象用來表示和操做結構化的緩衝區數據,或使用 JSON 編碼的數據。 例如 JSON 等 (10)控制抽象對象 例如 Promise、Generator 等 (11)反射 例如 Reflect、Proxy (12)國際化,爲了支持多語言處理而加入 ECMAScript 的對象。 例如 Intl、Intl.Collator 等 (13)WebAssembly (14)其餘 例如 arguments
回答:node
js 中的內置對象主要指的是在程序執行前存在全局做用域裏的由 js 定義的一些全局值屬性、函數和用來實例化其餘對象的構造函 數對象。通常咱們常常用到的如全局變量值 NaN、undefined,全局函數如 parseInt()、parseFloat() 用來實例化對象的構 造函數如 Date、Object 等,還有提供數學計算的單體內置對象如 Math 對象。
詳細資料能夠參考:
《標準內置對象的分類》
《JS 全部內置對象屬性和方法彙總》webpack
已在做用域中聲明但尚未賦值的變量,是 undefined 的。相反,尚未在做用域中聲明過的變量,是 undeclared 的。 對於 undeclared 變量的引用,瀏覽器會報引用錯誤,如 ReferenceError: b is not defined 。可是咱們能夠使用 typ eof 的安全防範機制來避免報錯,由於對於 undeclared(或者 not defined )變量,typeof 會返回 "undefined"。
首先 Undefined 和 Null 都是基本數據類型,這兩個基本數據類型分別都只有一個值,就是 undefined 和 null。 undefined 表明的含義是未定義,null 表明的含義是空對象。通常變量聲明瞭但尚未定義的時候會返回 undefined,null 主要用於賦值給一些可能會返回對象的變量,做爲初始化。 undefined 在 js 中不是一個保留字,這意味着咱們能夠使用 undefined 來做爲一個變量名,這樣的作法是很是危險的,它 會影響咱們對 undefined 值的判斷。可是咱們能夠經過一些方法得到安全的 undefined 值,好比說 void 0。 當咱們對兩種類型使用 typeof 進行判斷的時候,Null 類型化會返回 「object」,這是一個歷史遺留的問題。當咱們使用雙等 號對兩種類型的值進行比較時會返回 true,使用三個等號時會返回 false。
詳細資料能夠參考:
《JavaScript 深刻理解之 undefined 與 null》ios
由於 undefined 是一個標識符,因此能夠被看成變量來使用和賦值,可是這樣會影響 undefined 的正常判斷。 表達式 void ___ 沒有返回值,所以返回結果是 undefined。void 並不改變表達式的結果,只是讓表達式不返回值。 按慣例咱們用 void 0 來得到 undefined。
在日常項目開發中,咱們遵照一些這樣的基本規範,好比說: (1)一個函數做用域中全部的變量聲明應該儘可能提到函數首部,用一個 var 聲明,不容許出現兩個連續的 var 聲明,聲明時 若是變量沒有值,應該給該變量賦值對應類型的初始值,便於他人閱讀代碼時,可以一目瞭然的知道變量對應的類型值。 (2)代碼中出現地址、時間等字符串時須要使用常量代替。 (3)在進行比較的時候吧,儘可能使用'===', '!=='代替'==', '!='。 (4)不要在內置對象的原型上添加方法,如 Array, Date。 (5)switch 語句必須帶有 default 分支。 (6)for 循環必須使用大括號。 (7)if 語句必須使用大括號。
在 js 中咱們是使用構造函數來新建一個對象的,每個構造函數的內部都有一個 prototype 屬性值,這個屬性值是一個對 象,這個對象包含了能夠由該構造函數的全部實例共享的屬性和方法。當咱們使用構造函數新建一個對象後,在這個對象的內部 將包含一個指針,這個指針指向構造函數的 prototype 屬性對應的值,在 ES5 中這個指針被稱爲對象的原型。通常來講咱們 是不該該可以獲取到這個值的,可是如今瀏覽器中都實現了 __proto__ 屬性來讓咱們訪問這個屬性,可是咱們最好不要使用這 個屬性,由於它不是規範中規定的。ES5 中新增了一個 Object.getPrototypeOf() 方法,咱們能夠經過這個方法來獲取對 象的原型。 當咱們訪問一個對象的屬性時,若是這個對象內部不存在這個屬性,那麼它就會去它的原型對象裏找這個屬性,這個原型對象又 會有本身的原型,因而就這樣一直找下去,也就是原型鏈的概念。原型鏈的盡頭通常來講都是 Object.prototype 因此這就 是咱們新建的對象爲何可以使用 toString() 等方法的緣由。 特色: JavaScript 對象是經過引用來傳遞的,咱們建立的每一個新對象實體中並無一份屬於本身的原型副本。當咱們修改原型時,與 之相關的對象也會繼承這一改變。
詳細資料能夠參考:
《JavaScript 深刻理解之原型與原型鏈》
安全整數指的是,在這個範圍內的整數轉化爲二進制存儲的時候不會出現精度丟失,可以被「安全」呈現的最大整數是 2^53 - 1, 即9007199254740991,在 ES6 中被定義爲 Number.MAX_SAFE_INTEGER。最小整數是-9007199254740991,在 ES6 中 被定義爲 Number.MIN_SAFE_INTEGER。 若是某次計算的結果獲得了一個超過 JavaScript 數值範圍的值,那麼這個值會被自動轉換爲特殊的 Infinity 值。若是某次 計算返回了正或負的 Infinity 值,那麼該值將沒法參與下一次的計算。判斷一個數是否是有窮的,能夠使用 isFinite 函數 來判斷。
NaN 意指「不是一個數字」(not a number),NaN 是一個「警惕值」(sentinel value,有特殊用途的常規值),用於指出 數字類型中的錯誤狀況,即「執行數學運算沒有成功,這是失敗後返回的結果」。 typeof NaN; // "number" NaN 是一個特殊值,它和自身不相等,是惟一一個非自反(自反,reflexive,即 x === x 不成立)的值。而 NaN != NaN 爲 true。
函數 isNaN 接收參數後,會嘗試將這個參數轉換爲數值,任何不能被轉換爲數值的的值都會返回 true,所以非數字值傳入也會 返回 true ,會影響 NaN 的判斷。 函數 Number.isNaN 會首先判斷傳入參數是否爲數字,若是是數字再繼續判斷是否爲 NaN ,這種方法對於 NaN 的判斷更爲 準確。
Array 構造函數只帶一個數字參數的時候,該參數會被做爲數組的預設長度(length),而非只充當數組中的一個元素。這樣 建立出來的只是一個空數組,只不過它的 length 屬性被設置成了指定的值。 構造函數 Array(..) 不要求必須帶 new 關鍵字。不帶時,它會被自動補上。
規範的 9.8 節中定義了抽象操做 ToString ,它負責處理非字符串到字符串的強制類型轉換。 (1)Null 和 Undefined 類型 ,null 轉換爲 "null",undefined 轉換爲 "undefined", (2)Boolean 類型,true 轉換爲 "true",false 轉換爲 "false"。 (3)Number 類型的值直接轉換,不過那些極小和極大的數字會使用指數形式。 (4)Symbol 類型的值直接轉換,可是隻容許顯式強制類型轉換,使用隱式強制類型轉換會產生錯誤。 (3)對普通對象來講,除非自行定義 toString() 方法,不然會調用 toString()(Object.prototype.toString()) 來返回內部屬性 [[Class]] 的值,如"[object Object]"。若是對象有本身的 toString() 方法,字符串化時就會 調用該方法並使用其返回值。
有時咱們須要將非數字值看成數字來使用,好比數學運算。爲此 ES5 規範在 9.3 節定義了抽象操做 ToNumber。 (1)Undefined 類型的值轉換爲 NaN。 (2)Null 類型的值轉換爲 0。 (3)Boolean 類型的值,true 轉換爲 1,false 轉換爲 0。 (4)String 類型的值轉換如同使用 Number() 函數進行轉換,若是包含非數字值則轉換爲 NaN,空字符串爲 0。 (5)Symbol 類型的值不能轉換爲數字,會報錯。 (6)對象(包括數組)會首先被轉換爲相應的基本類型值,若是返回的是非數字的基本類型值,則再遵循以上規則將其強制轉換爲數字。 爲了將值轉換爲相應的基本類型值,抽象操做 ToPrimitive 會首先(經過內部操做 DefaultValue)檢查該值是否有valueOf() 方法。若是有而且返回基本類型值,就使用該值進行強制類型轉換。若是沒有就使用 toString() 的返回值(若是存在)來進行強制類型轉換。 若是 valueOf() 和 toString() 均不返回基本類型值,會產生 TypeError 錯誤。
ES5 規範 9.2 節中定義了抽象操做 ToBoolean,列舉了布爾強制類型轉換全部可能出現的結果。 如下這些是假值: • undefined • null • false • +0、-0 和 NaN • "" 假值的布爾強制類型轉換結果爲 false。從邏輯上說,假值列表之外的都應該是真值。
{} 的 valueOf 結果爲 {} ,toString 的結果爲 "[object Object]" [] 的 valueOf 結果爲 [] ,toString 的結果爲 ""
瀏覽器在某些特定狀況下,在常規 JavaScript 語法基礎上本身建立了一些外來值,這些就是「假值對象」。假值對象看起來和 普通對象並沒有二致(都有屬性,等等),但將它們強制類型轉換爲布爾值時結果爲 false 最多見的例子是 document.all,它 是一個類數組對象,包含了頁面上的全部元素,由 DOM(而不是 JavaScript 引擎)提供給 JavaScript 程序使用。
~ 返回 2 的補碼,而且 ~ 會將數字轉換爲 32 位整數,所以咱們能夠使用 ~ 來進行取整操做。 ~x 大體等同於 -(x+1)。
解析容許字符串(如 parseInt() )中含有非數字字符,解析按從左到右的順序,若是遇到非數字字符就中止。而轉換(如 Nu mber ())不容許出現非數字字符,不然會失敗並返回 NaN。
+
操做符何時用於字符串的拼接?根據 ES5 規範 11.6.1 節,若是某個操做數是字符串或者可以經過如下步驟轉換爲字符串的話,+ 將進行拼接操做。若是其 中一個操做數是對象(包括數組),則首先對其調用 ToPrimitive 抽象操做,該抽象操做再調用 [[DefaultValue]],以 數字做爲上下文。若是不能轉換爲字符串,則會將其轉換爲數字類型來進行計算。 簡單來講就是,若是 + 的其中一個操做數是字符串(或者經過以上步驟最終獲得字符串),則執行字符串拼接,不然執行數字 加法。 那麼對於除了加法的運算符來講,只要其中一方是數字,那麼另外一方就會被轉爲數字。
(1) if (..) 語句中的條件判斷表達式。 (2) for ( .. ; .. ; .. ) 語句中的條件判斷表達式(第二個)。 (3) while (..) 和 do..while(..) 循環中的條件判斷表達式。 (4) ? : 中的條件判斷表達式。 (5) 邏輯運算符 ||(邏輯或)和 &&(邏輯與)左邊的操做數(做爲條件判斷表達式)。
|| 和 && 首先會對第一個操做數執行條件判斷,若是其不是布爾值就先進行 ToBoolean 強制類型轉換,而後再執行條件 判斷。 對於 || 來講,若是條件判斷結果爲 true 就返回第一個操做數的值,若是爲 false 就返回第二個操做數的值。 && 則相反,若是條件判斷結果爲 true 就返回第二個操做數的值,若是爲 false 就返回第一個操做數的值。 || 和 && 返回它們其中一個操做數的值,而非條件判斷的結果
ES6 容許從符號到字符串的顯式強制類型轉換,然而隱式強制類型轉換會產生錯誤。 Symbol 值不可以被強制類型轉換爲數字(顯式和隱式都會產生錯誤),但能夠被強制類型轉換爲布爾值(顯式和隱式結果 都是 true )。
(1)字符串和數字之間的相等比較,將字符串轉換爲數字以後再進行比較。 (2)其餘類型和布爾類型之間的相等比較,先將布爾值轉換爲數字後,再應用其餘規則進行比較。 (3)null 和 undefined 之間的相等比較,結果爲真。其餘值和它們進行比較都返回假值。 (4)對象和非對象之間的相等比較,對象先調用 ToPrimitive 抽象操做後,再進行比較。 (5)若是一個操做值爲 NaN ,則相等比較返回 false( NaN 自己也不等於 NaN )。 (6)若是兩個操做值都是對象,則比較它們是否是指向同一個對象。若是兩個操做數都指向同一個對象,則相等操做符返回 true,不然,返回 false。
詳細資料能夠參考:
《JavaScript 字符串間的比較》
(1)使用 Number() 方法,前提是所包含的字符串不包含不合法字符。 (2)使用 parseInt() 方法,parseInt() 函數可解析一個字符串,並返回一個整數。還能夠設置要解析的數字的基數。當基數的值爲 0,或沒有設置該參數時,parseInt() 會根據 string 來判斷數字的基數。 (3)使用 parseFloat() 方法,該函數解析一個字符串參數並返回一個浮點數。 (4)使用 + 操做符的隱式轉換。
詳細資料能夠參考:
《詳解 JS 中 Number()、parseInt() 和 parseFloat() 的區別》
function format(number) { return number && number.replace(/(?!^)(?=(\d{3})+\.)/g, ","); }
// (1)匹配 16 進制顏色值 var regex = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})/g; // (2)匹配日期,如 yyyy-mm-dd 格式 var regex = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/; // (3)匹配 qq 號 var regex = /^[1-9][0-9]{4,10}$/g; // (4)手機號碼正則 var regex = /^1[34578]\d{9}$/g; // (5)用戶名正則 var regex = /^[a-zA-Z\$][a-zA-Z0-9_\$]{4,16}$/;
詳細資料能夠參考:
《前端表單驗證經常使用的 15 個 JS 正則表達式》
《JS 經常使用正則彙總》
《JS - 生成隨機數的方法彙總(不一樣範圍、類型的隨機數)》
// (1)使用數組 sort 方法對數組元素隨機排序,讓 Math.random() 出來的數與 0.5 比較,若是大於就返回 1 交換位置,若是小於就返回 -1,不交換位置。 function randomSort(a, b) { return Math.random() > 0.5 ? -1 : 1; } // 缺點:每一個元素被派到新數組的位置不是隨機的,緣由是 sort() 方法是依次比較的。 // (2)隨機從原數組抽取一個元素,加入到新數組 function randomSort(arr) { var result = []; while (arr.length > 0) { var randomIndex = Math.floor(Math.random() * arr.length); result.push(arr[randomIndex]); arr.splice(randomIndex, 1); } return result; } // (3)隨機交換數組內的元素(洗牌算法相似) function randomSort(arr) { var index, randomIndex, temp, len = arr.length; for (index = 0; index < len; index++) { randomIndex = Math.floor(Math.random() * (len - index)) + index; temp = arr[index]; arr[index] = arr[randomIndex]; arr[randomIndex] = temp; } return arr; } // es6 function randomSort(array) { let length = array.length; if (!Array.isArray(array) || length <= 1) return; for (let index = 0; index < length - 1; index++) { let randomIndex = Math.floor(Math.random() * (length - index)) + index; [array[index], array[randomIndex]] = [array[randomIndex], array[index]]; } return array; }
詳細資料能夠參考:
《Fisher and Yates 的原始版》
《javascript 實現數組隨機排序?》
《JavaScript 學習筆記:數組隨機排序》
咱們通常使用字面量的形式直接建立對象,可是這種建立方式對於建立大量類似對象的時候,會產生大量的重複代碼。但 js 和通常的面向對象的語言不一樣,在 ES6 以前它沒有類的概念。可是咱們能夠使用函數來進行模擬,從而產生出可複用的對象 建立方式,我瞭解到的方式有這麼幾種: (1)第一種是工廠模式,工廠模式的主要工做原理是用函數來封裝建立對象的細節,從而經過調用函數來達到複用的目的。可是它有一個很大的問題就是建立出來的對象沒法和某個類型聯繫起來,它只是簡單的封裝了複用代碼,而沒有創建起對象和類型間的關係。 (2)第二種是構造函數模式。js 中每個函數均可以做爲構造函數,只要一個函數是經過 new 來調用的,那麼咱們就能夠把它稱爲構造函數。執行構造函數首先會建立一個對象,而後將對象的原型指向構造函數的 prototype 屬性,而後將執行上下文中的 this 指向這個對象,最後再執行整個函數,若是返回值不是對象,則返回新建的對象。由於 this 的值指向了新建的對象,所以咱們能夠使用 this 給對象賦值。構造函數模式相對於工廠模式的優勢是,所建立的對象和構造函數創建起了聯繫,所以咱們能夠經過原型來識別對象的類型。可是構造函數存在一個缺點就是,形成了沒必要要的函數對象的建立,由於在 js 中函數也是一個對象,所以若是對象屬性中若是包含函數的話,那麼每次咱們都會新建一個函數對象,浪費了沒必要要的內存空間,由於函數是全部的實例均可以通用的。 (3)第三種模式是原型模式,由於每個函數都有一個 prototype 屬性,這個屬性是一個對象,它包含了經過構造函數建立的全部實例都能共享的屬性和方法。所以咱們能夠使用原型對象來添加公用屬性和方法,從而實現代碼的複用。這種方式相對於構造函數模式來講,解決了函數對象的複用問題。可是這種模式也存在一些問題,一個是沒有辦法經過傳入參數來初始化值,另外一個是若是存在一個引用類型如 Array 這樣的值,那麼全部的實例將共享一個對象,一個實例對引用類型值的改變會影響全部的實例。 (4)第四種模式是組合使用構造函數模式和原型模式,這是建立自定義類型的最多見方式。由於構造函數模式和原型模式分開使用都存在一些問題,所以咱們能夠組合使用這兩種模式,經過構造函數來初始化對象的屬性,經過原型對象來實現函數方法的複用。這種方法很好的解決了兩種模式單獨使用時的缺點,可是有一點不足的就是,由於使用了兩種不一樣的模式,因此對於代碼的封裝性不夠好。 (5)第五種模式是動態原型模式,這一種模式將原型方法賦值的建立過程移動到了構造函數的內部,經過對屬性是否存在的判斷,能夠實現僅在第一次調用函數時對原型對象賦值一次的效果。這一種方式很好地對上面的混合模式進行了封裝。 (6)第六種模式是寄生構造函數模式,這一種模式和工廠模式的實現基本相同,我對這個模式的理解是,它主要是基於一個已有的類型,在實例化時對實例化的對象進行擴展。這樣既不用修改原來的構造函數,也達到了擴展對象的目的。它的一個缺點和工廠模式同樣,沒法實現對象的識別。 嗯我目前瞭解到的就是這麼幾種方式。
詳細資料能夠參考:
《JavaScript 深刻理解之對象建立》
我瞭解的 js 中實現繼承的幾種方式有: (1)第一種是以原型鏈的方式來實現繼承,可是這種實現方式存在的缺點是,在包含有引用類型的數據時,會被全部的實例對象所共享,容易形成修改的混亂。還有就是在建立子類型的時候不能向超類型傳遞參數。 (2)第二種方式是使用借用構造函數的方式,這種方式是經過在子類型的函數中調用超類型的構造函數來實現的,這一種方法解決了不能向超類型傳遞參數的缺點,可是它存在的一個問題就是沒法實現函數方法的複用,而且超類型原型定義的方法子類型也沒有辦法訪問到。 (3)第三種方式是組合繼承,組合繼承是將原型鏈和借用構造函數組合起來使用的一種方式。經過借用構造函數的方式來實現類型的屬性的繼承,經過將子類型的原型設置爲超類型的實例來實現方法的繼承。這種方式解決了上面的兩種模式單獨使用時的問題,可是因爲咱們是以超類型的實例來做爲子類型的原型,因此調用了兩次超類的構造函數,形成了子類型的原型中多了不少沒必要要的屬性。 (4)第四種方式是原型式繼承,原型式繼承的主要思路就是基於已有的對象來建立新的對象,實現的原理是,向函數中傳入一個對象,而後返回一個以這個對象爲原型的對象。這種繼承的思路主要不是爲了實現創造一種新的類型,只是對某個對象實現一種簡單繼承,ES5 中定義的 Object.create() 方法就是原型式繼承的實現。缺點與原型鏈方式相同。 (5)第五種方式是寄生式繼承,寄生式繼承的思路是建立一個用於封裝繼承過程的函數,經過傳入一個對象,而後複製一個對象的副本,而後對象進行擴展,最後返回這個對象。這個擴展的過程就能夠理解是一種繼承。這種繼承的優勢就是對一個簡單對象實現繼承,若是這個對象不是咱們的自定義類型時。缺點是沒有辦法實現函數的複用。 (6)第六種方式是寄生式組合繼承,組合繼承的缺點就是使用超類型的實例作爲子類型的原型,致使添加了沒必要要的原型屬性。寄生式組合繼承的方式是使用超類型的原型的副原本做爲子類型的原型,這樣就避免了建立沒必要要的屬性。
詳細資料能夠參考:
《JavaScript 深刻理解之繼承》
function Person(name) { this.name = name; } Person.prototype.sayName = function() { console.log("My name is " + this.name + "."); }; function Student(name, grade) { Person.call(this, name); this.grade = grade; } Student.prototype = Object.create(Person.prototype); Student.prototype.constructor = Student; Student.prototype.sayMyGrade = function() { console.log("My grade is " + this.grade + "."); };
做用域鏈的做用是保證對執行環境有權訪問的全部變量和函數的有序訪問,經過做用域鏈,咱們能夠訪問到外層環境的變量和 函數。 做用域鏈的本質上是一個指向變量對象的指針列表。變量對象是一個包含了執行環境中全部變量和函數的對象。做用域鏈的前 端始終都是當前執行上下文的變量對象。全局執行上下文的變量對象(也就是全局對象)始終是做用域鏈的最後一個對象。 當咱們查找一個變量時,若是當前執行環境中沒有找到,咱們能夠沿着做用域鏈向後查找。 做用域鏈的建立過程跟執行上下文的創建有關....
詳細資料能夠參考:
《JavaScript 深刻理解之做用域鏈》
this 是執行上下文中的一個屬性,它指向最後一次調用這個方法的對象。在實際開發中,this 的指向能夠經過四種調用模 式來判斷。
這四種方式,使用構造器調用模式的優先級最高,而後是 apply 、 call 和 bind 調用模式,而後是方法調用模式,而後 是函數調用模式。
它的功能是把對應的字符串解析成 JS 代碼並運行。 應該避免使用 eval,不安全,很是耗性能(2次,一次解析成 js 語句,一次執行)。
詳細資料能夠參考:
《eval()》
DOM 指的是文檔對象模型,它指的是把文檔當作一個對象來對待,這個對象主要定義了處理網頁內容的方法和接口。 BOM 指的是瀏覽器對象模型,它指的是把瀏覽器當作一個對象來對待,這個對象主要定義了與瀏覽器進行交互的法和接口。BOM 的核心是 window,而 window 對象具備雙重角色,它既是經過 js 訪問瀏覽器窗口的一個接口,又是一個 Global(全局) 對象。這意味着在網頁中定義的任何對象,變量和函數,都做爲全局對象的一個屬性或者方法存在。window 對象含有 locati on 對象、navigator 對象、screen 對象等子對象,而且 DOM 的最根本的對象 document 對象也是 BOM 的 window 對 象的子對象。
詳細資料能夠參考:
《DOM, DOCUMENT, BOM, WINDOW 有什麼區別?》
《Window 對象》
《DOM 與 BOM 分別是什麼,有何關聯?》
《JavaScript 學習總結(三)BOM 和 DOM 詳解》
const EventUtils = { // 視能力分別使用dom0||dom2||IE方式 來綁定事件 // 添加事件 addEvent: function(element, type, handler) { if (element.addEventListener) { element.addEventListener(type, handler, false); } else if (element.attachEvent) { element.attachEvent("on" + type, handler); } else { element["on" + type] = handler; } }, // 移除事件 removeEvent: function(element, type, handler) { if (element.removeEventListener) { element.removeEventListener(type, handler, false); } else if (element.detachEvent) { element.detachEvent("on" + type, handler); } else { element["on" + type] = null; } }, // 獲取事件目標 getTarget: function(event) { return event.target || event.srcElement; }, // 獲取 event 對象的引用,取到事件的全部信息,確保隨時能使用 event getEvent: function(event) { return event || window.event; }, // 阻止事件(主要是事件冒泡,由於 IE 不支持事件捕獲) stopPropagation: function(event) { if (event.stopPropagation) { event.stopPropagation(); } else { event.cancelBubble = true; } }, // 取消事件的默認行爲 preventDefault: function(event) { if (event.preventDefault) { event.preventDefault(); } else { event.returnValue = false; } } };
詳細資料能夠參考:
《JS 事件模型》
詳細資料能夠參考:
《Javascript 事件模型系列(一)事件及事件的三種模型》
《Javascript 事件模型:事件捕獲和事件冒泡》
事件是用戶操做網頁時發生的交互動做或者網頁自己的一些操做,現代瀏覽器一共有三種事件模型。 第一種事件模型是最先的 DOM0 級模型,這種模型不會傳播,因此沒有事件流的概念,可是如今有的瀏覽器支持以冒泡的方式實 現,它能夠在網頁中直接定義監聽函數,也能夠經過 js 屬性來指定監聽函數。這種方式是全部瀏覽器都兼容的。 第二種事件模型是 IE 事件模型,在該事件模型中,一次事件共有兩個過程,事件處理階段,和事件冒泡階段。事件處理階段會首先執行目標元素綁定的監聽事件。而後是事件冒泡階段,冒泡指的是事件從目標元素冒泡到 document,依次檢查通過的節點是否綁定了事件監聽函數,若是有則執行。這種模型經過 attachEvent 來添加監聽函數,能夠添加多個監聽函數,會按順序依次執行。 第三種是 DOM2 級事件模型,在該事件模型中,一次事件共有三個過程,第一個過程是事件捕獲階段。捕獲指的是事件從 document 一直向下傳播到目標元素,依次檢查通過的節點是否綁定了事件監聽函數,若是有則執行。後面兩個階段和 IE 事件模型的兩個階段相同。這種事件模型,事件綁定的函數是 addEventListener,其中第三個參數能夠指定事件是否在捕獲階段執行。
詳細資料能夠參考:
《一個 DOM 元素綁定多個事件時,先執行冒泡仍是捕獲》
事件委託本質上是利用了瀏覽器事件冒泡的機制。由於事件在冒泡過程當中會上傳到父節點,而且父節點能夠經過事件對象獲取到 目標節點,所以能夠把子節點的監聽函數定義在父節點上,由父節點的監聽函數統一處理多個子元素的事件,這種方式稱爲事件代理。 使用事件代理咱們能夠沒必要要爲每個子元素都綁定一個監聽事件,這樣減小了內存上的消耗。而且使用事件代理咱們還能夠實現事件的動態綁定,好比說新增了一個子節點,咱們並不須要單獨地爲它添加一個監聽事件,它所發生的事件會交給父元素中的監聽函數來處理。
詳細資料能夠參考:
《JavaScript 事件委託詳解》
parseInt() 函數能解析一個字符串,並返回一個整數,須要兩個參數 (val, radix),其中 radix 表示要解析的數字的基數。(該值介於 2 ~ 36 之間,而且字符串中的數字不能大於 radix 才能正確返回數字結果值)。 此處 map 傳了 3 個參數 (element, index, array),默認第三個參數被忽略掉,所以三次傳入的參數分別爲 "1-0", "2-1", "3-2" 由於字符串的值不能大於基數,所以後面兩次調用均失敗,返回 NaN ,第一次基數爲 0 ,按十進制解析返回 1。
詳細資料能夠參考:
[《爲何 ["1", "2", "3"].map(parseInt) 返回 [1,NaN,NaN]?》](https://blog.csdn.net/justjav...
閉包是指有權訪問另外一個函數做用域中變量的函數,建立閉包的最多見的方式就是在一個函數內建立另外一個函數,建立的函數能夠 訪問到當前函數的局部變量。 閉包有兩個經常使用的用途。 閉包的第一個用途是使咱們在函數外部可以訪問到函數內部的變量。經過使用閉包,咱們能夠經過在外部調用閉包函數,從而在外 部訪問到函數內部的變量,能夠使用這種方法來建立私有變量。 函數的另外一個用途是使已經運行結束的函數上下文中的變量對象繼續留在內存中,由於閉包函數保留了這個變量對象的引用,因此 這個變量對象不會被回收。 其實閉包的本質就是做用域鏈的一個特殊的應用,只要瞭解了做用域鏈的建立過程,就可以理解閉包的實現原理。
詳細資料能夠參考:
《JavaScript 深刻理解之閉包》
相關知識點:
use strict 是一種 ECMAscript5 添加的(嚴格)運行模式,這種模式使得 Javascript 在更嚴格的條件下運行。 設立"嚴格模式"的目的,主要有如下幾個:
區別:
回答:
use strict 指的是嚴格運行模式,在這種模式對 js 的使用添加了一些限制。好比說禁止 this 指向全局對象,還有禁止使 用 with 語句等。設立嚴格模式的目的,主要是爲了消除代碼使用中的一些不安全的使用方式,也是爲了消除 js 語法自己的一 些不合理的地方,以此來減小一些運行時的怪異的行爲。同時使用嚴格運行模式也可以提升編譯的效率,從而提升代碼的運行速度。 我認爲嚴格模式表明了 js 一種更合理、更安全、更嚴謹的發展方向。
詳細資料能夠參考:
《Javascript 嚴格模式詳解》
第一種方式是使用 instanceof 運算符來判斷構造函數的 prototype 屬性是否出如今對象的原型鏈中的任何位置。 第二種方式能夠經過對象的 constructor 屬性來判斷,對象的 constructor 屬性指向該對象的構造函數,可是這種方式不是很安全,由於 constructor 屬性能夠被改寫。 第三種方式,若是須要判斷的是某個內置的引用類型的話,能夠使用 Object.prototype.toString() 方法來打印對象的 [[Class]] 屬性來進行判斷。
詳細資料能夠參考:
《js 判斷一個對象是否屬於某一類》
// instanceof 運算符用於判斷構造函數的 prototype 屬性是否出如今對象的原型鏈中的任何位置。 // 實現: function myInstanceof(left, right) { let proto = Object.getPrototypeOf(left), // 獲取對象的原型 prototype = right.prototype; // 獲取構造函數的 prototype 對象 // 判斷構造函數的 prototype 對象是否在對象的原型鏈上 while (true) { if (!proto) return false; if (proto === prototype) return true; proto = Object.getPrototypeOf(proto); } }
詳細資料能夠參考:
《instanceof》
// (1)首先建立了一個新的空對象 // (2)設置原型,將對象的原型設置爲函數的 prototype 對象。 // (3)讓函數的 this 指向這個對象,執行構造函數的代碼(爲這個新對象添加屬性) // (4)判斷函數的返回值類型,若是是值類型,返回建立的對象。若是是引用類型,就返回這個引用類型的對象。 // 實現: function objectFactory() { let newObject = null, constructor = Array.prototype.shift.call(arguments), result = null; // 參數判斷 if (typeof constructor !== "function") { console.error("type error"); return; } // 新建一個空對象,對象的原型爲構造函數的 prototype 對象 newObject = Object.create(constructor.prototype); // 將 this 指向新建對象,並執行函數 result = constructor.apply(newObject, arguments); // 判斷返回對象 let flag = result && (typeof result === "object" || typeof result === "function"); // 判斷返回結果 return flag ? result : newObject; } // 使用方法 // objectFactory(構造函數, 初始化參數);
詳細資料能夠參考:
《new 操做符具體幹了什麼?》
《JavaScript 深刻之 new 的模擬實現》
hasOwnProperty 全部繼承了 Object 的對象都會繼承到 hasOwnProperty 方法。這個方法能夠用來檢測一個對象是否含有特定的自身屬性,和 in 運算符不一樣,該方法會忽略掉那些從原型鏈上繼承到的屬性。
詳細資料能夠參考:
《Object.prototype.hasOwnProperty()》
相關知識點:
JSON 是一種數據交換格式,基於文本,優於輕量,用於交換數據。 JSON 能夠表示數字、布爾值、字符串、null、數組(值的有序序列),以及由這些值(或數組、對象)所組成的對象(字符串與 值的映射)。 JSON 使用 JavaScript 語法,可是 JSON 格式僅僅是一個文本。文本能夠被任何編程語言讀取及做爲數據格式傳遞。
回答:
JSON 是一種基於文本的輕量級的數據交換格式。它能夠被任何的編程語言讀取和做爲數據格式來傳遞。 在項目開發中,咱們使用 JSON 做爲先後端數據交換的方式。在前端咱們經過將一個符合 JSON 格式的數據結構序列化爲 JSON 字符串,而後將它傳遞到後端,後端經過 JSON 格式的字符串解析後生成對應的數據結構,以此來實現先後端數據的一個傳遞。 由於 JSON 的語法是基於 js 的,所以很容易將 JSON 和 js 中的對象弄混,可是咱們應該注意的是 JSON 和 js 中的對象不是一回事,JSON 中對象格式更加嚴格,好比說在 JSON 中屬性值不能爲函數,不能出現 NaN 這樣的屬性值等,所以大多數的 js 對象是不符合 JSON 對象的格式的。 在 js 中提供了兩個函數來實現 js 數據結構和 JSON 格式的轉換處理,一個是 JSON.stringify 函數,經過傳入一個符合 JSON 格式的數據結構,將其轉換爲一個 JSON 字符串。若是傳入的數據結構不符合 JSON 格式,那麼在序列化的時候會對這些值進行對應的特殊處理,使其符合規範。在前端向後端發送數據時,咱們能夠調用這個函數將數據對象轉化爲 JSON 格式的字符串。 另外一個函數 JSON.parse() 函數,這個函數用來將 JSON 格式的字符串轉換爲一個 js 數據結構,若是傳入的字符串不是標準的 JSON 格式的字符串的話,將會拋出錯誤。當咱們從後端接收到 JSON 格式的字符串時,咱們能夠經過這個方法來將其解析爲一個 js 數據結構,以此來進行數據的訪問。
詳細資料能夠參考:
《深刻了解 JavaScript 中的 JSON 》
(1)選取頁面全部 DOM 元素。在瀏覽器的控制檯中能夠使用$$()方法來獲取頁面中相應的元素,這是現代瀏覽器提供的一個命令行 API 至關於 document.querySelectorAll 方法。 (2)循環遍歷 DOM 元素 (3)給元素添加 outline 。因爲渲染的 outline 是不在 CSS 盒模型中的,因此爲元素添加 outline 並不會影響元素的大小和頁面的佈局。 (4)生成隨機顏色函數。Math.random()*(1<<24) 能夠獲得 0~2^24 - 1 之間的隨機數,由於獲得的是一個浮點數,但咱們只須要整數部分,使用取反操做符 ~ 連續兩次取反得到整數部分,而後再用 toString(16) 的方式,轉換爲一個十六進制的字符串。
詳細資料能夠參考:
《經過一行代碼學 JavaScript》
相關知識點:
js 延遲加載,也就是等頁面加載完成以後再加載 JavaScript 文件。 js 延遲加載有助於提升頁面加載速度。
通常有如下幾種方式:
回答:
js 的加載、解析和執行會阻塞頁面的渲染過程,所以咱們但願 js 腳本可以儘量的延遲加載,提升頁面的渲染速度。 我瞭解到的幾種方式是: 第一種方式是咱們通常採用的是將 js 腳本放在文檔的底部,來使 js 腳本儘量的在最後來加載執行。 第二種方式是給 js 腳本添加 defer 屬性,這個屬性會讓腳本的加載與文檔的解析同步解析,而後在文檔解析完成後再執行這個腳本文件,這樣的話就能使頁面的渲染不被阻塞。多個設置了 defer 屬性的腳本按規範來講最後是順序執行的,可是在一些瀏覽器中可能不是這樣。 第三種方式是給 js 腳本添加 async 屬性,這個屬性會使腳本異步加載,不會阻塞頁面的解析過程,可是當腳本加載完成後當即執行 js 腳本,這個時候若是文檔沒有解析完成的話一樣會阻塞。多個 async 屬性的腳本的執行順序是不可預測的,通常不會按照代碼的順序依次執行。 第四種方式是動態建立 DOM 標籤的方式,咱們能夠對文檔的加載事件進行監聽,當文檔加載完成後再動態的建立 script 標籤來引入 js 腳本。
詳細資料能夠參考:
《JS 延遲加載的幾種方式》
《HTML 5 <script>
async
屬性》
相關知識點:
2005 年 2 月,AJAX 這個詞第一次正式提出,它是 Asynchronous JavaScript and XML 的縮寫,指的是經過 JavaScript 的
異步通訊,從服務器獲取 XML 文檔從中提取數據,再更新當前網頁的對應部分,而不用刷新整個網頁。
具體來講,AJAX 包括如下幾個步驟。
通常實現:
const SERVER_URL = "/server"; let xhr = new XMLHttpRequest(); // 建立 Http 請求 xhr.open("GET", SERVER_URL, true); // 設置狀態監聽函數 xhr.onreadystatechange = function() { if (this.readyState !== 4) return; // 當請求成功時 if (this.status === 200) { handle(this.response); } else { console.error(this.statusText); } }; // 設置請求失敗時的監聽函數 xhr.onerror = function() { console.error(this.statusText); }; // 設置請求頭信息 xhr.responseType = "json"; xhr.setRequestHeader("Accept", "application/json"); // 發送 Http 請求 xhr.send(null); // promise 封裝實現: function getJSON(url) { // 建立一個 promise 對象 let promise = new Promise(function(resolve, reject) { let xhr = new XMLHttpRequest(); // 新建一個 http 請求 xhr.open("GET", url, true); // 設置狀態的監聽函數 xhr.onreadystatechange = function() { if (this.readyState !== 4) return; // 當請求成功或失敗時,改變 promise 的狀態 if (this.status === 200) { resolve(this.response); } else { reject(new Error(this.statusText)); } }; // 設置錯誤監聽函數 xhr.onerror = function() { reject(new Error(this.statusText)); }; // 設置響應的數據類型 xhr.responseType = "json"; // 設置請求頭信息 xhr.setRequestHeader("Accept", "application/json"); // 發送 http 請求 xhr.send(null); }); return promise; }
回答:
我對 ajax 的理解是,它是一種異步通訊的方法,經過直接由 js 腳本向服務器發起 http 通訊,而後根據服務器返回的數據,更新網頁的相應部分,而不用刷新整個頁面的一種方法。 建立一個 ajax 有這樣幾個步驟 首先是建立一個 XMLHttpRequest 對象。 而後在這個對象上使用 open 方法建立一個 http 請求,open 方法所須要的參數是請求的方法、請求的地址、是否異步和用戶的認證信息。 在發起請求前,咱們能夠爲這個對象添加一些信息和監聽函數。好比說咱們能夠經過 setRequestHeader 方法來爲請求添加頭信息。咱們還能夠爲這個對象添加一個狀態監聽函數。一個 XMLHttpRequest 對象一共有 5 個狀態,當它的狀態變化時會觸發onreadystatechange 事件,咱們能夠經過設置監聽函數,來處理請求成功後的結果。當對象的 readyState 變爲 4 的時候,表明服務器返回的數據接收完成,這個時候咱們能夠經過判斷請求的狀態,若是狀態是 2xx 或者 304 的話則表明返回正常。這個時候咱們就能夠經過 response 中的數據來對頁面進行更新了。 當對象的屬性和監聽函數設置完成後,最後咱們調用 sent 方法來向服務器發起請求,能夠傳入參數做爲發送的數據體。
詳細資料能夠參考:
《XMLHttpRequest 對象》
《從 ajax 到 fetch、axios》
《Fetch 入門》
《傳統 Ajax 已死,Fetch 永生》
瀏覽器的緩存機制指的是經過在一段時間內保留已接收到的 web 資源的一個副本,若是在資源的有效時間內,發起了對這個資源的再一次請求,那麼瀏覽器會直接使用緩存的副本,而不是向服務器發起請求。使用 web 緩存能夠有效地提升頁面的打開速度,減小沒必要要的網絡帶寬的消耗。 web 資源的緩存策略通常由服務器來指定,能夠分爲兩種,分別是強緩存策略和協商緩存策略。 使用強緩存策略時,若是緩存資源有效,則直接使用緩存資源,沒必要再向服務器發起請求。強緩存策略能夠經過兩種方式來設置,分別是 http 頭信息中的 Expires 屬性和 Cache-Control 屬性。 服務器經過在響應頭中添加 Expires 屬性,來指定資源的過時時間。在過時時間之內,該資源能夠被緩存使用,沒必要再向服務器發送請求。這個時間是一個絕對時間,它是服務器的時間,所以可能存在這樣的問題,就是客戶端的時間和服務器端的時間不一致,或者用戶能夠對客戶端時間進行修改的狀況,這樣就可能會影響緩存命中的結果。 Expires 是 http1.0 中的方式,由於它的一些缺點,在 http 1.1 中提出了一個新的頭部屬性就是 Cache-Control 屬性, 它提供了對資源的緩存的更精確的控制。它有不少不一樣的值,經常使用的好比咱們能夠經過設置 max-age 來指定資源可以被緩存的時間 的大小,這是一個相對的時間,它會根據這個時間的大小和資源第一次請求時的時間來計算出資源過時的時間,所以相對於 Expires 來講,這種方式更加有效一些。經常使用的還有好比 private ,用來規定資源只能被客戶端緩存,不可以代理服務器所緩存。還有如 n o-store ,用來指定資源不可以被緩存,no-cache 表明該資源可以被緩存,可是當即失效,每次都須要向服務器發起請求。 通常來講只須要設置其中一種方式就能夠實現強緩存策略,當兩種方式一塊兒使用時,Cache-Control 的優先級要高於 Expires 。 使用協商緩存策略時,會先向服務器發送一個請求,若是資源沒有發生修改,則返回一個 304 狀態,讓瀏覽器使用本地的緩存副本。 若是資源發生了修改,則返回修改後的資源。協商緩存也能夠經過兩種方式來設置,分別是 http 頭信息中的 Etag 和 Last-Modified 屬性。 服務器經過在響應頭中添加 Last-Modified 屬性來指出資源最後一次修改的時間,當瀏覽器下一次發起請求時,會在請求頭中添加一個 If-Modified-Since 的屬性,屬性值爲上一次資源返回時的 Last-Modified 的值。當請求發送到服務器後服務器會經過這個屬性來和資源的最後一次的修改時間來進行比較,以此來判斷資源是否作了修改。若是資源沒有修改,那麼返回 304 狀態,讓客戶端使用本地的緩存。若是資源已經被修改了,則返回修改後的資源。使用這種方法有一個缺點,就是 Last-Modified 標註的最後修改時間只能精確到秒級,若是某些文件在1秒鐘之內,被修改屢次的話,那麼文件已將改變了可是 Last-Modified 卻沒有改變, 這樣會形成緩存命中的不許確。 由於 Last-Modified 的這種可能發生的不許確性,http 中提供了另一種方式,那就是 Etag 屬性。服務器在返回資源的時候,在頭信息中添加了 Etag 屬性,這個屬性是資源生成的惟一標識符,當資源發生改變的時候,這個值也會發生改變。在下一次資源請求時,瀏覽器會在請求頭中添加一個 If-None-Match 屬性,這個屬性的值就是上次返回的資源的 Etag 的值。服務接收到請求後會根據這個值來和資源當前的 Etag 的值來進行比較,以此來判斷資源是否發生改變,是否須要返回資源。經過這種方式,比 Last-Modified 的方式更加精確。 當 Last-Modified 和 Etag 屬性同時出現的時候,Etag 的優先級更高。使用協商緩存的時候,服務器須要考慮負載平衡的問題,所以多個服務器上資源的 Last-Modified 應該保持一致,由於每一個服務器上 Etag 的值都不同,所以在考慮負載平衡時,最好不要設置 Etag 屬性。 強緩存策略和協商緩存策略在緩存命中時都會直接使用本地的緩存副本,區別只在於協商緩存會向服務器發送一次請求。它們緩存不命中時,都會向服務器發送請求來獲取資源。在實際的緩存機制中,強緩存策略和協商緩存策略是一塊兒合做使用的。瀏覽器首先會根據請求的信息判斷,強緩存是否命中,若是命中則直接使用資源。若是不命中則根據頭信息向服務器發起請求,使用協商緩存,若是協商緩存命中的話,則服務器不返回資源,瀏覽器直接使用本地資源的副本,若是協商緩存不命中,則瀏覽器返回最新的資源給瀏覽器。
詳細資料能夠參考:
《淺談瀏覽器緩存》
《前端優化:瀏覽器緩存技術介紹》
《請求頭中的 Cache-Control》
《Cache-Control 字段值詳解》
詳細資料能夠參考:
《Ajax 中瀏覽器的緩存問題解決方法》
《淺談瀏覽器緩存》
相關知識點:
同步,能夠理解爲在執行完一個函數或方法以後,一直等待系統返回值或消息,這時程序是處於阻塞的,只有接收到返回的值或消息後才往下執行其餘的命令。 異步,執行完函數或方法後,沒必要阻塞性地等待返回值或消息,只須要向系統委託一個異步過程,那麼當系統接收到返回值或消息時,系統會自動觸發委託的異步過程,從而完成一個完整的流程。
回答:
同步指的是當一個進程在執行某個請求的時候,若是這個請求須要等待一段時間才能返回,那麼這個進程會一直等待下去,直到消息返 回爲止再繼續向下執行。 異步指的是當一個進程在執行某個請求的時候,若是這個請求須要等待一段時間才能返回,這個時候進程會繼續往下執行,不會阻塞等 待消息的返回,當消息返回時系統再通知進程進行處理。
詳細資料能夠參考:
《同步和異步的區別》
我對瀏覽器的同源政策的理解是,一個域下的 js 腳本在未經容許的狀況下,不可以訪問另外一個域的內容。這裏的同源的指的是兩個 域的協議、域名、端口號必須相同,不然則不屬於同一個域。 同源政策主要限制了三個方面 第一個是當前域下的 js 腳本不可以訪問其餘域下的 cookie、localStorage 和 indexDB。 第二個是當前域下的 js 腳本不可以操做訪問操做其餘域下的 DOM。 第三個是當前域下 ajax 沒法發送跨域請求。 同源政策的目的主要是爲了保證用戶的信息安全,它只是對 js 腳本的一種限制,並非對瀏覽器的限制,對於通常的 img、或者 script 腳本請求都不會有跨域的限制,這是由於這些操做都不會經過響應結果來進行可能出現安全問題的操做。
相關知識點:
回答:
解決跨域的方法咱們能夠根據咱們想要實現的目的來劃分。 首先咱們若是隻是想要實現主域名下的不一樣子域名的跨域操做,咱們能夠使用設置 document.domain 來解決。 (1)將 document.domain 設置爲主域名,來實現相同子域名的跨域操做,這個時候主域名下的 cookie 就可以被子域名所訪問。同時若是文檔中含有主域名相同,子域名不一樣的 iframe 的話,咱們也能夠對這個 iframe 進行操做。 若是是想要解決不一樣跨域窗口間的通訊問題,好比說一個頁面想要和頁面的中的不一樣源的 iframe 進行通訊的問題,咱們能夠使用 location.hash 或者 window.name 或者 postMessage 來解決。 (2)使用 location.hash 的方法,咱們能夠在主頁面動態的修改 iframe 窗口的 hash 值,而後在 iframe 窗口裏實現監聽函數來實現這樣一個單向的通訊。由於在 iframe 是沒有辦法訪問到不一樣源的父級窗口的,因此咱們不能直接修改父級窗口的 hash 值來實現通訊,咱們能夠在 iframe 中再加入一個 iframe ,這個 iframe 的內容是和父級頁面同源的,因此咱們能夠 window.parent.parent 來修改最頂級頁面的 src,以此來實現雙向通訊。 (3)使用 window.name 的方法,主要是基於同一個窗口中設置了 window.name 後不一樣源的頁面也能夠訪問,因此不一樣源的子頁面能夠首先在 window.name 中寫入數據,而後跳轉到一個和父級同源的頁面。這個時候級頁面就能夠訪問同源的子頁面中 window.name 中的數據了,這種方式的好處是能夠傳輸的數據量大。 (4)使用 postMessage 來解決的方法,這是一個 h5 中新增的一個 api。經過它咱們能夠實現多窗口間的信息傳遞,經過獲取到指定窗口的引用,而後調用 postMessage 來發送信息,在窗口中咱們經過對 message 信息的監聽來接收信息,以此來實現不一樣源間的信息交換。 若是是像解決 ajax 沒法提交跨域請求的問題,咱們能夠使用 jsonp、cors、websocket 協議、服務器代理來解決問題。 (5)使用 jsonp 來實現跨域請求,它的主要原理是經過動態構建 script 標籤來實現跨域請求,由於瀏覽器對 script 標籤的引入沒有跨域的訪問限制 。經過在請求的 url 後指定一個回調函數,而後服務器在返回數據的時候,構建一個 json 數據的包裝,這個包裝就是回調函數,而後返回給前端,前端接收到數據後,由於請求的是腳本文件,因此會直接執行,這樣咱們先前定義好的回調函數就能夠被調用,從而實現了跨域請求的處理。這種方式只能用於 get 請求。 (6)使用 CORS 的方式,CORS 是一個 W3C 標準,全稱是"跨域資源共享"。CORS 須要瀏覽器和服務器同時支持。目前,全部瀏覽器都支持該功能,所以咱們只須要在服務器端配置就行。瀏覽器將 CORS 請求分紅兩類:簡單請求和非簡單請求。對於簡單請求,瀏覽器直接發出 CORS 請求。具體來講,就是會在頭信息之中,增長一個 Origin 字段。Origin 字段用來講明本次請求來自哪一個源。服務器根據這個值,決定是否贊成此次請求。對於若是 Origin 指定的源,不在許可範圍內,服務器會返回一個正常的 HTTP 迴應。瀏覽器發現,這個迴應的頭信息沒有包含 Access-Control-Allow-Origin 字段,就知道出錯了,從而拋出一個錯誤,ajax 不會收到響應信息。若是成功的話會包含一些以 Access-Control- 開頭的字段。 非簡單請求,瀏覽器會先發出一次預檢請求,來判斷該域名是否在服務器的白名單中,若是收到確定回覆後纔會發起請求。 (7)使用 websocket 協議,這個協議沒有同源限制。 (8)使用服務器來代理跨域的訪問請求,就是有跨域的請求操做時發送請求給後端,讓後端代爲請求,而後最後將獲取的結果發返回。
詳細資料能夠參考:
《前端常見跨域解決方案(全)》
《瀏覽器同源政策及其規避方法》
《跨域,你須要知道的全在這裏》
《爲何 form 表單提交沒有跨域問題,但 ajax 提交有跨域問題?》
詳細資料能夠參考:
《深刻淺出 Nginx》
個人理解是 cookie 是服務器提供的一種用於維護會話狀態信息的數據,經過服務器發送到瀏覽器,瀏覽器保存在本地,當下一次有同源的請求時,將保存的 cookie 值添加到請求頭部,發送給服務端。這能夠用來實現記錄用戶登陸狀態等功能。cookie 通常能夠存儲 4k 大小的數據,而且只可以被同源的網頁所共享訪問。 服務器端能夠使用 Set-Cookie 的響應頭部來配置 cookie 信息。一條cookie 包括了5個屬性值 expires、domain、path、secure、HttpOnly。其中 expires 指定了 cookie 失效的時間,domain 是域名、path是路徑,domain 和 path 一塊兒限制了 cookie 可以被哪些 url 訪問。secure 規定了 cookie 只能在確保安全的狀況下傳輸,HttpOnly 規定了這個 cookie 只能被服務器訪問,不能使用 js 腳本訪問。 在發生 xhr 的跨域請求的時候,即便是同源下的 cookie,也不會被自動添加到請求頭部,除非顯示地規定。
詳細資料能夠參考:
《HTTP cookies》
《聊一聊 cookie》
我對模塊的理解是,一個模塊是實現一個特定功能的一組方法。在最開始的時候,js 只實現一些簡單的功能,因此並無模塊的概念 ,但隨着程序愈來愈複雜,代碼的模塊化開發變得愈來愈重要。 因爲函數具備獨立做用域的特色,最原始的寫法是使用函數來做爲模塊,幾個函數做爲一個模塊,可是這種方式容易形成全局變量的污 染,而且模塊間沒有聯繫。 後面提出了對象寫法,經過將函數做爲一個對象的方法來實現,這樣解決了直接使用函數做爲模塊的一些缺點,可是這種辦法會暴露所 有的全部的模塊成員,外部代碼能夠修改內部屬性的值。 如今最經常使用的是當即執行函數的寫法,經過利用閉包來實現模塊私有做用域的創建,同時不會對全局做用域形成污染。
詳細資料能夠參考:
《淺談模塊化開發》
《Javascript 模塊化編程(一):模塊的寫法》
《前端模塊化:CommonJS,AMD,CMD,ES6》
《Module 的語法》
js 中如今比較成熟的有四種模塊加載方案。 第一種是 CommonJS 方案,它經過 require 來引入模塊,經過 module.exports 定義模塊的輸出接口。這種模塊加載方案是 服務器端的解決方案,它是以同步的方式來引入模塊的,由於在服務端文件都存儲在本地磁盤,因此讀取很是快,因此以同步的方式 加載沒有問題。但若是是在瀏覽器端,因爲模塊的加載是使用網絡請求,所以使用異步加載的方式更加合適。 第二種是 AMD 方案,這種方案採用異步加載的方式來加載模塊,模塊的加載不影響後面語句的執行,全部依賴這個模塊的語句都定 義在一個回調函數裏,等到加載完成後再執行回調函數。require.js 實現了 AMD 規範。 第三種是 CMD 方案,這種方案和 AMD 方案都是爲了解決異步模塊加載的問題,sea.js 實現了 CMD 規範。它和 require.js 的區別在於模塊定義時對依賴的處理不一樣和對依賴模塊的執行時機的處理不一樣。參考60 第四種方案是 ES6 提出的方案,使用 import 和 export 的形式來導入導出模塊。這種方案和上面三種方案都不一樣。參考 61。
它們之間的主要區別有兩個方面。
(1)第一個方面是在模塊定義時對依賴的處理不一樣。AMD 推崇依賴前置,在定義模塊的時候就要聲明其依賴的模塊。而 CMD 推崇
就近依賴,只有在用到某個模塊的時候再去 require。
(2)第二個方面是對依賴模塊的執行時機處理不一樣。首先 AMD 和 CMD 對於模塊的加載方式都是異步加載,不過它們的區別在於
模塊的執行時機,AMD 在依賴模塊加載完成後就直接執行依賴模塊,依賴模塊的執行順序和咱們書寫的順序不必定一致。而 CMD
在依賴模塊加載完成後並不執行,只是下載而已,等到全部的依賴模塊都加載好後,進入回調函數邏輯,遇到 require 語句
的時候才執行對應的模塊,這樣模塊的執行順序就和咱們書寫的順序保持一致了。
// CMD define(function(require, exports, module) { var a = require("./a"); a.doSomething(); // 此處略去 100 行 var b = require("./b"); // 依賴能夠就近書寫 b.doSomething(); // ... }); // AMD 默認推薦 define(["./a", "./b"], function(a, b) { // 依賴必須一開始就寫好 a.doSomething(); // 此處略去 100 行 b.doSomething(); // ... });
詳細資料能夠參考:
《前端模塊化,AMD 與 CMD 的區別》
require.js 的核心原理是經過動態建立 script 腳原本異步引入模塊,而後對每一個腳本的 load 事件進行監聽,若是每一個腳本都加載完成了,再調用回調函數。
詳細資料能夠參考:
《requireJS 的用法和原理分析》
《requireJS 的核心原理是什麼?》
《從 RequireJs 源碼剖析腳本加載原理》
《requireJS 原理分析》
詳細資料能夠參考:
《JS 模塊加載器加載原理是怎麼樣的?》
在我看來 ES6 新添加的 class 只是爲了補充 js 中缺乏的一些面嚮對象語言的特性,但本質上來講它只是一種語法糖,不是一個新的東西,其背後仍是原型繼承的思想。經過加入 class 能夠有利於咱們更好的組織代碼。 在 class 中添加的方法,實際上是添加在類的原型上的。
詳細資料能夠參考:
《ECMAScript 6 實現了 class,對 JavaScript 前端開發有什麼意義?》
《Class 的基本語法》
document.write 的內容會代替整個文檔內容,會重寫整個頁面。 innerHTML 的內容只是替代指定元素的內容,只會重寫頁面中的部份內容。
詳細資料能夠參考:
《簡述 document.write 和 innerHTML 的區別。》
(1)建立新節點
createDocumentFragment(node); createElement(node); createTextNode(text);
(2)添加、移除、替換、插入
appendChild(node) removeChild(node) replaceChild(new,old) insertBefore(new,old)
(3)查找
getElementById(); getElementsByName(); getElementsByTagName(); getElementsByClassName(); querySelector(); querySelectorAll();
(4)屬性操做
getAttribute(key); setAttribute(key, value); hasAttribute(key); removeAttribute(key);
詳細資料能夠參考:
《DOM 概述》
《原生 JavaScript 的 DOM 操做彙總》
《原生 JS 中 DOM 節點相關 API 合集》
對於這樣一個 HTML 元素:<div>content<br/></div>。 innerHTML:內部 HTML,content<br/>; outerHTML:外部 HTML,<div>content<br/></div>; innerText:內部文本,content ; outerText:內部文本,content ;
它們的做用如出一轍,區別僅在於傳入參數的形式的不一樣。 apply 接受兩個參數,第一個參數指定了函數體內 this 對象的指向,第二個參數爲一個帶下標的集合,這個集合能夠爲數組,也能夠爲類數組,apply 方法把這個集合中的元素做爲參數傳遞給被調用的函數。 call 傳入的參數數量不固定,跟 apply 相同的是,第一個參數也是表明函數體內的 this 指向,從第二個參數開始日後,每一個參數被依次傳入函數。
詳細資料能夠參考:
《apply、call 的區別和用途》
一個擁有 length 屬性和若干索引屬性的對象就能夠被稱爲類數組對象,類數組對象和數組相似,可是不能調用數組的方法。 常見的類數組對象有 arguments 和 DOM 方法的返回結果,還有一個函數也能夠被看做是類數組對象,由於它含有 length 屬性值,表明可接收的參數個數。
常見的類數組轉換爲數組的方法有這樣幾種:
(1)經過 call 調用數組的 slice 方法來實現轉換
Array.prototype.slice.call(arrayLike);
(2)經過 call 調用數組的 splice 方法來實現轉換
Array.prototype.splice.call(arrayLike, 0);
(3)經過 apply 調用數組的 concat 方法來實現轉換
Array.prototype.concat.apply([], arrayLike);
(4)經過 Array.from 方法來實現轉換
Array.from(arrayLike);
詳細的資料能夠參考:
《JavaScript 深刻之類數組對象與 arguments》
《javascript 類數組》
《深刻理解 JavaScript 類數組》
數組和字符串的轉換方法:toString()、toLocalString()、join() 其中 join() 方法能夠指定轉換爲字符串時的分隔符。 數組尾部操做的方法 pop() 和 push(),push 方法能夠傳入多個參數。 數組首部操做的方法 shift() 和 unshift() 重排序的方法 reverse() 和 sort(),sort() 方法能夠傳入一個函數來進行比較,傳入先後兩個值,若是返回值爲正數,則交換兩個參數的位置。 數組鏈接的方法 concat() ,返回的是拼接好的數組,不影響原數組。 數組截取辦法 slice(),用於截取數組中的一部分返回,不影響原數組。 數組插入方法 splice(),影響原數組查找特定項的索引的方法,indexOf() 和 lastIndexOf() 迭代方法 every()、some()、filter()、map() 和 forEach() 方法 數組歸併方法 reduce() 和 reduceRight() 方法
詳細資料能夠參考:
《JavaScript 深刻理解之 Array 類型詳解》
fill() 方法用一個固定值填充一個數組中從起始索引到終止索引內的所有元素。不包括終止索引。 fill 方法接受三個參數 value,start 以及 end,start 和 end 參數是可選的,其默認值分別爲 0 和 this 對象的 length 屬性值。
詳細資料能夠參考:
《Array.prototype.fill()》
尾後逗號 (有時叫作「終止逗號」)在向 JavaScript 代碼添加元素、參數、屬性時十分有用。若是你想要添加新的屬性,而且上一行已經使用了尾後逗號,你能夠僅僅添加新的一行,而不須要修改上一行。這使得版本控制更加清晰,以及代碼維護麻煩更少。 JavaScript 一開始就支持數組字面值中的尾後逗號,隨後向對象字面值(ECMAScript 5)中添加了尾後逗號。最近(ECMAS cript 2017),又將其添加到函數參數中。可是 JSON 不支持尾後逗號。 若是使用了多於一個尾後逗號,會產生間隙。 帶有間隙的數組叫作稀疏數組(密緻數組沒有間隙)。稀疏數組的長度爲逗號的數 量。
詳細資料能夠參考:
《尾後逗號》
變量提高的表現是,不管咱們在函數中何處位置聲明的變量,好像都被提高到了函數的首部,咱們能夠在變量聲明前訪問到而不會報錯。 形成變量聲明提高的本質緣由是 js 引擎在代碼執行前有一個解析的過程,建立了執行上下文,初始化了一些代碼執行時須要用到的對象。當咱們訪問一個變量時,咱們會到當前執行上下文中的做用域鏈中去查找,而做用域鏈的首端指向的是當前執行上下文的變量對象,這個變量對象是執行上下文的一個屬性,它包含了函數的形參、全部的函數和變量聲明,這個對象的是在代碼解析的時候建立的。這就是會出現變量聲明提高的根本緣由。
詳細資料能夠參考:
《JavaScript 深刻理解之變量對象》
詳細資料能夠參考:
《如何編寫高性能的 Javascript?》
v8 的垃圾回收機制基於分代回收機制,這個機制又基於世代假說,這個假說有兩個特色,一是新生的對象容易早死,另外一個是不死的對象會活得更久。基於這個假說,v8 引擎將內存分爲了新生代和老生代。 新建立的對象或者只經歷過一次的垃圾回收的對象被稱爲新生代。經歷過屢次垃圾回收的對象被稱爲老生代。 新生代被分爲 From 和 To 兩個空間,To 通常是閒置的。當 From 空間滿了的時候會執行 Scavenge 算法進行垃圾回收。當咱們執行垃圾回收算法的時候應用邏輯將會中止,等垃圾回收結束後再繼續執行。這個算法分爲三步: (1)首先檢查 From 空間的存活對象,若是對象存活則判斷對象是否知足晉升到老生代的條件,若是知足條件則晉升到老生代。若是不知足條件則移動 To 空間。 (2)若是對象不存活,則釋放對象的空間。 (3)最後將 From 空間和 To 空間角色進行交換。 新生代對象晉升到老生代有兩個條件: (1)第一個是判斷是對象否已經通過一次 Scavenge 回收。若經歷過,則將對象從 From 空間複製到老生代中;若沒有經歷,則複製到 To 空間。 (2)第二個是 To 空間的內存使用佔比是否超過限制。當對象從 From 空間複製到 To 空間時,若 To 空間使用超過 25%,則對象直接晉升到老生代中。設置 25% 的緣由主要是由於算法結束後,兩個空間結束後會交換位置,若是 To 空間的內存過小,會影響後續的內存分配。 老生代採用了標記清除法和標記壓縮法。標記清除法首先會對內存中存活的對象進行標記,標記結束後清除掉那些沒有標記的對象。因爲標記清除後會形成不少的內存碎片,不便於後面的內存分配。因此瞭解決內存碎片的問題引入了標記壓縮法。 因爲在進行垃圾回收的時候會暫停應用的邏輯,對於新生代方法因爲內存小,每次停頓的時間不會太長,但對於老生代來講每次垃圾回收的時間長,停頓會形成很大的影響。 爲了解決這個問題 V8 引入了增量標記的方法,將一次停頓進行的過程分爲了多步,每次執行完一小步就讓運行邏輯執行一會,就這樣交替運行。
詳細資料能夠參考:
《深刻理解 V8 的垃圾回收原理》
《JavaScript 中的垃圾回收》
相關知識點:
回答:
第一種狀況是咱們因爲使用未聲明的變量,而意外的建立了一個全局變量,而使這個變量一直留在內存中沒法被回收。 第二種狀況是咱們設置了 setInterval 定時器,而忘記取消它,若是循環函數有對外部變量的引用的話,那麼這個變量會被一直留 在內存中,而沒法被回收。 第三種狀況是咱們獲取一個 DOM 元素的引用,然後面這個元素被刪除,因爲咱們一直保留了對這個元素的引用,因此它也沒法被回 收。 第四種狀況是不合理的使用閉包,從而致使某些變量一直被留在內存當中。
詳細資料能夠參考:
《JavaScript 內存泄漏教程》
《4 類 JavaScript 內存泄漏及如何避免》
《杜絕 js 中四種內存泄漏類型的發生》
《javascript 典型內存泄漏及 chrome 的排查方法》
經過使用 pushState + ajax 實現瀏覽器無刷新前進後退,當一次 ajax 調用成功後咱們將一條 state 記錄加入到 history 對象中。一條 state 記錄包含了 url、title 和 content 屬性,在 popstate 事件中能夠獲取到這個 state 對象,咱們可 以使用 content 來傳遞數據。最後咱們經過對 window.onpopstate 事件監聽來響應瀏覽器的前進後退操做。 使用 pushState 來實現有兩個問題,一個是打開首頁時沒有記錄,咱們能夠使用 replaceState 來將首頁的記錄替換,另外一個問 題是當一個頁面刷新的時候,仍然會向服務器端請求數據,所以若是請求的 url 須要後端的配合將其重定向到一個頁面。
詳細資料能夠參考:
《pushState + ajax 實現瀏覽器無刷新前進後退》
《Manipulating the browser history》
this === window ? 'browser' : 'node'; 經過判斷 Global 對象是否爲 window,若是不爲 window,當前腳本沒有運行在瀏覽器中。
詳細資料能夠參考:
《爲何把 script 標籤放在 body 結束標籤以後 html 結束標籤以前?》
《從 Chrome 源碼看瀏覽器如何加載資源》
移動端點擊有 300ms 的延遲是由於移動端會有雙擊縮放的這個操做,所以瀏覽器在 click 以後要等待 300ms,看用戶有沒有下一次點擊,來判斷此次操做是否是雙擊。
有三種辦法來解決這個問題:
click 延時問題還可能引發點擊穿透的問題,就是若是咱們在一個元素上註冊了 touchStart 的監聽事件,這個事件會將這個元素隱藏掉,咱們發現當這個元素隱藏後,觸發了這個元素下的一個元素的點擊事件,這就是點擊穿透。
詳細資料能夠參考:
《移動端 300ms 點擊延遲和點擊穿透》
(1)什麼是前端路由? 前端路由就是把不一樣路由對應不一樣的內容或頁面的任務交給前端來作,以前是經過服務端根據 url 的不一樣返回不一樣的頁面實現的。 (2)何時使用前端路由? 在單頁面應用,大部分頁面結構不變,只改變部份內容的使用 (3)前端路由有什麼優勢和缺點? 優勢:用戶體驗好,不須要每次都從服務器所有獲取,快速展示給用戶 缺點:單頁面沒法記住以前滾動的位置,沒法在前進,後退的時候記住滾動的位置 前端路由一共有兩種實現方式,一種是經過 hash 的方式,一種是經過使用 pushState 的方式。
詳細資料能夠參考:
《什麼是「前端路由」》
《淺談前端路由》
《前端路由是什麼東西?》
詳細資料能夠參考:
《淺談前端單元測試》
檢測瀏覽器版本一共有兩種方式: 一種是檢測 window.navigator.userAgent 的值,但這種方式很不可靠,由於 userAgent 能夠被改寫,而且早期的瀏覽器如 ie,會經過假裝本身的 userAgent 的值爲 Mozilla 來躲過服務器的檢測。 第二種方式是功能檢測,根據每一個瀏覽器獨有的特性來進行判斷,如 ie 下獨有的 ActiveXObject。
詳細資料能夠參考:
《JavaScript 判斷瀏覽器類型》
Polyfill 指的是用於實現瀏覽器並不支持的原生 API 的代碼。 好比說 querySelectorAll 是不少現代瀏覽器都支持的原生 Web API,可是有些古老的瀏覽器並不支持,那麼假設有人寫了一段代碼來實現這個功能使這些瀏覽器也支持了這個功能,那麼這就能夠成爲一個 Polyfill。 一個 shim 是一個庫,有本身的 API,而不是單純實現原生不支持的 API。
詳細資料能夠參考:
《Web 開發中的「黑話」》
《Polyfill 爲什麼物》
// String.lastIndexOf() 方法返回指定值(本例中的'.')在調用該方法的字符串中最後出現的位置,若是沒找到則返回 -1。 // 對於 'filename' 和 '.hiddenfile' ,lastIndexOf 的返回值分別爲 0 和 -1 無符號右移操做符(>>>) 將 -1 轉換爲 4294967295 ,將 -2 轉換爲 4294967294 ,這個方法能夠保證邊緣狀況時文件名不變。 // String.prototype.slice() 從上面計算的索引處提取文件的擴展名。若是索引比文件名的長度大,結果爲""。 function getFileExtension(filename) { return filename.slice(((filename.lastIndexOf(".") - 1) >>> 0) + 2); }
詳細資料能夠參考:
《如何更有效的獲取文件擴展名》
相關知識點:
// 函數防抖: 在事件被觸發 n 秒後再執行回調,若是在這 n 秒內事件又被觸發,則從新計時。 // 函數節流: 規定一個單位時間,在這個單位時間內,只能有一次觸發事件的回調函數執行,若是在同一個單位時間內某事件被觸發屢次,只有一次能生效。 // 函數防抖的實現 function debounce(fn, wait) { var timer = null; return function() { var context = this, args = arguments; // 若是此時存在定時器的話,則取消以前的定時器從新記時 if (timer) { clearTimeout(timer); timer = null; } // 設置定時器,使事件間隔指定事件後執行 timer = setTimeout(() => { fn.apply(context, args); }, wait); }; } // 函數節流的實現; function throttle(fn, delay) { var preTime = Date.now(); return function() { var context = this, args = arguments, nowTime = Date.now(); // 若是兩次時間間隔超過了指定時間,則執行函數。 if (nowTime - preTime >= delay) { preTime = Date.now(); return fn.apply(context, args); } }; }
回答:
函數防抖是指在事件被觸發 n 秒後再執行回調,若是在這 n 秒內事件又被觸發,則從新計時。這能夠使用在一些點擊請求的事件上,避免由於用戶的屢次點擊向後端發送屢次請求。 函數節流是指規定一個單位時間,在這個單位時間內,只能有一次觸發事件的回調函數執行,若是在同一個單位時間內某事件被觸發屢次,只有一次能生效。節流能夠使用在 scroll 函數的事件監聽上,經過事件節流來下降事件調用的頻率。
詳細資料能夠參考:
《輕鬆理解 JS 函數節流和函數防抖》
《JavaScript 事件節流和事件防抖》
《JS 的防抖與節流》
相關知識點:
兩等號判等,會在比較時進行類型轉換。 三等號判等(判斷嚴格),比較時不進行隱式類型轉換,(類型不一樣則會返回false)。 Object.is 在三等號判等的基礎上特別處理了 NaN 、-0 和 +0 ,保證 -0 和 +0 再也不相同,但 Object.is(NaN, NaN) 會返回 true. Object.is 應被認爲有其特殊的用途,而不能用它認爲它比其它的相等對比更寬鬆或嚴格。
回答:
使用雙等號進行相等判斷時,若是兩邊的類型不一致,則會進行強制類型轉化後再進行比較。 使用三等號進行相等判斷時,若是兩邊的類型不一致時,不會作強制類型準換,直接返回 false。 使用 Object.is 來進行相等判斷時,通常狀況下和三等號的判斷相同,它處理了一些特殊的狀況,好比 -0 和 +0 再也不相等,兩個 NaN 認定爲是相等的。
相關知識點:
escape 和 encodeURI 都屬於 Percent-encoding,基本功能都是把 URI 非法字符轉化成合法字符,轉化後形式相似「%*」。 它們的根本區別在於,escape 在處理 0xff 以外字符的時候,是直接使用字符的 unicode 在前面加上一個「%u」,而 encode URI 則是先進行 UTF-8,再在 UTF-8 的每一個字節碼前加上一個「%」;在處理 0xff 之內字符時,編碼方式是同樣的(都是「%XX」,XX 爲字符的 16 進制 unicode,同時也是字符的 UTF-8),只是範圍(即哪些字符編碼哪些字符不編碼)不同。
回答:
encodeURI 是對整個 URI 進行轉義,將 URI 中的非法字符轉換爲合法字符,因此對於一些在 URI 中有特殊意義的字符不會進行轉義。 encodeURIComponent 是對 URI 的組成部分進行轉義,因此一些特殊字符也會獲得轉義。 escape 和 encodeURI 的做用相同,不過它們對於 unicode 編碼爲 0xff 以外字符的時候會有區別,escape 是直接在字符的 unicode 編碼前加上 %u,而 encodeURI 首先會將字符轉換爲 UTF-8 的格式,再在每一個字節前加上 %。
詳細資料能夠參考:
《escape,encodeURI,encodeURIComponent 有什麼區別?》
Unicode 是一種字符集合,如今可容納 100 多萬個字符。每一個字符對應一個不一樣的 Unicode 編碼,它只規定了符號的二進制代碼,卻沒有規定這個二進制代碼在計算機中如何編碼傳輸。 UTF-8 是一種對 Unicode 的編碼方式,它是一種變長的編碼方式,能夠用 1~4 個字節來表示一個字符。
詳細資料能夠參考:
《字符編碼詳解》
《字符編碼筆記:ASCII,Unicode 和 UTF-8》
相關知識點:
事件隊列是一個存儲着待執行任務的隊列,其中的任務嚴格按照時間前後順序執行,排在隊頭的任務將會率先執行,而排在隊尾的任務會最後執行。事件隊列每次僅執行一個任務,在該任務執行完畢以後,再執行下一個任務。執行棧則是一個相似於函數調用棧的運行容器,當執行棧爲空時,JS 引擎便檢查事件隊列,若是不爲空的話,事件隊列便將第一個任務壓入執行棧中運行。
回答:
由於 js 是單線程運行的,在代碼執行的時候,經過將不一樣函數的執行上下文壓入執行棧中來保證代碼的有序執行。在執行同步代碼的時候,若是遇到了異步事件,js 引擎並不會一直等待其返回結果,而是會將這個事件掛起,繼續執行執行棧中的其餘任務。當異步事件執行完畢後,再將異步事件對應的回調加入到與當前執行棧中不一樣的另外一個任務隊列中等待執行。任務隊列能夠分爲宏任務對列和微任務對列,噹噹前執行棧中的事件執行完畢後,js 引擎首先會判斷微任務對列中是否有任務能夠執行,若是有就將微任務隊首的事件壓入棧中執行。當微任務對列中的任務都執行完成後再去判斷宏任務對列中的任務。 微任務包括了 promise 的回調、node 中的 process.nextTick 、對 Dom 變化監聽的 MutationObserver。 宏任務包括了 script 腳本的執行、setTimeout ,setInterval ,setImmediate 一類的定時事件,還有如 I/O 操做、UI 渲 染等。
詳細資料能夠參考:
《瀏覽器事件循環機制(event loop)》
《詳解 JavaScript 中的 Event Loop(事件循環)機制》
《什麼是 Event Loop?》
《這一次,完全弄懂 JavaScript 執行機制》
相關資料:
// 淺拷貝的實現; function shallowCopy(object) { // 只拷貝對象 if (!object || typeof object !== "object") return; // 根據 object 的類型判斷是新建一個數組仍是對象 let newObject = Array.isArray(object) ? [] : {}; // 遍歷 object,而且判斷是 object 的屬性才拷貝 for (let key in object) { if (object.hasOwnProperty(key)) { newObject[key] = object[key]; } } return newObject; } // 深拷貝的實現; function deepCopy(object) { if (!object || typeof object !== "object") return; let newObject = Array.isArray(object) ? [] : {}; for (let key in object) { if (object.hasOwnProperty(key)) { newObject[key] = typeof object[key] === "object" ? deepCopy(object[key]) : object[key]; } } return newObject; }
回答:
淺拷貝指的是將一個對象的屬性值複製到另外一個對象,若是有的屬性的值爲引用類型的話,那麼會將這個引用的地址複製給對象,所以兩個對象會有同一個引用類型的引用。淺拷貝能夠使用 Object.assign 和展開運算符來實現。 深拷貝相對淺拷貝而言,若是遇到屬性值爲引用類型的時候,它新建一個引用類型並將對應的值複製給它,所以對象得到的一個新的引用類型而不是一個原有類型的引用。深拷貝對於一些對象能夠使用 JSON 的兩個函數來實現,可是因爲 JSON 的對象格式比 js 的對象格式更加嚴格,因此若是屬性值裏邊出現函數或者 Symbol 類型的值時,會轉換失敗。
詳細資料能夠參考:
《JavaScript 專題之深淺拷貝》
《前端面試之道》
相關資料:
// call函數實現 Function.prototype.myCall = function(context) { // 判斷調用對象 if (typeof this !== "function") { console.error("type error"); } // 獲取參數 let args = [...arguments].slice(1), result = null; // 判斷 context 是否傳入,若是未傳入則設置爲 window context = context || window; // 將調用函數設爲對象的方法 context.fn = this; // 調用函數 result = context.fn(...args); // 將屬性刪除 delete context.fn; return result; }; // apply 函數實現 Function.prototype.myApply = function(context) { // 判斷調用對象是否爲函數 if (typeof this !== "function") { throw new TypeError("Error"); } let result = null; // 判斷 context 是否存在,若是未傳入則爲 window context = context || window; // 將函數設爲對象的方法 context.fn = this; // 調用方法 if (arguments[1]) { result = context.fn(...arguments[1]); } else { result = context.fn(); } // 將屬性刪除 delete context.fn; return result; }; // bind 函數實現 Function.prototype.myBind = function(context) { // 判斷調用對象是否爲函數 if (typeof this !== "function") { throw new TypeError("Error"); } // 獲取參數 var args = [...arguments].slice(1), fn = this; return function Fn() { // 根據調用方式,傳入不一樣綁定值 return fn.apply( this instanceof Fn ? this : context, args.concat(...arguments) ); }; };
回答:
call 函數的實現步驟:
apply 函數的實現步驟:
bind 函數的實現步驟:
詳細資料能夠參考:
《手寫 call、apply 及 bind 函數》
《JavaScript 深刻之 call 和 apply 的模擬實現》
// 函數柯里化指的是一種將使用多個參數的一個函數轉換成一系列使用一個參數的函數的技術。 function curry(fn, args) { // 獲取函數須要的參數長度 let length = fn.length; args = args || []; return function() { let subArgs = args.slice(0); // 拼接獲得現有的全部參數 for (let i = 0; i < arguments.length; i++) { subArgs.push(arguments[i]); } // 判斷參數的長度是否已經知足函數所需參數的長度 if (subArgs.length >= length) { // 若是知足,執行函數 return fn.apply(this, subArgs); } else { // 若是不知足,遞歸返回科裏化的函數,等待參數的傳入 return curry.call(this, fn, subArgs); } }; } // es6 實現 function curry(fn, ...args) { return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args); }
詳細資料能夠參考:
《JavaScript 專題之函數柯里化》
當計算機計算 0.1+0.2 的時候,實際上計算的是這兩個數字在計算機裏所存儲的二進制,0.1 和 0.2 在轉換爲二進制表示的時候會出現位數無限循環的狀況。js 中是以 64 位雙精度格式來存儲數字的,只有 53 位的有效數字,超過這個長度的位數會被截取掉這樣就形成了精度丟失的問題。這是第一個會形成精度丟失的地方。在對兩個以 64 位雙精度格式的數據進行計算的時候,首先會進行對階的處理,對階指的是將階碼對齊,也就是將小數點的位置對齊後,再進行計算,通常是小階向大階對齊,所以小階的數在對齊的過程當中,有效數字會向右移動,移動後超過有效位數的位會被截取掉,這是第二個可能會出現精度丟失的地方。當兩個數據階碼對齊後,進行相加運算後,獲得的結果可能會超過 53 位有效數字,所以超過的位數也會被截取掉,這是可能發生精度丟失的第三個地方。 對於這樣的狀況,咱們能夠將其轉換爲整數後再進行運算,運算後再轉換爲對應的小數,以這種方式來解決這個問題。 咱們還能夠將兩個數相加的結果和右邊相減,若是相減的結果小於一個極小數,那麼咱們就能夠認定結果是相等的,這個極小數能夠 使用 es6 的 Number.EPSILON
詳細資料能夠參考:
《十進制的 0.1 爲何不能用二進制很好的表示?》
《十進制浮點數轉成二進制》
《浮點數的二進制表示》
《js 浮點數存儲精度丟失原理》
《浮點數精度之謎》
《JavaScript 浮點數陷阱及解法》
《0.1+0.2 !== 0.3?》
《JavaScript 中奇特的~運算符》
原碼是計算機中對數字的二進制的定點表示方法,最高位表示符號位,其他位表示數值位。優勢是易於分辨,缺點是不可以直接參與運算。 正數的反碼和其原碼同樣;負數的反碼,符號位爲1,數值部分按原碼取反。 如 [+7]原 = 00000111,[+7]反 = 00000111; [-7]原 = 10000111,[-7]反 = 11111000。 正數的補碼和其原碼同樣;負數的補碼爲其反碼加1。 例如 [+7]原 = 00000111,[+7]反 = 00000111,[+7]補 = 00000111; [-7]原 = 10000111,[-7]反 = 11111000,[-7]補 = 11111001 之因此在計算機中使用補碼來表示負數的緣由是,這樣能夠將加法運算擴展到全部的數值計算上,所以在數字電路中咱們只須要考慮加法器的設計就好了,而不用再爲減法設置新的數字電路。
詳細資料能夠參考:
《關於 2 的補碼》
toPrecision 用於處理精度,精度是從左至右第一個不爲 0 的數開始數起。 toFixed 是對小數點後指定位數取整,從小數點開始數起。 Math.round 是將一個數字四捨五入到一個整數。
XSS 攻擊指的是跨站腳本攻擊,是一種代碼注入攻擊。攻擊者經過在網站注入惡意腳本,使之在用戶的瀏覽器上運行,從而盜取用戶的信息如 cookie 等。 XSS 的本質是由於網站沒有對惡意代碼進行過濾,與正常的代碼混合在一塊兒了,瀏覽器沒有辦法分辨哪些腳本是可信的,從而致使了惡意代碼的執行。 XSS 通常分爲存儲型、反射型和 DOM 型。 存儲型指的是惡意代碼提交到了網站的數據庫中,當用戶請求數據的時候,服務器將其拼接爲 HTML 後返回給了用戶,從而致使了惡意代碼的執行。 反射型指的是攻擊者構建了特殊的 URL,當服務器接收到請求後,從 URL 中獲取數據,拼接到 HTML 後返回,從而致使了惡意代碼的執行。 DOM 型指的是攻擊者構建了特殊的 URL,用戶打開網站後,js 腳本從 URL 中獲取數據,從而致使了惡意代碼的執行。 XSS 攻擊的預防能夠從兩個方面入手,一個是惡意代碼提交的時候,一個是瀏覽器執行惡意代碼的時候。 對於第一個方面,若是咱們對存入數據庫的數據都進行的轉義處理,可是一個數據可能在多個地方使用,有的地方可能不須要轉義,因爲咱們沒有辦法判斷數據最後的使用場景,因此直接在輸入端進行惡意代碼的處理,實際上是不太可靠的。 所以咱們能夠從瀏覽器的執行來進行預防,一種是使用純前端的方式,不用服務器端拼接後返回。另外一種是對須要插入到 HTML 中的代碼作好充分的轉義。對於 DOM 型的攻擊,主要是前端腳本的不可靠而形成的,咱們對於數據獲取渲染和字符串拼接的時候應該對可能出現的惡意代碼狀況進行判斷。 還有一些方式,好比使用 CSP ,CSP 的本質是創建一個白名單,告訴瀏覽器哪些外部資源能夠加載和執行,從而防止惡意代碼的注入攻擊。 還能夠對一些敏感信息進行保護,好比 cookie 使用 http-only ,使得腳本沒法獲取。也能夠使用驗證碼,避免腳本假裝成用戶執行一些操做。
詳細資料能夠參考:
《前端安全系列(一):如何防止 XSS 攻擊?》
CSP 指的是內容安全策略,它的本質是創建一個白名單,告訴瀏覽器哪些外部資源能夠加載和執行。咱們只須要配置規則,如何攔截由瀏覽器本身來實現。 一般有兩種方式來開啓 CSP,一種是設置 HTTP 首部中的 Content-Security-Policy,一種是設置 meta 標籤的方式 <meta http-equiv="Content-Security-Policy">
詳細資料能夠參考:
《內容安全策略(CSP)》
《前端面試之道》
CSRF 攻擊指的是跨站請求僞造攻擊,攻擊者誘導用戶進入一個第三方網站,而後該網站向被攻擊網站發送跨站請求。若是用戶在被 攻擊網站中保存了登陸狀態,那麼攻擊者就能夠利用這個登陸狀態,繞事後臺的用戶驗證,冒充用戶向服務器執行一些操做。 CSRF 攻擊的本質是利用了 cookie 會在同源請求中攜帶發送給服務器的特色,以此來實現用戶的冒充。 通常的 CSRF 攻擊類型有三種: 第一種是 GET 類型的 CSRF 攻擊,好比在網站中的一個 img 標籤裏構建一個請求,當用戶打開這個網站的時候就會自動發起提 交。 第二種是 POST 類型的 CSRF 攻擊,好比說構建一個表單,而後隱藏它,當用戶進入頁面時,自動提交這個表單。 第三種是連接類型的 CSRF 攻擊,好比說在 a 標籤的 href 屬性裏構建一個請求,而後誘導用戶去點擊。 CSRF 能夠用下面幾種方法來防禦: 第一種是同源檢測的方法,服務器根據 http 請求頭中 origin 或者 referer 信息來判斷請求是否爲容許訪問的站點,從而對請求進行過濾。當 origin 或者 referer 信息都不存在的時候,直接阻止。這種方式的缺點是有些狀況下 referer 能夠被僞造。還有就是咱們這種方法同時把搜索引擎的連接也給屏蔽了,因此通常網站會容許搜索引擎的頁面請求,可是相應的頁面請求這種請求方式也可能被攻擊者給利用。 第二種方法是使用 CSRF Token 來進行驗證,服務器向用戶返回一個隨機數 Token ,當網站再次發起請求時,在請求參數中加入服務器端返回的 token ,而後服務器對這個 token 進行驗證。這種方法解決了使用 cookie 單一驗證方式時,可能會被冒用的問題,可是這種方法存在一個缺點就是,咱們須要給網站中的全部請求都添加上這個 token,操做比較繁瑣。還有一個問題是通常不會只有一臺網站服務器,若是咱們的請求通過負載平衡轉移到了其餘的服務器,可是這個服務器的 session 中沒有保留這個 token 的話,就沒有辦法驗證了。這種狀況咱們能夠經過改變 token 的構建方式來解決。 第三種方式使用雙重 Cookie 驗證的辦法,服務器在用戶訪問網站頁面時,向請求域名注入一個Cookie,內容爲隨機字符串,而後當用戶再次向服務器發送請求的時候,從 cookie 中取出這個字符串,添加到 URL 參數中,而後服務器經過對 cookie 中的數據和參數中的數據進行比較,來進行驗證。使用這種方式是利用了攻擊者只能利用 cookie,可是不能訪問獲取 cookie 的特色。而且這種方法比 CSRF Token 的方法更加方便,而且不涉及到分佈式訪問的問題。這種方法的缺點是若是網站存在 XSS 漏洞的,那麼這種方式會失效。同時這種方式不能作到子域名的隔離。 第四種方式是使用在設置 cookie 屬性的時候設置 Samesite ,限制 cookie 不能做爲被第三方使用,從而能夠避免被攻擊者利用。Samesite 一共有兩種模式,一種是嚴格模式,在嚴格模式下 cookie 在任何狀況下都不可能做爲第三方 Cookie 使用,在寬鬆模式下,cookie 能夠被請求是 GET 請求,且會發生頁面跳轉的請求所使用。
詳細資料能夠參考:
《前端安全系列之二:如何防止 CSRF 攻擊?》
[《[ HTTP 趣談] origin, referer 和 host 區別》](https://www.jianshu.com/p/1f9...
Samesite Cookie 表示同站 cookie,避免 cookie 被第三方所利用。 將 Samesite 設爲 strict ,這種稱爲嚴格模式,表示這個 cookie 在任何狀況下都不可能做爲第三方 cookie。 將 Samesite 設爲 Lax ,這種模式稱爲寬鬆模式,若是這個請求是個 GET 請求,而且這個請求改變了當前頁面或者打開了新的頁面,那麼這個 cookie 能夠做爲第三方 cookie,其他狀況下都不能做爲第三方 cookie。 使用這種方法的缺點是,由於它不支持子域,因此子域沒有辦法與主域共享登陸信息,每次轉入子域的網站,都回從新登陸。還有一個問題就是它的兼容性不夠好。
點擊劫持是一種視覺欺騙的攻擊手段,攻擊者將須要攻擊的網站經過 iframe 嵌套的方式嵌入本身的網頁中,並將 iframe 設置爲透明,在頁面中透出一個按鈕誘導用戶點擊。 咱們能夠在 http 相應頭中設置 X-FRAME-OPTIONS 來防護用 iframe 嵌套的點擊劫持攻擊。經過不一樣的值,能夠規定頁面在特 定的一些狀況才能做爲 iframe 來使用。
詳細資料能夠參考:
《web 安全之--點擊劫持攻擊與防護技術簡介》
SQL 注入攻擊指的是攻擊者在 HTTP 請求中注入惡意的 SQL 代碼,服務器使用參數構建數據庫 SQL 命令時,惡意 SQL 被一塊兒構 造,破壞原有 SQL 結構,並在數據庫中執行,達到編寫程序時意料以外結果的攻擊行爲。
詳細資料能夠參考:
《Web 安全漏洞之 SQL 注入》
《如何防範常見的 Web 攻擊》
MVC、MVP 和 MVVM 是三種常見的軟件架構設計模式,主要經過分離關注點的方式來組織代碼結構,優化咱們的開發效率。 好比說咱們實驗室在之前項目開發的時候,使用單頁應用時,每每一個路由頁面對應了一個腳本文件,全部的頁面邏輯都在一個腳本文件裏。頁面的渲染、數據的獲取,對用戶事件的響應全部的應用邏輯都混合在一塊兒,這樣在開發簡單項目時,可能看不出什麼問題,當時一旦項目變得複雜,那麼整個文件就會變得冗長,混亂,這樣對咱們的項目開發和後期的項目維護是很是不利的。 MVC 經過分離 Model、View 和 Controller 的方式來組織代碼結構。其中 View 負責頁面的顯示邏輯,Model 負責存儲頁面的業務數據,以及對相應數據的操做。而且 View 和 Model 應用了觀察者模式,當 Model 層發生改變的時候它會通知有關 View 層更新頁面。Controller 層是 View 層和 Model 層的紐帶,它主要負責用戶與應用的響應操做,當用戶與頁面產生交互的時候,Co ntroller 中的事件觸發器就開始工做了,經過調用 Model 層,來完成對 Model 的修改,而後 Model 層再去通知 View 層更新。 MVP 模式與 MVC 惟一不一樣的在於 Presenter 和 Controller。在 MVC 模式中咱們使用觀察者模式,來實現當 Model 層數據發生變化的時候,通知 View 層的更新。這樣 View 層和 Model 層耦合在一塊兒,當項目邏輯變得複雜的時候,可能會形成代碼的混亂,而且可能會對代碼的複用性形成一些問題。MVP 的模式經過使用 Presenter 來實現對 View 層和 Model 層的解耦。MVC 中的 Controller 只知道 Model 的接口,所以它沒有辦法控制 View 層的更新,MVP 模式中,View 層的接口暴露給了 Presenter 所以咱們能夠在 Presenter 中將 Model 的變化和 View 的變化綁定在一塊兒,以此來實現 View 和 Model 的同步更新。這樣就實現了對 View 和 Model 的解耦,Presenter 還包含了其餘的響應邏輯。 MVVM 模式中的 VM,指的是 ViewModel,它和 MVP 的思想實際上是相同的,不過它經過雙向的數據綁定,將 View 和 Model 的同步更新給自動化了。當 Model 發生變化的時候,ViewModel 就會自動更新;ViewModel 變化了,View 也會更新。這樣就將 Presenter 中的工做給自動化了。我瞭解過一點雙向數據綁定的原理,好比 vue 是經過使用數據劫持和發佈訂閱者模式來實現的這一功 能。
詳細資料能夠參考:
《淺析前端開發中的 MVC/MVP/MVVM 模式》
《MVC,MVP 和 MVVM 的圖示》
《MVVM》
《一篇文章瞭解架構模式:MVC/MVP/MVVM》
vue 經過使用雙向數據綁定,來實現了 View 和 Model 的同步更新。vue 的雙向數據綁定主要是經過使用數據劫持和發佈訂閱者模式來實現的。 首先咱們經過 Object.defineProperty() 方法來對 Model 數據各個屬性添加訪問器屬性,以此來實現數據的劫持,所以當 Model 中的數據發生變化的時候,咱們能夠經過配置的 setter 和 getter 方法來實現對 View 層數據更新的通知。 數據在 html 模板中一共有兩種綁定狀況,一種是使用 v-model 來對 value 值進行綁定,一種是做爲文本綁定,在對模板引擎進行解析的過程當中。 若是遇到元素節點,而且屬性值包含 v-model 的話,咱們就從 Model 中去獲取 v-model 所對應的屬性的值,並賦值給元素的 value 值。而後給這個元素設置一個監聽事件,當 View 中元素的數據發生變化的時候觸發該事件,通知 Model 中的對應的屬性的值進行更新。 若是遇到了綁定的文本節點,咱們使用 Model 中對應的屬性的值來替換這個文本。對於文本節點的更新,咱們使用了發佈訂閱者模式,屬性做爲一個主題,咱們爲這個節點設置一個訂閱者對象,將這個訂閱者對象加入這個屬性主題的訂閱者列表中。當 Model 層數據發生改變的時候,Model 做爲發佈者向主題發出通知,主題收到通知再向它的全部訂閱者推送,訂閱者收到通知後更改本身的數 據。
詳細資料能夠參考:
《Vue.js 雙向綁定的實現原理》
Object.defineProperty 函數一共有三個參數,第一個參數是須要定義屬性的對象,第二個參數是須要定義的屬性,第三個是該屬性描述符。 一個屬性的描述符有四個屬性,分別是 value 屬性的值,writable 屬性是否可寫,enumerable 屬性是否可枚舉,configurable 屬性是否可配置修改。
詳細資料能夠參考:
《Object.defineProperty()》
有一些對屬性的操做,使用這種方法沒法攔截,好比說經過下標方式修改數組數據或者給對象新增屬性,vue 內部經過重寫函數解決了這個問題。在 Vue3.0 中已經不使用這種方式了,而是經過使用 Proxy 對對象進行代理,從而實現數據劫持。使用 Proxy 的好處是它能夠完美的監聽到任何方式的數據改變,惟一的缺點是兼容性的問題,由於這是 ES6 的語法。
我對 Virtual DOM 的理解是, 首先對咱們將要插入到文檔中的 DOM 樹結構進行分析,使用 js 對象將其表示出來,好比一個元素對象,包含 TagName、props 和 Children 這些屬性。而後咱們將這個 js 對象樹給保存下來,最後再將 DOM 片斷插入到文檔中。 當頁面的狀態發生改變,咱們須要對頁面的 DOM 的結構進行調整的時候,咱們首先根據變動的狀態,從新構建起一棵對象樹,而後將這棵新的對象樹和舊的對象樹進行比較,記錄下兩棵樹的的差別。 最後將記錄的有差別的地方應用到真正的 DOM 樹中去,這樣視圖就更新了。 我認爲 Virtual DOM 這種方法對於咱們須要有大量的 DOM 操做的時候,可以很好的提升咱們的操做效率,經過在操做前肯定須要作的最小修改,儘量的減小 DOM 操做帶來的重流和重繪的影響。其實 Virtual DOM 並不必定比咱們真實的操做 DOM 要快,這種方法的目的是爲了提升咱們開發時的可維護性,在任意的狀況下,都能保證一個儘可能小的性能消耗去進行操做。
詳細資料能夠參考:
《Virtual DOM》
《理解 Virtual DOM》
《深度剖析:如何實現一個 Virtual DOM 算法》
《網上都說操做真實 DOM 慢,但測試結果卻比 React 更快,爲何?》
兩個樹的徹底 diff 算法的時間複雜度爲 O(n^3) ,可是在前端中,咱們不多會跨層級的移動元素,因此咱們只須要比較同一層級的元素進行比較,這樣就能夠將算法的時間複雜度下降爲 O(n)。 算法首先會對新舊兩棵樹進行一個深度優先的遍歷,這樣每一個節點都會有一個序號。在深度遍歷的時候,每遍歷到一個節點,咱們就將這個節點和新的樹中的節點進行比較,若是有差別,則將這個差別記錄到一個對象中。 在對列表元素進行對比的時候,因爲 TagName 是重複的,因此咱們不能使用這個來對比。咱們須要給每個子節點加上一個 key,列表對比的時候使用 key 來進行比較,這樣咱們纔可以複用老的 DOM 樹上的節點。
詳細資料能夠參考:
《你須要知道的 requestAnimationFrame》
《CSS3 動畫那麼強,requestAnimationFrame 還有毛線用?》
我當時使用 webpack 的一個最主要緣由是爲了簡化頁面依賴的管理,而且經過將其打包爲一個文件來下降頁面加載時請求的資源 數。 我認爲 webpack 的主要原理是,它將全部的資源都當作是一個模塊,而且把頁面邏輯看成一個總體,經過一個給定的入口文件,webpack 從這個文件開始,找到全部的依賴文件,將各個依賴文件模塊經過 loader 和 plugins 處理後,而後打包在一塊兒,最後輸出一個瀏覽器可識別的 JS 文件。 Webpack 具備四個核心的概念,分別是 Entry(入口)、Output(輸出)、loader 和 Plugins(插件)。 Entry 是 webpack 的入口起點,它指示 webpack 應該從哪一個模塊開始着手,來做爲其構建內部依賴圖的開始。 Output 屬性告訴 webpack 在哪裏輸出它所建立的打包文件,也可指定打包文件的名稱,默認位置爲 ./dist。 loader 能夠理解爲 webpack 的編譯器,它使得 webpack 能夠處理一些非 JavaScript 文件。在對 loader 進行配置的時候,test 屬性,標誌有哪些後綴的文件應該被處理,是一個正則表達式。use 屬性,指定 test 類型的文件應該使用哪一個 loader 進行預處理。經常使用的 loader 有 css-loader、style-loader 等。 插件能夠用於執行範圍更廣的任務,包括打包、優化、壓縮、搭建服務器等等,要使用一個插件,通常是先使用 npm 包管理器進行安裝,而後在配置文件中引入,最後將其實例化後傳遞給 plugins 數組屬性。 使用 webpack 的確可以提供咱們對於項目的管理,可是它的缺點就是調試和配置起來太麻煩了。但如今 webpack4.0 的免配置必定程度上解決了這個問題。可是我感受就是對我來講,就是一個黑盒,不少時候出現了問題,沒有辦法很好的定位。
詳細資料能夠參考:
《不聊 webpack 配置,來講說它的原理》
《前端工程化——構建工具選型:grunt、gulp、webpack》
《淺入淺出 webpack》
《前端構建工具發展及其比較》
clientWidth/clientHeight 返回的是元素的內部寬度,它的值只包含 content + padding,若是有滾動條,不包含滾動條。 clientTop 返回的是上邊框的寬度。 clientLeft 返回的左邊框的寬度。 offsetWidth/offsetHeight 返回的是元素的佈局寬度,它的值包含 content + padding + border 包含了滾動條。 offsetTop 返回的是當前元素相對於其 offsetParent 元素的頂部的距離。 offsetLeft 返回的是當前元素相對於其 offsetParent 元素的左部的距離。 scrollWidth/scrollHeight 返回值包含 content + padding + 溢出內容的尺寸。 scrollTop 屬性返回的是一個元素的內容垂直滾動的像素數。 scrollLeft 屬性返回的是元素滾動條到元素左邊的距離。
詳細資料能夠參考:
《最全的獲取元素寬高及位置的方法》
《用 Javascript 獲取頁面元素的位置》
簡單說,"函數式編程"是一種"編程範式"(programming paradigm),也就是如何編寫程序的方法論。 它具備如下特性:閉包和高階函數、惰性計算、遞歸、函數是"第一等公民"、只用"表達式"。
詳細資料能夠參考:
《函數式編程初探》
相關資料:
回調函數 優勢:簡單、容易理解 缺點:不利於維護,代碼耦合高 事件監聽(採用時間驅動模式,取決於某個事件是否發生): 優勢:容易理解,能夠綁定多個事件,每一個事件能夠指定多個回調函數 缺點:事件驅動型,流程不夠清晰 發佈/訂閱(觀察者模式) 相似於事件監聽,可是能夠經過‘消息中心’,瞭解如今有多少發佈者,多少訂閱者 Promise 對象 優勢:能夠利用 then 方法,進行鏈式寫法;能夠書寫錯誤時的回調函數; 缺點:編寫和理解,相對比較難 Generator 函數 優勢:函數體內外的數據交換、錯誤處理機制 缺點:流程管理不方便 async 函數 優勢:內置執行器、更好的語義、更廣的適用性、返回的是 Promise、結構清晰。 缺點:錯誤處理機制
回答:
js 中的異步機制能夠分爲如下幾種: 第一種最多見的是使用回調函數的方式,使用回調函數的方式有一個缺點是,多個回調函數嵌套的時候會形成回調函數地獄,上下兩層的回調函數間的代碼耦合度過高,不利於代碼的可維護。 第二種是 Promise 的方式,使用 Promise 的方式能夠將嵌套的回調函數做爲鏈式調用。可是使用這種方法,有時會形成多個 then 的鏈式調用,可能會形成代碼的語義不夠明確。 第三種是使用 generator 的方式,它能夠在函數的執行過程當中,將函數的執行權轉移出去,在函數外部咱們還能夠將執行權轉移回來。當咱們遇到異步函數執行的時候,將函數執行權轉移出去,當異步函數執行完畢的時候咱們再將執行權給轉移回來。所以咱們在 generator 內部對於異步操做的方式,能夠以同步的順序來書寫。使用這種方式咱們須要考慮的問題是什麼時候將函數的控制權轉移回來,所以咱們須要有一個自動執行 generator 的機制,好比說 co 模塊等方式來實現 generator 的自動執行。 第四種是使用 async 函數的形式,async 函數是 generator 和 promise 實現的一個自動執行的語法糖,它內部自帶執行器,當函數內部執行到一個 await 語句的時候,若是語句返回一個 promise 對象,那麼函數將會等待 promise 對象的狀態變爲 resolve 後再繼續向下執行。所以咱們能夠將異步邏輯,轉化爲同步的順序來書寫,而且這個函數能夠自動執行。
CSS3 的動畫的優勢 在性能上會稍微好一些,瀏覽器會對 CSS3 的動畫作一些優化 代碼相對簡單 缺點 在動畫控制上不夠靈活 兼容性很差 JavaScript 的動畫正好彌補了這兩個缺點,控制能力很強,能夠單幀的控制、變換,同時寫得好徹底能夠兼容 IE6,而且功能強大。對於一些複雜控制的動畫,使用 javascript 會比較靠譜。而在實現一些小的交互動效的時候,就多考慮考慮 CSS 吧
誤區:咱們常常說 get 請求參數的大小存在限制,而 post 請求的參數大小是無限制的。 實際上 HTTP 協議從未規定 GET/POST 的請求長度限制是多少。對 get 請求參數的限制是來源與瀏覽器或web 服務器,瀏覽器或 web 服務器限制了 url 的長度。爲了明確這個概念,咱們必須再次強調下面幾點:
URI: Uniform Resource Identifier 指的是統一資源標識符 URL: Uniform Resource Location 指的是統一資源定位符 URN: Universal Resource Name 指的是統一資源名稱 URI 指的是統一資源標識符,用惟一的標識來肯定一個資源,它是一種抽象的定義,也就是說,無論使用什麼方法來定義,只要能惟一的標識一個資源,就能夠稱爲 URI。 URL 指的是統一資源定位符,URN 指的是統一資源名稱。URL 和 URN 是 URI 的子集,URL 能夠理解爲使用地址來標識資源,URN 能夠理解爲使用名稱來標識資源。
詳細資料能夠參考:
《HTTP 協議中 URI 和 URL 有什麼區別?》
《你知道 URL、URI 和 URN 三者之間的區別嗎?》
《URI、URL 和 URN 的區別》
相關知識點:
get 請求相似於查找的過程,用戶獲取數據,能夠不用每次都與數據庫鏈接,因此能夠使用緩存。 post 不一樣,post 作的通常是修改和刪除的工做,因此必須與數據庫交互,因此不能使用緩存。所以 get 請求適合於請求緩存。
回答:
緩存通常只適用於那些不會更新服務端數據的請求。通常 get 請求都是查找請求,不會對服務器資源數據形成修改,而 post 請求通常都會對服務器數據形成修改,因此,通常會對 get 請求進行緩存,不多會對 post 請求進行緩存。
詳細資料能夠參考:
《HTML 關於 post 和 get 的區別以及緩存問題的理解》
相關知識點:
預加載:提早加載圖片,當用戶須要查看時可直接從本地緩存中渲染。 懶加載:懶加載的主要目的是做爲服務器前端的優化,減小請求數或延遲請求數。 兩種技術的本質:二者的行爲是相反的,一個是提早加載,一個是遲緩甚至不加載。 懶加載對服務器前端有必定的緩解壓力做用,預加載則會增長服務器前端壓力。
回答:
懶加載也叫延遲加載,指的是在長網頁中延遲加載圖片的時機,當用戶須要訪問時,再去加載,這樣能夠提升網站的首屏加載速度,提高用戶的體驗,而且能夠減小服務器的壓力。它適用於圖片不少,頁面很長的電商網站的場景。懶加載的實現原理是,將頁面上的圖片的 src 屬性設置爲空字符串,將圖片的真實路徑保存在一個自定義屬性中,當頁面滾動的時候,進行判斷,若是圖片進入頁面可視區域內,則從自定義屬性中取出真實路徑賦值給圖片的 src 屬性,以此來實現圖片的延遲加載。 預加載指的是將所需的資源提早請求加載到本地,這樣後面在須要用到時就直接從緩存取資源。經過預加載可以減小用戶的等待時間,提升用戶的體驗。我瞭解的預加載的最經常使用的方式是使用 js 中的 image 對象,經過爲 image 對象來設置 scr 屬性,來實現圖片的預加載。 這兩種方式都是提升網頁性能的方式,二者主要區別是一個是提早加載,一個是遲緩甚至不加載。懶加載對服務器前端有必定的緩解壓力做用,預加載則會增長服務器前端壓力。
詳細資料能夠參考:
《懶加載和預加載》
《網頁圖片加載優化方案》
《基於用戶行爲的圖片等資源預加載》
當鼠標移動到元素上時就會觸發 mouseenter 事件,相似 mouseover,它們二者之間的差異是 mouseenter 不會冒泡。 因爲 mouseenter 不支持事件冒泡,致使在一個元素的子元素上進入或離開的時候會觸發其 mouseover 和 mouseout 事件,可是卻不會觸發 mouseenter 和 mouseleave 事件。
詳細資料能夠參考:
《mouseenter 與 mouseover 爲什麼這般糾纏不清?》
相關知識點:
首先是三個事件,分別是 mousedown,mousemove,mouseup 當鼠標點擊按下的時候,須要一個 tag 標識此時已經按下,能夠執行 mousemove 裏面的具體方法。 clientX,clientY 標識的是鼠標的座標,分別標識橫座標和縱座標,而且咱們用 offsetX 和 offsetY 來表示 元素的元素的初始座標,移動的舉例應該是: 鼠標移動時候的座標-鼠標按下去時候的座標。 也就是說定位信息爲: 鼠標移動時候的座標-鼠標按下去時候的座標+元素初始狀況下的 offetLeft.
回答:
一個元素的拖拽過程,咱們能夠分爲三個步驟,第一步是鼠標按下目標元素,第二步是鼠標保持按下的狀態移動鼠標,第三步是鼠 標擡起,拖拽過程結束。 這三步分別對應了三個事件,mousedown 事件,mousemove 事件和 mouseup 事件。只有在鼠標按下的狀態移動鼠標咱們纔會 執行拖拽事件,所以咱們須要在 mousedown 事件中設置一個狀態來標識鼠標已經按下,而後在 mouseup 事件中再取消這個狀 態。在 mousedown 事件中咱們首先應該判斷,目標元素是否爲拖拽元素,若是是拖拽元素,咱們就設置狀態而且保存這個時候鼠 標的位置。而後在 mousemove 事件中,咱們經過判斷鼠標如今的位置和之前位置的相對移動,來肯定拖拽元素在移動中的座標。 最後 mouseup 事件觸發後,清除狀態,結束拖拽事件。
詳細資料能夠參考:
《原生 js 實現拖拽功能基本思路》
相關知識點:
// 思路是使用遞歸函數,不斷地去執行 setTimeout 從而達到 setInterval 的效果 function mySetInterval(fn, timeout) { // 控制器,控制定時器是否繼續執行 var timer = { flag: true }; // 設置遞歸函數,模擬定時器執行。 function interval() { if (timer.flag) { fn(); setTimeout(interval, timeout); } } // 啓動定時器 setTimeout(interval, timeout); // 返回控制器 return timer; }
回答:
setInterval 的做用是每隔一段指定時間執行一個函數,可是這個執行不是真的到了時間當即執行,它真正的做用是每隔一段時間將事件加入事件隊列中去,只有噹噹前的執行棧爲空的時候,才能去從事件隊列中取出事件執行。因此可能會出現這樣的狀況,就是當前執行棧執行的時間很長,致使事件隊列裏邊積累多個定時器加入的事件,當執行棧結束的時候,這些事件會依次執行,所以就不能到間隔一段時間執行的效果。 針對 setInterval 的這個缺點,咱們能夠使用 setTimeout 遞歸調用來模擬 setInterval,這樣咱們就確保了只有一個事件結束了,咱們纔會觸發下一個定時器事件,這樣解決了 setInterval 的問題。
詳細資料能夠參考:
《用 setTimeout 實現 setInterval》
《setInterval 有什麼缺點?》
rest 參數(形式爲...變量名),用於獲取函數的多餘參數。
尾調用指的是函數的最後一步調用另外一個函數。咱們代碼執行是基於執行棧的,因此當咱們在一個函數裏調用另外一個函數時,咱們會保留當前的執行上下文,而後再新建另一個執行上下文加入棧中。使用尾調用的話,由於已是函數的最後一步,因此這個時候咱們能夠沒必要再保留當前的執行上下文,從而節省了內存,這就是尾調用優化。可是 ES6 的尾調用優化只在嚴格模式下開啓,正常模式是無效的。
Proxy 用於修改某些操做的默認行爲,等同於在語言層面作出修改,因此屬於一種「元編程」,即對編程語言進行編程。 Proxy 能夠理解成,在目標對象以前架設一層「攔截」,外界對該對象的訪問,都必須先經過這層攔截,所以提供了一種機制,能夠對外界的訪問進行過濾和改寫。Proxy 這個詞的原意是代理,用在這裏表示由它來「代理」某些操做,能夠譯爲「代理器」。
當 Node 遇到 require(X) 時,按下面的順序處理。 (1)若是 X 是內置模塊(好比 require('http')) a. 返回該模塊。 b. 再也不繼續執行。 (2)若是 X 以 "./" 或者 "/" 或者 "../" 開頭 a. 根據 X 所在的父模塊,肯定 X 的絕對路徑。 b. 將 X 當成文件,依次查找下面文件,只要其中有一個存在,就返回該文件,再也不繼續執行。 X X.js X.json X.node c. 將 X 當成目錄,依次查找下面文件,只要其中有一個存在,就返回該文件,再也不繼續執行。 X/package.json(main字段) X/index.js X/index.json X/index.node (3)若是 X 不帶路徑 a. 根據 X 所在的父模塊,肯定 X 可能的安裝目錄。 b. 依次在每一個目錄中,將 X 當成文件名或目錄名加載。 (4)拋出 "not found"
詳細資料能夠參考:
《require() 源碼解讀》
Promise 對象是異步編程的一種解決方案,最先由社區提出。Promises/A+ 規範是 JavaScript Promise 的標準,規定了一個 Promise 所必須具備的特性。 Promise 是一個構造函數,接收一個函數做爲參數,返回一個 Promise 實例。一個 Promise 實例有三種狀態,分別是 pending、resolved 和 rejected,分別表明了進行中、已成功和已失敗。實例的狀態只能由 pending 轉變 resolved 或者 rejected 狀態,而且狀態一經改變,就凝固了,沒法再被改變了。狀態的改變是經過 resolve() 和 reject() 函數來實現的,咱們 能夠在異步操做結束後調用這兩個函數改變 Promise 實例的狀態,它的原型上定義了一個 then 方法,使用這個 then 方法能夠爲兩個狀態的改變註冊回調函數。這個回調函數屬於微任務,會在本輪事件循環的末尾執行。
詳細資料能夠參考:
《Promises/A+ 規範》
《Promise》
const PENDING = "pending"; const RESOLVED = "resolved"; const REJECTED = "rejected"; function MyPromise(fn) { // 保存初始化狀態 var self = this; // 初始化狀態 this.state = PENDING; // 用於保存 resolve 或者 rejected 傳入的值 this.value = null; // 用於保存 resolve 的回調函數 this.resolvedCallbacks = []; // 用於保存 reject 的回調函數 this.rejectedCallbacks = []; // 狀態轉變爲 resolved 方法 function resolve(value) { // 判斷傳入元素是否爲 Promise 值,若是是,則狀態改變必須等待前一個狀態改變後再進行改變 if (value instanceof MyPromise) { return value.then(resolve, reject); } // 保證代碼的執行順序爲本輪事件循環的末尾 setTimeout(() => { // 只有狀態爲 pending 時才能轉變, if (self.state === PENDING) { // 修改狀態 self.state = RESOLVED; // 設置傳入的值 self.value = value; // 執行回調函數 self.resolvedCallbacks.forEach(callback => { callback(value); }); } }, 0); } // 狀態轉變爲 rejected 方法 function reject(value) { // 保證代碼的執行順序爲本輪事件循環的末尾 setTimeout(() => { // 只有狀態爲 pending 時才能轉變 if (self.state === PENDING) { // 修改狀態 self.state = REJECTED; // 設置傳入的值 self.value = value; // 執行回調函數 self.rejectedCallbacks.forEach(callback => { callback(value); }); } }, 0); } // 將兩個方法傳入函數執行 try { fn(resolve, reject); } catch (e) { // 遇到錯誤時,捕獲錯誤,執行 reject 函數 reject(e); } } MyPromise.prototype.then = function(onResolved, onRejected) { // 首先判斷兩個參數是否爲函數類型,由於這兩個參數是可選參數 onResolved = typeof onResolved === "function" ? onResolved : function(value) { return value; }; onRejected = typeof onRejected === "function" ? onRejected : function(error) { throw error; }; // 若是是等待狀態,則將函數加入對應列表中 if (this.state === PENDING) { this.resolvedCallbacks.push(onResolved); this.rejectedCallbacks.push(onRejected); } // 若是狀態已經凝固,則直接執行對應狀態的函數 if (this.state === RESOLVED) { onResolved(this.value); } if (this.state === REJECTED) { onRejected(this.value); } };
用 JS 設置 DOM 的字體爲某一個值,而後再取出來,若是值設置成功,就說明支持。
error 統計使用瀏覽器的 window.error 事件。
單例模式保證了全局只有一個實例來被訪問。好比說經常使用的如彈框組件的實現和全局狀態的實現。
策略模式主要是用來將方法的實現和方法的調用分離開,外部經過不一樣的參數能夠調用不一樣的策略。我主要在 MVP 模式解耦的時候 用來將視圖層的方法定義和方法調用分離。
代理模式是爲一個對象提供一個代用品或佔位符,以便控制對它的訪問。好比說常見的事件代理。
中介者模式指的是,多個對象經過一箇中介者進行交流,而不是直接進行交流,這樣可以將通訊的各個對象解耦。
適配器用來解決兩個接口不兼容的狀況,不須要改變已有的接口,經過包裝一層的方式實現兩個接口的正常協做。假如咱們須要一種 新的接口返回方式,可是老的接口因爲在太多地方已經使用了,不能隨意更改,這個時候就能夠使用適配器模式。好比咱們須要一種 自定義的時間返回格式,可是咱們又不能對 js 時間格式化的接口進行修改,這個時候就能夠使用適配器模式。
更多關於設計模式的資料能夠參考:
《前端面試之道》
《JavaScript 設計模式》
《JavaScript 中常見設計模式整理》
發佈訂閱模式其實屬於廣義上的觀察者模式 在觀察者模式中,觀察者須要直接訂閱目標事件。在目標發出內容改變的事件後,直接接收事件並做出響應。 而在發佈訂閱模式中,發佈者和訂閱者之間多了一個調度中心。調度中心一方面從發佈者接收事件,另外一方面向訂閱者發佈事件,訂閱者須要在調度中心中訂閱事件。經過調度中心實現了發佈者和訂閱者關係的解耦。使用發佈訂閱者模式更利於咱們代碼的可維護性。
詳細資料能夠參考:
《觀察者模式和發佈訂閱模式有什麼不一樣?》
Vue 的生命週期指的是組件從建立到銷燬的一系列的過程,被稱爲 Vue 的生命週期。經過提供的 Vue 在生命週期各個階段的鉤子函數,咱們能夠很好的在 Vue 的各個生命階段實現一些操做。
Vue 一共有8個生命階段,分別是建立前、建立後、加載前、加載後、更新前、更新後、銷燬前和銷燬後,每一個階段對應了一個生命週期的鉤子函數。 (1)beforeCreate 鉤子函數,在實例初始化以後,在數據監聽和事件配置以前觸發。所以在這個事件中咱們是獲取不到 data 數據的。 (2)created 鉤子函數,在實例建立完成後觸發,此時能夠訪問 data、methods 等屬性。但這個時候組件尚未被掛載到頁面中去,因此這個時候訪問不到 $el 屬性。通常咱們能夠在這個函數中進行一些頁面初始化的工做,好比經過 ajax 請求數據來對頁面進行初始化。 (3)beforeMount 鉤子函數,在組件被掛載到頁面以前觸發。在 beforeMount 以前,會找到對應的 template,並編譯成 render 函數。 (4)mounted 鉤子函數,在組件掛載到頁面以後觸發。此時能夠經過 DOM API 獲取到頁面中的 DOM 元素。 (5)beforeUpdate 鉤子函數,在響應式數據更新時觸發,發生在虛擬 DOM 從新渲染和打補丁以前,這個時候咱們能夠對可能會被移除的元素作一些操做,好比移除事件監聽器。 (6)updated 鉤子函數,虛擬 DOM 從新渲染和打補丁以後調用。 (7)beforeDestroy 鉤子函數,在實例銷燬以前調用。通常在這一步咱們能夠銷燬定時器、解綁全局事件等。 (8)destroyed 鉤子函數,在實例銷燬以後調用,調用後,Vue 實例中的全部東西都會解除綁定,全部的事件監聽器會被移除,全部的子實例也會被銷燬。 當咱們使用 keep-alive 的時候,還有兩個鉤子函數,分別是 activated 和 deactivated 。用 keep-alive 包裹的組件在切換時不會進行銷燬,而是緩存到內存中並執行 deactivated 鉤子函數,命中緩存渲染後會執行 actived 鉤子函數。
詳細資料能夠參考:
《vue 生命週期深刻》
《Vue 實例》
(1)父子組件間通訊 第一種方法是子組件經過 props 屬性來接受父組件的數據,而後父組件在子組件上註冊監聽事件,子組件經過 emit 觸發事 件來向父組件發送數據。 第二種是經過 ref 屬性給子組件設置一個名字。父組件經過 $refs 組件名來得到子組件,子組件經過 $parent 得到父組 件,這樣也能夠實現通訊。 第三種是使用 provider/inject,在父組件中經過 provider 提供變量,在子組件中經過 inject 來將變量注入到組件 中。不論子組件有多深,只要調用了 inject 那麼就能夠注入 provider 中的數據。 (2)兄弟組件間通訊 第一種是使用 eventBus 的方法,它的本質是經過建立一個空的 Vue 實例來做爲消息傳遞的對象,通訊的組件引入這個實 例,通訊的組件經過在這個實例上監聽和觸發事件,來實現消息的傳遞。 第二種是經過 $parent.$refs 來獲取到兄弟組件,也能夠進行通訊。 (3)任意組件之間 使用 eventBus ,其實就是建立一個事件中心,至關於中轉站,能夠用它來傳遞事件和接收事件。 若是業務邏輯複雜,不少組件之間須要同時處理一些公共的數據,這個時候採用上面這一些方法可能不利於項目的維護。這個時候 能夠使用 vuex ,vuex 的思想就是將這一些公共的數據抽離出來,將它做爲一個全局的變量來管理,而後其餘組件就能夠對這個 公共數據進行讀寫操做,這樣達到了解耦的目的。
詳細資料能夠參考:
《VUE 組件之間數據傳遞全集》
(1)computed 是計算一個新的屬性,並將該屬性掛載到 Vue 實例上,而 watch 是監聽已經存在且已掛載到 Vue 實例上的數據,因此用 watch 一樣能夠監聽 computed 計算屬性的變化。 (2)computed 本質是一個惰性求值的觀察者,具備緩存性,只有當依賴變化後,第一次訪問 computed 屬性,纔會計算新的值。而 watch 則是當數據發生變化便會調用執行函數。 (3)從使用場景上說,computed 適用一個數據被多個數據影響,而 watch 適用一個數據影響多個數據。
詳細資料能夠參考:
《作面試的不倒翁:淺談 Vue 中 computed 實現原理》
《深刻理解 Vue 的 watch 實現原理及其實現方式》
(1)全局的鉤子函數 beforeEach 和 afterEach beforeEach 有三個參數,to 表明要進入的路由對象,from 表明離開的路由對象。next 是一個必需要執行的函數,若是不傳參數,那就執行下一個鉤子函數,若是傳入 false,則終止跳轉,若是傳入一個路徑,則導航到對應的路由,若是傳入 error ,則導航終止,error 傳入錯誤的監聽函數。 (2)單個路由獨享的鉤子函數 beforeEnter,它是在路由配置上直接進行定義的。 (3)組件內的導航鉤子主要有這三種:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave。它們是直接在路由組 件內部直接進行定義的。
詳細資料能夠參考:
《導航守衛》
$route 是「路由信息對象」,包括 path,params,hash,query,fullPath,matched,name 等路由信息參數。而 $router 是「路由實例」對象包括了路由的跳轉方法,鉤子函數等。
.prevent: 提交事件再也不重載頁面;.stop: 阻止單擊事件冒泡;.self: 當事件發生在該元素自己而不是子元素的時候會觸發;
vue 中 key 值的做用能夠分爲兩種狀況來考慮。 第一種狀況是 v-if 中使用 key。因爲 Vue 會盡量高效地渲染元素,一般會複用已有元素而不是從頭開始渲染。所以當咱們使用 v-if 來實現元素切換的時候,若是切換先後含有相同類型的元素,那麼這個元素就會被複用。若是是相同的 input 元素,那麼切換先後用戶的輸入不會被清除掉,這樣是不符合需求的。所以咱們能夠經過使用 key 來惟一的標識一個元素,這個狀況下,使用 key 的元素不會被複用。這個時候 key 的做用是用來標識一個獨立的元素。 第二種狀況是 v-for 中使用 key。用 v-for 更新已渲染過的元素列表時,它默認使用「就地複用」的策略。若是數據項的順序發生了改變,Vue 不會移動 DOM 元素來匹配數據項的順序,而是簡單複用此處的每一個元素。所以經過爲每一個列表項提供一個 key 值,來以便 Vue 跟蹤元素的身份,從而高效的實現複用。這個時候 key 的做用是爲了高效的更新渲染虛擬 DOM。
詳細資料能夠參考:
《Vue 面試中,常常會被問到的面試題 Vue 知識點整理》
《Vue2.0 v-for 中 :key 到底有什麼用?》
《vue 中 key 的做用》
computed 是計算屬性,依賴其餘屬性計算值,而且 computed 的值有緩存,只有當計算值變化纔會返回內容。 watch 監聽到值的變化就會執行回調,在回調中能夠進行一些邏輯操做。
若是你須要在組件切換的時候,保存一些組件的狀態防止屢次渲染,就能夠使用 keep-alive 組件包裹須要保存的組件。
mixin 用於全局混入,會影響到每一個組件實例。 mixins 應該是咱們最常使用的擴展組件的方式了。若是多個組件中有相同的業務邏輯,就能夠將這些邏輯剝離出來,經過 mixins 混入代碼,好比上拉下拉加載數據這種邏輯等等。另外須要注意的是 mixins 混入的鉤子函數會先於組件內的鉤子函數執行,而且在遇到同名選項的時候也會有選擇性的進行合併
(1)application/x-www-form-urlencoded 瀏覽器的原生 form 表單,若是不設置 enctype 屬性,那麼最終就會以 application/x-www-form-urlencoded 方式提交數據。該種方式提交的數據放在 body 裏面,數據按照 key1=val1&key2=val2 的方式進行編碼,key 和 val 都進行了 URL 轉碼。 (2)multipart/form-data 該種方式也是一個常見的 POST 提交方式,一般表單上傳文件時使用該種方式。 (3)application/json 告訴服務器消息主體是序列化後的 JSON 字符串。 (4)text/xml 該種方式主要用來提交 XML 格式的數據。
詳細資料能夠參考:
《經常使用的幾種 Content-Type》
function getType(value) { // 判斷數據是 null 的狀況 if (value === null) { return value + ""; } // 判斷數據是引用類型的狀況 if (typeof value === "object") { let valueClass = Object.prototype.toString.call(value), type = valueClass.split(" ")[1].split(""); type.pop(); return type.join("").toLowerCase(); } else { // 判斷數據是基本數據類型的狀況和函數的狀況 return typeof value; } }
詳細資料能夠參考:
《JavaScript 專題之類型判斷(上)》
function checkNullObj(obj) { return Object.keys(obj).length === 0; }
詳細資料能夠參考:
《js 判斷一個 object 對象是否爲空》
// 使用閉包實現 for (var i = 0; i < 5; i++) { (function(i) { setTimeout(function() { console.log(i); }, i * 1000); })(i); } // 使用 let 塊級做用域 for (let i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }, i * 1000); }
function jsonp(url, params, callback) { // 判斷是否含有參數 let queryString = url.indexOf("?") === "-1" ? "?" : "&"; // 添加參數 for (var k in params) { if (params.hasOwnProperty(k)) { queryString += k + "=" + params[k] + "&"; } } // 處理回調函數名 let random = Math.random() .toString() .replace(".", ""), callbackName = "myJsonp" + random; // 添加回調函數 queryString += "callback=" + callbackName; // 構建請求 let scriptNode = document.createElement("script"); scriptNode.src = url + queryString; window[callbackName] = function() { // 調用回調函數 callback(...arguments); // 刪除這個引入的腳本 document.getElementsByTagName("head")[0].removeChild(scriptNode); }; // 發起請求 document.getElementsByTagName("head")[0].appendChild(scriptNode); }
詳細資料能夠參考:
《原生 jsonp 具體實現》
《jsonp 的原理與實現》
var events = (function() { var topics = {}; return { // 註冊監聽函數 subscribe: function(topic, handler) { if (!topics.hasOwnProperty(topic)) { topics[topic] = []; } topics[topic].push(handler); }, // 發佈事件,觸發觀察者回調事件 publish: function(topic, info) { if (topics.hasOwnProperty(topic)) { topics[topic].forEach(function(handler) { handler(info); }); } }, // 移除主題的一個觀察者的回調事件 remove: function(topic, handler) { if (!topics.hasOwnProperty(topic)) return; var handlerIndex = -1; topics[topic].forEach(function(item, index) { if (item === handler) { handlerIndex = index; } }); if (handlerIndex >= 0) { topics[topic].splice(handlerIndex, 1); } }, // 移除主題的全部觀察者的回調事件 removeAll: function(topic) { if (topics.hasOwnProperty(topic)) { topics[topic] = []; } } }; })();
詳細資料能夠參考:
《JS 事件模型》
class EventEmitter { constructor() { this.events = {}; } on(event, callback) { let callbacks = this.events[event] || []; callbacks.push(callback); this.events[event] = callbacks; return this; } off(event, callback) { let callbacks = this.events[event]; this.events[event] = callbacks && callbacks.filter(fn => fn !== callback); return this; } emit(event, ...args) { let callbacks = this.events[event]; callbacks.forEach(fn => { fn(...args); }); return this; } once(event, callback) { let wrapFun = function(...args) { callback(...args); this.off(event, wrapFun); }; this.on(event, wrapFun); return this; } }
function Foo() { getName = function() { alert(1); }; return this; } Foo.getName = function() { alert(2); }; Foo.prototype.getName = function() { alert(3); }; var getName = function() { alert(4); }; function getName() { alert(5); } //請寫出如下輸出結果: Foo.getName(); // 2 getName(); // 4 Foo().getName(); // 1 getName(); // 1 new Foo.getName(); // 2 new Foo().getName(); // 3 new new Foo().getName(); // 3
詳細資料能夠參考:
《前端程序員常常忽視的一個 JavaScript 面試題》
《一道考察運算符優先級的 JavaScript 面試題》
《一道常被人輕視的前端 JS 面試題》
Performance API 用於精確度量、控制、加強瀏覽器的性能表現。這個 API 爲測量網站性能,提供之前沒有辦法作到的精度。 使用 getTime 來計算腳本耗時的缺點,首先,getTime方法(以及 Date 對象的其餘方法)都只能精確到毫秒級別(一秒的千分之一),想要獲得更小的時間差異就無能爲力了。其次,這種寫法只能獲取代碼運行過程當中的時間進度,沒法知道一些後臺事件的時間進度,好比瀏覽器用了多少時間從服務器加載網頁。 爲了解決這兩個不足之處,ECMAScript 5引入「高精度時間戳」這個 API,部署在 performance 對象上。它的精度能夠達到1毫秒 的千分之一(1秒的百萬分之一)。 navigationStart:當前瀏覽器窗口的前一個網頁關閉,發生 unload 事件時的 Unix 毫秒時間戳。若是沒有前一個網頁,則等於 fetchStart 屬性。 loadEventEnd:返回當前網頁 load 事件的回調函數運行結束時的 Unix 毫秒時間戳。若是該事件尚未發生,返回 0。
根據上面這些屬性,能夠計算出網頁加載各個階段的耗時。好比,網頁加載整個過程的耗時的計算方法以下:
var t = performance.timing; var pageLoadTime = t.loadEventEnd - t.navigationStart;
詳細資料能夠參考:
《Performance API》
(1)第一個字符必須是字母、下劃線(_)或美圓符號($) (2)餘下的字符能夠是下劃線、美圓符號或任何字母或數字字符 通常咱們推薦使用駝峯法來對變量名進行命名,由於這樣能夠與 ECMAScript 內置的函數和對象命名格式保持一致。
詳細資料能夠參考:
《ECMAScript 變量》
在 ECMAScript 規範中,語句結尾的分號並非必需的。可是咱們通常最好不要省略分號,由於加上分號一方面有 利於咱們代碼的可維護性,另外一方面也能夠避免咱們在對代碼進行壓縮時出現錯誤。
Object.assign() 方法用於將全部可枚舉屬性的值從一個或多個源對象複製到目標對象。它將返回目標對象。
Math.ceil() === 向上取整,函數返回一個大於或等於給定數字的最小整數。 Math.floor() === 向下取整,函數返回一個小於或等於給定數字的最大整數。
for (var i = 0, j = 0; i < 5, j < 9; i++, j++) { console.log(i, j); } // 當判斷語句含有多個語句時,以最後一個判斷語句的值爲準,所以上面的代碼會執行 10 次。 // 當判斷語句爲空時,循環會一直進行。
咱們須要思考的問題:該處理是否必須同步完成?數據是否必須按順序完成? 解決辦法: (1)將數據分頁,利用分頁的原理,每次服務器端只返回必定數目的數據,瀏覽器每次只對一部分進行加載。 (2)使用懶加載的方法,每次加載一部分數據,其他數據當須要使用時再去加載。 (3)使用數組分塊技術,基本思路是爲要處理的項目建立一個隊列,而後設置定時器每過一段時間取出一部分數據,而後再使用定時器取出下一個要處理的項目進行處理,接着再設置另外一個定時器。
在前端實現中咱們通常經過 setTimeout 和 setInterval 方法來實現一個倒計時效果。可是使用這些方法會存在時間誤差的問題,這是因爲 js 的程序執行機制形成的,setTimeout 和 setInterval 的做用是隔一段時間將回調事件加入到事件隊列中,所以事件並非當即執行的,它會等到當前執行棧爲空的時候再取出事件執行,所以事件等待執行的時間就是形成偏差的緣由。 通常解決倒計時中的偏差的有這樣兩種辦法: (1)第一種是經過前端定時向服務器發送請求獲取最新的時間差,以此來校準倒計時時間。 (2)第二種方法是前端根據誤差時間來自動調整間隔時間的方式來實現的。這一種方式首先是以 setTimeout 遞歸的方式來實現倒計時,而後經過一個變量來記錄已經倒計時的秒數。每一次函數調用的時候,首先將變量加一,而後根據這個變量和每次的間隔時間,咱們就能夠計算出此時無誤差時應該顯示的時間。而後將當前的真實時間與這個時間相減,這樣咱們就能夠獲得時間的誤差大小,所以咱們在設置下一個定時器的間隔大小的時候,咱們就從間隔時間中減去這個誤差大小,以此來實現因爲程序執行所形成的時間偏差的糾正。
詳細資料能夠參考:
《JavaScript 前端倒計時糾偏實現》
詳細資料能夠參考:
《進程間 8 種通訊方式詳解》
《進程與線程的一個簡單解釋》
function findMostWord(article) { // 合法性判斷 if (!article) return; // 參數處理 article = article.trim().toLowerCase(); let wordList = article.match(/[a-z]+/g), visited = [], maxNum = 0, maxWord = ""; article = " " + wordList.join(" ") + " "; // 遍歷判斷單詞出現次數 wordList.forEach(function(item) { if (visited.indexOf(item) < 0) { let word = new RegExp(" " + item + " ", "g"), num = article.match(word).length; if (num > maxNum) { maxNum = num; maxWord = item; } } }); return maxWord + " " + maxNum; }
筆者再次牆裂推薦收藏這個倉庫,收錄於CavsZhouyou - 🐜 前端面試複習筆記,這個倉庫是原做者校招時的前端複習筆記,主要總結一些比較重要的知識點和前端面試問題,但願對你們有所幫助。
最後若是文章和筆記能帶您一絲幫助或者啓發,請不要吝嗇你的贊和收藏,你的確定是我前進的最大動力 😁