前端(vue)入門到精通課程:進入學習
API 檔案、設計、偵錯、自動化測試一體化共同作業工具:
【相關推薦:、】
1.1 Js有哪些資料型別
JavaScript共有八種資料型別
基本資料型別: Undefined、Null、Boolean、Number、String、Symbol、BigInt。
複雜資料型別:Object
其中 Symbol 和 BigInt 是ES6 中新增的資料型別:
Symbol 代表建立後獨一無二且不可變的資料型別,它主要是為了解決可能出現的全域性變數衝突的問題。
BigInt 是一種數位型別的資料,它可以表示任意精度格式的整數,使用 BigInt 可以安全地儲存和操作大整數,即使這個數已經超出了 Number 能夠表示的安全整數範圍。
1.2 說說你對堆區和棧區的理解
在作業系統中,記憶體被分為棧區和堆區
棧區記憶體由編譯器自動分配釋放,存放函數的引數值,區域性變數的值等。其操作方式類似於資料結構中的棧。
堆區記憶體一般由開發著分配釋放,若開發者不釋放,程式結束時可能由垃圾回收機制回收。
在資料結構中:
在資料結構中,棧中資料的存取方式為先進後出。
堆是一個優先佇列,是按優先順序來進行排序的,優先順序可以按照大小來規定。
資料的儲存方式
原始資料型別直接儲存在棧(stack)中的簡單資料段,佔據空間小、大小固定,屬於被頻繁使用資料,所以放入棧中儲存;
參照資料型別儲存在堆(heap)中的物件,佔據空間大、大小不固定。如果儲存在棧中,將會影響程式執行的效能;參照資料型別在棧中儲存了指標,該指標指向堆中該實體的起始地址。當直譯器尋找參照值時,會首先檢索其在棧中的地址,取得地址後從堆中獲得實體。
1.3 資料型別檢測的方式有哪些
然後判斷資料型別的方法一般可以通過:typeof、instanceof、constructor、toString四種常用方法
1.4 判斷陣列的方式有哪些
通過Object.prototype.toString.call()做判斷
通過原型鏈做判斷
通過ES6的Array.isArray()做判斷
通過instanceof做判斷
通過Array.prototype.isPrototypeOf
1.5 null和undefined區別
首先 Undefined 和 Null 都是基本資料型別,這兩個基本資料型別分別都只有一個值,就是 undefined 和 null。
undefined 代表的含義是未定義,null 代表的含義是空物件。一般變數宣告了但還沒有定義的時候會返回 undefined,null主要用於賦值給一些可能會返回物件的變數,作為初始化。
undefined 在 JavaScript 中不是一個保留字,這意味著可以使用 undefined 來作為一個變數名,但是這樣的做法是非常危險的,它會影響對 undefined 值的判斷。我們可以通過一些方法獲得安全的 undefined 值,比如說 void 0。
當對這兩種型別使用 typeof 進行判斷時,Null 型別化會返回 「object」,這是一個歷史遺留的問題。當使用雙等號對兩種型別的值進行比較時會返回 true,使用三個等號時會返回 false。
1.6 typeof null 的結果是什麼,為什麼?
typeof null 的結果是Object。
在 JavaScript 第一個版本中,所有值都儲存在 32 位的單元中,每個單元包含一個小的 型別標籤(1-3 bits) 以及當前要儲存值的真實資料。型別標籤儲存在每個單元的低位中,共有五種資料型別:
000: object - 當前儲存的資料指向一個物件。 1: int - 當前儲存的資料是一個 31 位的有符號整數。 010: double - 當前儲存的資料指向一個雙精度的浮點數。 100: string - 當前儲存的資料指向一個字串。 110: boolean - 當前儲存的資料是布林值。
如果最低位是 1,則型別標籤標誌位的長度只有一位;如果最低位是 0,則型別標籤標誌位的長度佔三位,為儲存其他四種資料型別提供了額外兩個 bit 的長度。
有兩種特殊資料型別:
undefined的值是 (-2)30(一個超出整數範圍的數位);
null 的值是機器碼 NULL 指標(null 指標的值全是 0)
那也就是說null的型別標籤也是000,和Object的型別標籤一樣,所以會被判定為Object。
1.7 為什麼0.1+0.2 ! == 0.3,如何讓其相等 (精度丟失)
計算機是通過二進位制的方式儲存資料的,所以計算機計算0.1+0.2的時候,實際上是計算的兩個數的二進位制的和。
在 Js中只有一種數位型別:Number,它的實現遵循IEEE 754標準,使用64位元固定長度來表示,也就是標準的double雙精度浮點數。在二進位制科學表示法中,雙精度浮點數的小數部分最多隻能保留52位,再加上前面的1,其實就是保留53位有效數位,剩餘的需要捨去,遵從「0舍1入」的原則。
根據這個原則,0.1和0.2的二進位制數相加,再轉化為十進位制數就是:0.30000000000000004。所以不相等
解決方法就是設定一個誤差範圍,通常稱為「機器精度」。對JavaScript來說,這個值通常為2-52,在ES6中,提供了Number.EPSILON屬性,而它的值就是2-52,只要判斷0.1+0.2-0.3是否小於Number.EPSILON,如果小於,就可以判斷為0.1+0.2 ===0.3
function numberepsilon(arg1,arg2){ return Math.abs(arg1 - arg2) < Number.EPSILON; } console.log(numberepsilon(0.1 + 0.2, 0.3)); // true
1.8 如何獲取安全的 undefined 值?
因為 undefined 是一個識別符號,所以可以被當作變數來使用和賦值,但是這樣會影響 undefined 的正常判斷。表示式 void ___ 沒有返回值,因此返回結果是 undefined。void 並不改變表示式的結果,只是讓表示式不返回值。因此可以用 void 0 來獲得 undefined。
1.9 typeof NaN 的結果是什麼?
NaN 指「不是一個數位」(not a number),NaN 是一個「警戒值」(sentinel value,有特殊用途的常規值),用於指出數位型別中的錯誤情況,即「執行數學運算沒有成功,這是失敗後返回的結果」。
typeof NaN; // "number"
NaN 是一個特殊值,它和自身不相等,是唯一一個非自反的值。所謂的非自反就是說,NaN 與誰都不相等,包括它本身,但在 NaN != NaN 下會返回true
1.10 isNaN 和 Number.isNaN 函數的區別?
函數 isNaN 接收引數後,會嘗試將這個引數轉換為數值,任何不能被轉換為數值的的值都會返回 true,因此非數位值傳入也會返回 true ,會影響 NaN 的判斷。
函數 Number.isNaN 會首先判斷傳入引數是否為數位,如果是數位再繼續判斷是否為 NaN ,不會進行資料型別的轉換,這種方法對於 NaN 的判斷更為準確。
1.11 == 操作符的強制型別轉換規則是什麼?
對於 == 來說,如果對比雙方的型別不一樣,就會進行型別轉換。假如對比 x 和 y 是否相同,就會進行如下判斷流程:
首先會判斷兩者型別是否相同, 相同的話就比較兩者的大小;
型別不相同的話,就會進行型別轉換;
會先判斷是否在對比 null 和 undefined,是的話就會返回 true
判斷兩者型別是否為 string 和 number,是的話就會將字串轉換為 number
1 == '1' ↓ 1 == 1
判斷其中一方是否為 boolean,是的話就會把 boolean 轉為 number 再進行判斷
'1' == true ↓ '1' == 1 ↓ 1 == 1
判斷其中一方是否為 object 且另一方為 string、number 或者 symbol,是的話就會把 object 轉為原始型別再進行判斷
'1' == { name: 'js' } ↓'1' == '[object Object]'
其流程圖如下:
1.12 其他值型別轉成字串的轉換規則?
Null 和 Undefined 型別 ,null 轉換為 "null",undefined 轉換為 "undefined",
Boolean 型別,true 轉換為 "true",false 轉換為 "false"。
Number 型別的值直接轉換,不過那些極小和極大的數位會使用指數形式。
Symbol 型別的值直接轉換,但是隻允許顯式強制型別轉換,使用隱式強制型別轉換會產生錯誤。
對普通物件來說,除非自行定義 toString() 方法,否則會呼叫 toString()(Object.prototype.toString())來返回內部屬性 [[Class]] 的值,如"[object Object]"。如果物件有自己的 toString() 方法,字串化時就會呼叫該方法並使用其返回值。
1.13. 其他值型別轉成數位的轉換規則?
Undefined 型別的值轉換為 NaN。
Null 型別的值轉換為 0。
Boolean 型別的值,true 轉換為 1,false 轉換為 0。
String 型別的值轉換如同使用 Number() 函數進行轉換,如果包含非數位值則轉換為 NaN,空字串為 0。
Symbol 型別的值不能轉換為數位,會報錯。
物件(包括陣列)會首先被轉換為相應的基本型別值,如果返回的是非數位的基本型別值,則再遵循以上規則將其強制轉換為數位。
為了將值轉換為相應的基本型別值, 隱式轉換會首先檢查該值是否有valueOf()方法。如果有並且返回基本型別值,就使用該值進行強制型別轉換。如果沒有就使用 toString() 的返回值(如果存在)來進行強制型別轉換。
如果 valueOf() 和 toString() 均不返回基本型別值,會產生 TypeError 錯誤。
1.14 其他值型別轉成布林型別的轉換規則?
以下這些是假值: undefined 、 null 、 false 、 +0、-0 和 NaN 、 ""
假值的布林強制型別轉換結果為 false。從邏輯上說,假值列表以外的都應該是真值。
1.15. || 和 && 操作符的返回值?
|| 和 && 首先會對第一個運算元執行條件判斷,如果其不是布林值就先強制轉換為布林型別,然後再執行條件判斷。
對於 || 來說,如果條件判斷結果為 true 就返回第一個運算元的值,如果為 false 就返回第二個運算元的值。
&& 則相反,如果條件判斷結果為 true 就返回第二個運算元的值,如果為 false 就返回第一個運算元的值。
|| 和 && 返回它們其中一個運算元的值,而非條件判斷的結果
1.16. Object.is() 與比較操作符 「===」、「==」 的區別?
使用雙等號(==)進行相等判斷時,如果兩邊的型別不一致,則會進行強制型別轉化後再進行比較。
使用三等號(===)進行相等判斷時,如果兩邊的型別不一致時,不會做強制型別準換,直接返回 false。
使用 Object.is 來進行相等判斷時,一般情況下和三等號的判斷相同,它處理了一些特殊的情況,比如 -0 和 +0 不再相等,兩個 NaN 是相等的。
1.17. 什麼是 JavaScript 中的包裝型別?
在 JavaScript 中,基本型別是沒有屬性和方法的,但是為了便於操作基本型別的值,在呼叫基本型別的屬性或方法時 JavaScript 會在後臺隱式地將基本型別的值轉換為物件。如:
const a = "abc"; a.length; // 3
在存取'abc'.length時,JavaScript 將'abc'在後臺轉換成String('abc'),然後再存取其length屬性。
1.18 Js中隱式轉換規則
在 if 語句、邏輯語句、數學運算邏輯、== 等情況下都可能出現隱式型別轉換。
坑: 判斷時, 儘量不要用 = = , 要用 = = = ( 兩個等號判斷, 如果型別不同, 預設會進行隱式型別轉換再比較)
1.19 說說你對this的理解
this是一個在執行時才進行繫結的參照,在不同的情況下它可能會被繫結不同的物件。
1.20 如何判斷 this 的指向
第一種是函數呼叫模式,當一個函數不是一個物件的屬性時,直接作為函數來呼叫時,this 指向全域性物件。
第二種是方法呼叫模式,如果一個函數作為一個物件的方法來呼叫時,this 指向這個物件。
第三種是構造器呼叫模式,如果一個函數用 new 呼叫時,函數執行前會新建立一個物件,this 指向這個新建立的物件。
第四種是 apply 、 call 和 bind 呼叫模式,這三個方法都可以顯示的指定呼叫函數的 this 指向。其中 apply 方法接收兩個引數:一個是 this 繫結的物件,一個是引數陣列。call 方法接收的引數,第一個是 this 繫結的物件,後面的其餘引數是傳入函數執行的引數。也就是說,在使用 call() 方法時,傳遞給函數的引數必須逐個列舉出來。bind 方法通過傳入一個物件,返回一個 this 繫結了傳入物件的新函數。這個函數的 this 指向除了使用 new 時會被改變,其他情況下都不會改變。
this繫結的優先順序
new繫結優先順序 > 顯示繫結優先順序 > 隱式繫結優先順序 > 預設繫結優先順序
1.21 Map和Object的區別
1.22 說說你對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 資料結構,以此來進行資料的存取。
1.222 String和JSON.stringify的區別
console.log(String("abc")); // abc console.log(JSON.stringify("abc")); // "abc" console.log(String({ key: "value" })); // [object Object] console.log(JSON.stringify({ key: "value" })); // {"key":"value"} console.log(String([1, 2, 3])); // 1,2,3 console.log(JSON.stringify([1, 2, 3])); // [1,2,3] const obj = { title: "devpoint", toString() { return "obj"; }, }; console.log(String(obj)); // obj console.log(JSON.stringify(obj)); // {"title":"devpoint"}
當需要將一個陣列和一個普通物件轉換為字串時,經常使用JSON.stringify。
如果需要物件的toString方法被重寫,則需要使用String()。
在其他情況下,使用String()將變數轉換為字串。
1.23 什麼是偽陣列(類陣列)
一個擁有 length 屬性和若干索引屬性的物件就可以被稱為類陣列物件,類陣列物件和陣列類似,但是不能呼叫陣列的方法。
常見的類陣列物件有 arguments 和 DOM 方法的返回結果,還有一個函數也可以被看作是類陣列物件,因為它含有 length 屬性值,代表可接收的引數個數。
1.24 類陣列轉換成陣列的方法有哪些
常見的類陣列轉換為陣列的方法有這樣幾種:
通過 call 呼叫陣列的 slice 方法來實現轉換
Array.prototype.slice.call(arrayLike);
通過 call 呼叫陣列的 splice 方法來實現轉換
Array.prototype.splice.call(arrayLike, 0);
通過 apply 呼叫陣列的 concat 方法來實現轉換
Array.prototype.concat.apply([], arrayLike);
通過 Array.from 方法來實現轉換
Array.from(arrayLike);
1.25 Unicode、UTF-8、UTF-16、UTF-32的區別?
Unicode 是編碼字元集(字元集),而UTF-8、UTF-16、UTF-32是字元集編碼(編碼規則);
UTF-16 使用變長碼元序列的編碼方式,相較於定長碼元序列的UTF-32演演算法更復雜,甚至比同樣是變長碼元序列的UTF-8也更為複雜,因為其引入了獨特的代理對這樣的代理機制;
UTF-8需要判斷每個位元組中的開頭標誌資訊,所以如果某個位元組在傳送過程中出錯了,就會導致後面的位元組也會解析出錯;而UTF-16不會判斷開頭標誌,即使錯也只會錯一個字元,所以容錯能力教強;
如果字元內容全部英文或英文與其他文字混合,但英文佔絕大部分,那麼用UTF-8就比UTF-16節省了很多空間;而如果字元內容全部是中文這樣類似的字元或者混合字元中中文佔絕大多數,那麼UTF-16就佔優勢了,可以節省很多空間;
1.26 常見的位運運算元有哪些?其計算規則是什麼?
現代計算機中資料都是以二進位制的形式儲存的,即0、1兩種狀態,計算機對二進位制資料進行的運算加減乘除等都是叫位運算,即將符號位共同參與運算的運算。
常見的位運算有以下幾種:
1.27 為什麼函數的 arguments 引數是類陣列而不是陣列?如何遍歷類陣列?
arguments是一個物件,它的屬性是從 0 開始依次遞增的數位,還有callee和length等屬性,與陣列相似;但是它卻沒有陣列常見的方法屬性,如forEach, reduce等,所以叫它們類陣列。
要遍歷類陣列,有三個方法:
(1)將陣列的方法應用到類陣列上,這時候就可以使用call和apply方法,如:
function foo(){ Array.prototype.forEach.call(arguments, a => console.log(a)) }
(2)使用Array.from方法將類陣列轉化成陣列:
function foo(){ const arrArgs = Array.from(arguments) arrArgs.forEach(a => console.log(a)) }
(3)使用展開運運算元將類陣列轉化成陣列
function foo(){ const arrArgs = [...arguments] arrArgs.forEach(a => console.log(a)) }
1.28 escape、encodeURI、encodeURIComponent 的區別
encodeURI 是對整個 URI 進行跳脫,將 URI 中的非法字元轉換為合法字元,所以對於一些在 URI 中有特殊意義的字元不會進行跳脫。
encodeURIComponent 是對 URI 的組成部分進行跳脫,所以一些特殊字元也會得到跳脫。
escape 和 encodeURI 的作用相同,不過它們對於 unicode 編碼為 0xff 之外字元的時候會有區別,escape 是直接在字元的 unicode 編碼前加上 %u,而 encodeURI 首先會將字元轉換為 UTF-8 的格式,再在每個位元組前加上 %。
1.29 什麼是尾呼叫,使用尾呼叫有什麼好處?
尾呼叫指的是函數的最後一步呼叫另一個函數。程式碼執行是基於執行棧的,所以當在一個函數裡呼叫另一個函數時,會保留當前的執行上下文,然後再新建另外一個執行上下文加入棧中。使用尾呼叫的話,因為已經是函數的最後一步,所以這時可以不必再保留當前的執行上下文,從而節省了記憶體,這就是尾呼叫優化。
但是 ES6 的尾呼叫優化只在嚴格模式下開啟,正常模式是無效的。
1.30 use strict是什麼? 它有什麼用?
use strict 是一種 ECMAscript5 新增的(嚴格模式)執行模式,這種模式使得 Javascript 在更嚴格的條件下執行。設立嚴格模式的目的如下:
消除 Javascript 語法的不合理、不嚴謹之處,減少怪異行為;
消除程式碼執行的不安全之處,保證程式碼執行的安全;
提高編譯器效率,增加執行速度;
為未來新版本的 Javascript 做好鋪墊。
區別:
禁止使用 with 語句。
禁止 this 關鍵字指向全域性物件。
物件不能有重名的屬性。
1.31 如何判斷一個物件是否屬於某個類?
第一種方式,使用 instanceof 運運算元來判斷建構函式的 prototype 屬性是否出現在物件的原型鏈中的任何位置。
第二種方式,通過物件的 constructor 屬性來判斷,物件的 constructor 屬性指向該物件的建構函式,但是這種方式不是很安全,因為 constructor 屬性可以被改寫。
第三種方式,如果需要判斷的是某個內建的參照型別的話,可以使用 Object.prototype.toString() 方法來列印物件的[[Class]] 屬性來進行判斷。
1.32 強型別語言和弱型別語言的區別
強型別語言:強型別語言也稱為強型別定義語言,是一種總是強制型別定義的語言,要求變數的使用要嚴格符合定義,所有變數都必須先定義後使用。Java和C++等語言都是強制型別定義的,也就是說,一旦一個變數被指定了某個資料型別,如果不經過強制轉換,那麼它就永遠是這個資料型別了。例如你有一個整數,如果不顯式地進行轉換,你不能將其視為一個字串。
弱型別語言:弱型別語言也稱為弱型別定義語言,與強型別定義相反。JavaScript語言就屬於弱型別語言。簡單理解就是一種變數型別可以被忽略的語言。比如JavaScript是弱型別定義的,在JavaScript中就可以將字串'12'和整數3進行連線得到字串'123',在相加的時候會進行強制型別轉換。
兩者對比:強型別語言在速度上可能略遜色於弱型別語言,但是強型別語言帶來的嚴謹性可以有效地幫助避免許多錯誤。
1.33 解釋性語言和編譯型語言的區別
(1)直譯語言 使用專門的直譯器對源程式逐行解釋成特定平臺的機器碼並立即執行。是程式碼在執行時才被直譯器一行行動態翻譯和執行,而不是在執行之前就完成翻譯。直譯語言不需要事先編譯,其直接將原始碼解釋成機器碼並立即執行,所以只要某一平臺提供了相應的直譯器即可執行該程式。其特點總結如下
直譯語言每次執行都需要將原始碼解釋稱機器碼並執行,效率較低;
只要平臺提供相應的直譯器,就可以執行原始碼,所以可以方便源程式移植;
JavaScript、Python等屬於直譯語言。
(2)編譯型語言 使用專門的編譯器,針對特定的平臺,將高階語言原始碼一次性的編譯成可被該平臺硬體執行的機器碼,幷包裝成該平臺所能識別的可執行性程式的格式。在編譯型語言寫的程式執行之前,需要一個專門的編譯過程,把原始碼編譯成機器語言的檔案,如exe格式的檔案,以後要再執行時,直接使用編譯結果即可,如直接執行exe檔案。因為只需編譯一次,以後執行時不需要編譯,所以編譯型語言執行效率高。其特點總結如下:
一次性的編譯成平臺相關的機器語言檔案,執行時脫離開發環境,執行效率高;
與特定平臺相關,一般無法移植到其他平臺;
C、C++等屬於編譯型語言。
兩者主要區別在於: 前者源程式編譯後即可在該平臺執行,後者是在執行期間才編譯。所以前者執行速度快,後者跨平臺性好。
1.34 for...in和for...of的區別
for…of 是ES6新增的遍歷方式,允許遍歷一個含有iterator介面的資料結構(陣列、物件等)並且返回各項的值,和ES3中的for…in的區別如下
for…of 遍歷獲取的是物件的鍵值,for…in 獲取的是物件的鍵名;
for… in 會遍歷物件的整個原型鏈,效能非常差不推薦使用,而 for … of 只遍歷當前物件不會遍歷原型鏈;
對於陣列的遍歷,for…in 會返回陣列中所有可列舉的屬性(包括原型鏈上可列舉的屬性),for…of 只返回陣列的下標對應的屬性值;
總結: for...in 迴圈主要是為了遍歷物件而生,不適用於遍歷陣列;for...of 迴圈可以用來遍歷陣列、類陣列物件,字串、Set、Map 以及 Generator 物件。
1.35 ajax、axios、fetch的區別
(1)AJAX Ajax 即「AsynchronousJavascriptAndXML」(非同步 JavaScript 和 XML),是指一種建立互動式網頁應用的網頁開發技術。它是一種在無需重新載入整個網頁的情況下,能夠更新部分網頁的技術。通過在後臺與伺服器進行少量資料交換,Ajax 可以使網頁實現非同步更新。這意味著可以在不重新載入整個網頁的情況下,對網頁的某部分進行更新。傳統的網頁(不使用 Ajax)如果需要更新內容,必須過載整個網頁頁面。其缺點如下:
本身是針對MVC程式設計,不符合前端MVVM的浪潮
基於原生XHR開發,XHR本身的架構不清晰
不符合關注分離(Separation of Concerns)的原則
設定和呼叫方式非常混亂,而且基於事件的非同步模型不友好。
(2)Fetch fetch號稱是AJAX的替代品,是在ES6出現的,使用了ES6中的promise物件。Fetch是基於promise設計的。Fetch的程式碼結構比起ajax簡單多。fetch不是ajax的進一步封裝,而是原生js,沒有使用XMLHttpRequest物件。
fetch的優點:
語法簡潔,更加語意化
基於標準 Promise 實現,支援 async/await
更加底層,提供的API豐富(request, response)
脫離了XHR,是ES規範裡新的實現方式
fetch的缺點:
fetch只對網路請求報錯,對400,500都當做成功的請求,伺服器返回 400,500 錯誤碼時並不會 reject,只有網路錯誤這些導致請求不能完成時,fetch 才會被 reject。
fetch預設不會帶cookie,需要新增設定項: fetch(url, {credentials: 'include'})
fetch不支援abort,不支援超時控制,使用setTimeout及Promise.reject的實現的超時控制並不能阻止請求過程繼續在後臺執行,造成了流量的浪費
fetch沒有辦法原生監測請求的進度,而XHR可以
(3)Axios Axios 是一種基於Promise封裝的HTTP使用者端,其特點如下:
瀏覽器端發起XMLHttpRequests請求
node端發起http請求
支援Promise API
監聽請求和返回
對請求和返回進行轉化
取消請求
自動轉換json資料
使用者端支援抵禦XSRF攻擊
1.36 陣列的遍歷方法有哪些
1.37 forEach和map方法有什麼區別
這方法都是用來遍歷陣列的,兩者區別如下:
forEach()方法會針對每一個元素執行提供的函數,對資料的操作會改變原陣列,該方法沒有返回值;
map()方法不會改變原陣列的值,返回一個新陣列,新陣列中的值為原陣列呼叫函數處理之後的值;
1.38 說說你對淺拷貝和深拷貝的理解
淺拷貝
淺拷貝,指的是建立新的資料,這個資料有著原始資料屬性值的一份精確拷貝
如果屬性是基本型別,拷貝的就是基本型別的值。如果屬性是參照型別,拷貝的就是記憶體地址
即淺拷貝是拷貝一層,深層次的參照型別則共用記憶體地址
常見的淺拷貝:
Object.assign
Object.create
slice
concat()
展開運運算元
深拷貝
深拷貝開闢一個新的棧,兩個物件屬完成相同,但是對應兩個不同的地址,修改一個物件的屬性,不會改變另一個物件的屬性
常見的深拷貝方式有:
_.cloneDeep()
jQuery.extend()
JSON.stringify()
手寫回圈遞迴
1.39 JSON.stringify深拷貝的缺點
如果obj裡面有時間物件,則JSON.stringify後再JSON.parse的結果,時間將只是字串的形式,而不是物件的形式
如果obj裡面有RegExp,則列印出來是空物件
如果物件中有函數或者undefined,則會直接被丟掉
如果json裡有物件是由建構函式生成的,則會丟掉物件的constructon
1.40 知道lodash嗎?它有哪些常見的API ?
Lodash是一個一致性、模組化、高效能的 JavaScript 實用工具庫。
_.cloneDeep 深度拷貝
_.reject 根據條件去除某個元素。
_.drop(array, [n=1] ) 作用:將 array 中的前 n 個元素去掉,然後返回剩餘的部分.
1.41 LHS 和 RHS 查詢
LHS (Left-hand Side) 和 RHS (Right-hand Side) ,是在程式碼執行階段 JS 引擎操作變數的兩種方式,二者區別就是對變數的查詢目的是 變數賦值 還是 查詢 。
LHS 可以理解為變數在賦值操作符(=)的左側,例如 a = 1,當前引擎對變數 a 查詢的目的是變數賦值。這種情況下,引擎不關心變數 a 原始值是什麼,只管將值 1 賦給 a 變數。
RHS 可以理解為變數在賦值操作符(=)的右側,例如:console.log(a),其中引擎對變數a的查詢目的就是 查詢,它需要找到變數 a 對應的實際值是什麼,然後才能將它列印出來。
1.42 includes 比 indexOf好在哪?
includes可以檢測NaN,indexOf不能檢測NaN,includes內部使用了Number.isNaN對NaN進行了匹配
1.43 AMD 和 CMD 的區別?
1.44 (a == 1 && a == 2 && a == 3) 有可能是 true 嗎?
方案一:重寫toString()或valueOf()
let a = { i: 1, toString: function () { return a.i++; } } console.log(a == 1 && a == 2 && a == 3); // true
方案二:陣列
陣列的toString介面預設呼叫陣列的join方法,重寫join方法。定義a為數位,每次比較時就會呼叫 toString()方法,我們把陣列的shift方法覆蓋toString即可:
let a = [1,2,3]; a.toString = a.shift; console.log(a == 1 && a == 2 && a == 3); // true
當然把toString改為valueOf也是一樣效果:
let a = [1,2,3]; a. valueOf = a.shift; console.log(a == 1 && a == 2 && a == 3); // true
方案三:使用Object.defineProperty()
Object.defineProperty()用於定義物件中的屬性,接收三個引數:object物件、物件中的屬性,屬性描述符。屬性描述符中get:存取該屬性時自動呼叫。
var _a = 1; Object.defineProperty(this,'a',{ get:function(){ return _a++ } }) console.log(a===1 && a===2 && a===3)//true
1.45 JS中的 MUL 函數
MUL表示數的簡單乘法。在這種技術中,將一個值作為引數傳遞給一個函數,而該函數將返回另一個函數,將第二個值傳遞給該函數,然後重複繼續。例如:xyz可以表示為
const mul = x => y => z => x * y * z console.log(mul(1)(2)(3)) // 6
1.46 深度遍歷廣度遍歷的區別?
對於演演算法來說 無非就是時間換空間 空間換時間
1、深度優先不需要記住所有的節點, 所以佔用空間小, 而廣度優先需要先記錄所有的節點佔用空間大
2、深度優先有回溯的操作(沒有路走了需要回頭)所以相對而言時間會長一點
3、深度優先採用的是堆疊的形式, 即先進後出
4、廣度優先則採用的是佇列的形式, 即先進先出
1.47 JS中的設計模式有哪些?
單例模式
保證一個類僅有一個範例,並提供一個存取它的全域性存取點。實現的方法為先判斷範例存在與否,如果存在則直接返回,如果不存在就建立了再返回,這就確保了一個類只有一個範例物件。
策略模式
定義一系列的演演算法,把他們一個個封裝起來,並且使他們可以相互替換。
代理模式
為一個物件提供一個代用品或預留位置,以便控制對它的存取。
中介者模式
通過一箇中介者物件,其他所有的相關物件都通過該中介者物件來通訊,而不是相互參照,當其中的一個物件發生改變時,只需要通知中介者物件即可。通過中介者模式可以解除物件與物件之間的緊耦合關係。
裝飾者模式
在不改變物件自身的基礎上,在程式執行期間給物件動態地新增方法。
1.48 forEach如何跳出迴圈?
forEach是不能通過break或者return來實現跳出迴圈的,為什麼呢?實現過forEach的同學應該都知道,forEach的的回撥函數形成了一個作用域,在裡面使用return並不會跳出,只會被當做continue
可以利用try catch
function getItemById(arr, id) { var item = null; try { arr.forEach(function (curItem, i) { if (curItem.id == id) { item = curItem; throw Error(); } }) } catch (e) { } return item; }
1.49 JS中如何將頁面重定向到另一個頁面?
1、使用 location.href:window.location.href ="url"
2、使用 location.replace: window.location.replace("url");
1.50 行動端如何實現上拉載入,下拉重新整理?
上拉載入
上拉載入的本質是頁面觸底,或者快要觸底時的動作
判斷頁面觸底我們需要先了解一下下面幾個屬性
scrollTop:捲動視窗的高度距離window頂部的距離,它會隨著往上捲動而不斷增加,初始值是0,它是一個變化的值
clientHeight:它是一個定值,表示螢幕可視區域的高度;
scrollHeight:頁面不能捲動時也是存在的,此時scrollHeight等於clientHeight。scrollHeight表示body所有元素的總長度(包括body元素自身的padding)
綜上我們得出一個觸底公式:
scrollTop + clientHeight >= scrollHeight
下拉重新整理
下拉重新整理的本質是頁面本身置於頂部時,使用者下拉時需要觸發的動作
關於下拉重新整理的原生實現,主要分成三步:
監聽原生touchstart事件,記錄其初始位置的值,e.touches[0].pageY;
監聽原生touchmove事件,記錄並計算當前滑動的位置值與初始位置值的差值,大於0表示向下拉動,並藉助CSS3的translateY屬性使元素跟隨手勢向下滑動對應的差值,同時也應設定一個允許滑動的最大值;
監聽原生touchend事件,若此時元素滑動達到最大值,則觸發callback,同時將translateY重設為0,元素回到初始位置
1.51 JS 中的陣列和函數在記憶體中是如何儲存的?
JavaScript 中的陣列儲存大致需要分為兩種情況:
同種型別資料的陣列分配連續的記憶體空間
存在非同種型別資料的陣列使用雜湊對映分配記憶體空間
溫馨提示:可以想象一下連續的記憶體空間只需要根據索引(指標)直接計算儲存位置即可。如果是雜湊對映那麼首先需要計算索引值,然後如果索引值有衝突的場景下還需要進行二次查詢(需要知道雜湊的儲存方式)。
2.1 什麼是閉包?
官方說法:閉包就是指有權存取另一個函數作用域中的變數的函數。
MDN說法:閉包是一種特殊的物件。它由兩部分構成:函數,以及建立該函數的環境。環境由閉包建立時在作用域中的任何區域性變陣列成。
深度回答
瀏覽器在載入頁面會把程式碼放在棧記憶體( ECStack )中執行,函數進棧執行會產生一個私有上下文( EC ),此上下文能保護裡面的使用變數( AO )不受外界干擾,並且如果當前執行上下文中的某些內容,被上下文以外的內容佔用,當前上下文不會出棧釋放,這樣可以儲存裡面的變數和變數值,所以我認為閉包是一種儲存和保護內部私有變數的機制。
2.2 閉包的作用
閉包有兩個常用的用途;
閉包的第一個用途是使我們在函數外部能夠存取到函數內部的變數。通過使用閉包,可以通過在外部呼叫閉包函數,從而在外部存取到函數內部的變數,可以使用這種方法來建立私有變數。
閉包的另一個用途是使已經執行結束的函數上下文中的變數物件繼續留在記憶體中,因為閉包函數保留了這個變數物件的參照,所以這個變數物件不會被回收。
2.3 閉包在專案中的參照場景,以及帶來的問題
在實際的專案中,會基於閉包把自己編寫的模組內容包裹起來,這樣編寫就可以保護自己的程式碼是私有的,防止和全域性變數或者是其他的程式碼衝突,這一點是利用保護機制。
但是不建議過多的使用閉包,因為使用不被釋放的上下文,是佔用棧記憶體空間的,過多的使用會導致導致記憶體漏失。
解決閉包帶來的記憶體漏失問題的方法是:使用完閉包函數後手動釋放。
2.4 閉包的使用場景
return 回一個函數
函數作為引數
IIFE(自執行函數)
迴圈賦值
使用回撥函數就是在使用閉包
節流防抖
函數柯里化
2.5 閉包的執行過程
形成私有上下文
進棧執行
一系列操作
(1). 初始化作用域鏈(兩頭<當前作用域,上級作用域>)
(2). 初始化this
(3). 初始化arguments
(4). 賦值形參
(5). 變數提升
(6). 程式碼執行
遇到變數就先看是否是自己私有的,不是自己私有的按照作用域鏈上查詢,如果不是上級的就繼續線上查詢,,直到 EC(G),變數的查詢其實就是一個作用域鏈的拼接過程,拼接查詢的鏈式就是作用域鏈。
正常情況下,程式碼執行完成之後,私有上下文出棧被回收。但是遇到特殊情況,如果當前私有上下文執行完成之後中的某個東西被執行上下文以外的東西佔用,則當前私有上下文就不會出棧釋放,也就是形成了不被銷燬的上下文,閉包。
2.6 執行上下文的型別
(1)全域性執行上下文
任何不在函數內部的都是全域性執行上下文,它首先會建立一個全域性的window物件,並且設定this的值等於這個全域性物件,一個程式中只有一個全域性執行上下文。
(2)函數執行上下文
當一個函數被呼叫時,就會為該函數建立一個新的執行上下文,函數的上下文可以有任意多個。
(3) eval函數執行上下文
執行在eval函數中的程式碼會有屬於他自己的執行上下文,不過eval函數不常使用,不做介紹。
2.7 執行上下文棧是什麼
JavaScript引擎使用執行上下文棧來管理執行上下文
當JavaScript執行程式碼時,首先遇到全域性程式碼,會建立一個全域性執行上下文並且壓入執行棧中,每當遇到一個函數呼叫,就會為該函數建立一個新的執行上下文並壓入棧頂,引擎會執行位於執行上下文棧頂的函數,當函數執行完成之後,執行上下文從棧中彈出,繼續執行下一個上下文。當所有的程式碼都執行完畢之後,從棧中彈出全域性執行上下文。
2.8 執行上下文的三個階段
建立階段 → 執行階段 → 回收階段
建立階段
(1)this繫結
在全域性執行上下文中,this指向全域性物件(window物件)
在函數執行上下文中,this指向取決於函數如何呼叫。如果它被一個參照物件呼叫,那麼 this 會被設定成那個物件,否則 this 的值被設定為全域性物件或者 undefined
(2)建立詞法環境元件
詞法環境是一種有識別符號——變數對映的資料結構,識別符號是指變數/函數名,變數是對實際物件或原始資料的參照。
詞法環境的內部有兩個元件:加粗樣式:環境記錄器:用來儲存變數個函數宣告的實際位置外部環境的參照:可以存取父級作用域
(3)建立變數環境元件
變數環境也是一個詞法環境,其環境記錄器持有變數宣告語句在執行上下文中建立的繫結關係。
執行階段
在這階段,執行變數賦值、程式碼執行
如果 Javascript 引擎在原始碼中宣告的實際位置找不到變數的值,那麼將為其分配 undefined 值
回收階段
執行上下文出棧等待虛擬機器器回收執行上下文
2.9 談談你對作用域的理解
作用域可以視為一套規則,這套規則用來管理引擎如何在當前作用域以及巢狀的子作用域根據識別符號名稱進行變數查詢。
簡單來說作用域就是變數的有效範圍。在一定的空間裡可以對變數資料進行讀寫操作,這個空間就是變數的作用域。
(1)全域性作用域
直接寫在script標籤的JS程式碼,都在全域性作用域。在全域性作用域下宣告的變數叫做全域性變數(在塊級外部定義的變數)。
全域性變數在全域性的任何位置下都可以使用;全域性作用域中無法存取到區域性作用域的中的變數。
全域性作用域在頁面開啟的時候建立,在頁面關閉時銷燬。
所有 window 物件的屬性擁有全域性作用域
var和function命令宣告的全域性變數和函數是window物件的屬性和方法
let命令、const命令、class命令宣告的全域性變數,不屬於window物件的屬性
(2)函數作用域(區域性作用域)
呼叫函數時會建立函數作用域,函數執行完畢以後,作用域銷燬。每呼叫一次函數就會建立一個新的函數作用域,他們之間是相互獨立的。
在函數作用域中可以存取全域性變數,在函數的外面無法存取函數內的變數。
當在函數作用域操作一個變數時,它會先在自身作用域中尋找,如果有就直接使用,如果沒有就向上一作用域中尋找,直到找到全域性作用域,如果全域性作用域中仍然沒有找到,則會報錯。
(3)塊級作用域
ES6之前JavaScript採用的是函數作用域+詞法作用域,ES6引入了塊級作用域。
任何一對花括號{}中的語句集都屬於一個塊,在塊中使用let和const宣告的變數,外部是存取不到的,這種作用域的規則就叫塊級作用域。
通過var宣告的變數或者非嚴格模式下建立的函數宣告沒有塊級作用域。
(4)詞法作用域
詞法作用域是靜態的作用域,無論函數在哪裡被呼叫,也無論它如何被呼叫,它的詞法作用域都只由函數被宣告時所處的位置決定。
編譯的詞法分析階段基本能夠知道全部識別符號在哪裡以及是如何宣告的,從而能夠預測在執行過中如何對它們進行查詢。
換句話說,詞法作用域就是你在寫程式碼的時候就已經決定了變數的作用域。
2.10 什麼是作用域鏈
當在js中使用一個變數的時候,首先js引擎會嘗試在當前作用域下去尋找該變數,如果沒找到,再到它的上層作用域尋找,以此類推直到找到該變數或是已經到了全域性作用域,這樣的變數作用域存取的鏈式結構, 被稱之為作用域鏈
深度回答
作用域鏈的本質上是一個指向變數物件的指標列表。變數物件是一個包含了執行環境中所有變數和函數的物件。作用域鏈的前端始終都是當前執行上下文的變數物件。全域性執行上下文的變數物件(也就是全域性物件)始終是作用域鏈的最後一個物件。
2.11 作用域鏈的作用
作用域鏈的作用是保證對執行環境有權存取的所有變數和函數的有序存取,通過作用域鏈,可以存取到外層環境的變數和函數。
2.12 作用域的常見應用場景
作用域的一個常見運用場景之一,就是 模組化。
由於 javascript 並未原生支援模組化導致了很多令人口吐芬芳的問題,比如全域性作用域汙染和變數名衝突,程式碼結構臃腫且複用性不高。在正式的模組化方案出臺之前,開發者為了解決這類問題,想到了使用函數作用域來建立模組的方案。
2.13 說說Js中的預解析?
JS 引擎在執行一份程式碼的時候,會按照下面的步驟進行工作:
1.把變數的宣告提升到當前作用域的最前面,只會提升宣告,不會提升賦值
2.把函數的宣告提升到當前作用域的最前面,只會提升宣告,不會提升呼叫
3.先提升 function,在提升 var
2.14 變數提升與函數提升的區別?
變數提升
簡單說就是在 JavaScript 程式碼執行前引擎會先進行預編譯,預編譯期間會將變數宣告與函數宣告提升至其對應作用域的最頂端,函數內宣告的變數只會提升至該函數作用域最頂層,當函數內部定義的一個變數與外部相同時,那麼函數體內的這個變數就會被上升到最頂端。
函數提升
函數提升只會提升函數宣告式寫法,函數表示式的寫法不存在函數提升
函數提升的優先順序大於變數提升的優先順序,即函數提升在變數提升之上
2.14 如何延長作用域鏈?
作用域鏈是可以延長的。
延長作用域鏈: 執行環境的型別只有兩種,全域性和區域性(函數)。但是有些語句可以在作用域鏈的前端臨時增加一個變數物件,該變數物件會在程式碼執行後被移除。
具體來說就是執行這兩個語句時,作用域鏈都會得到加強
try - catch 語句的 catch 塊:會建立一個新的變數物件,包含的是被丟擲的錯誤對 象的宣告。
with 語句:with 語句會將指定的物件新增到作用域鏈中。
2.15 瀏覽器的垃圾回收機制
(1)記憶體的生命週期
JS 環境中分配的記憶體, 一般有如下生命週期:
記憶體分配:當我們宣告變數、函數、物件的時候,系統會自動為他們分配記憶體
記憶體使用:即讀寫記憶體,也就是使用變數、函數等
記憶體回收:使用完畢,由垃圾回收自動回收不再使用的記憶體
全域性變數一般不會回收, 一般區域性變數的的值, 不用了, 會被自動回收掉
(2)垃圾回收的概念
垃圾回收:JavaScript程式碼執行時,需要分配記憶體空間來儲存變數和值。當變數不在參與執行時,就需要系統收回被佔用的記憶體空間,這就是垃圾回收。
回收機制:
Javascript 具有自動垃圾回收機制,會定期對那些不再使用的變數、物件所佔用的記憶體進行釋放,原理就是找到不再使用的變數,然後釋放掉其佔用的記憶體。
JavaScript中存在兩種變數:區域性變數和全域性變數。全域性變數的生命週期會持續要頁面解除安裝;而區域性變數宣告在函數中,它的生命週期從函數執行開始,直到函數執行結束,在這個過程中,區域性變數會在堆或棧中儲存它們的值,當函數執行結束後,這些區域性變數不再被使用,它們所佔有的空間就會被釋放。
不過,當區域性變數被外部函數使用時,其中一種情況就是閉包,在函數執行結束後,函數外部的變數依然指向函數內部的區域性變數,此時區域性變數依然在被使用,所以不會回收。
(3)垃圾回收的方式
1.參照計數法
這個用的相對較少,IE採用的參照計數演演算法。參照計數就是跟蹤記錄每個值被參照的次數。當宣告了一個變數並將一個參照型別賦值給該變數時,則這個值的參照次數就是1。相反,如果包含對這個值參照的變數又取得了另外一個值,則這個值的參照次數就減1。當這個參照次數變為0時,說明這個變數已經沒有價值,因此,在在機回收期下次再執行時,這個變數所佔有的記憶體空間就會被釋放出來。
這種方法會引起迴圈參照的問題:例如:obj1和obj2通過屬性進行相互參照,兩個物件的參照次數都是2。當使用迴圈計數時,由於函數執行完後,兩個物件都離開作用域,函數執行結束,obj1和obj2還將會繼續存在,因此它們的參照次數永遠不會是0,就會引起迴圈參照。
2.標記清除法
現代的瀏覽器已經不再使用參照計數演演算法了。
現代瀏覽器通用的大多是基於標記清除演演算法的某些改進演演算法,總體思想都是一致的。
標記清除是瀏覽器常見的垃圾回收方式,當變數進入執行環境時,就標記這個變數「進入環境」,被標記為「進入環境」的變數是不能被回收的,因為他們正在被使用。當變數離開環境時,就會被標記為「離開環境」,被標記為「離開環境」的變數會被記憶體釋放。
垃圾收集器在執行的時候會給儲存在記憶體中的所有變數都加上標記。然後,它會去掉環境中的變數以及被環境中的變數參照的標記。而在此之後再被加上標記的變數將被視為準備刪除的變數,原因是環境中的變數已經無法存取到這些變數了。最後。垃圾收集器完成記憶體清除工作,銷燬那些帶標記的值,並回收他們所佔用的記憶體空間。
(4)如何減少垃圾回收
雖然瀏覽器可以進行垃圾自動回收,但是當程式碼比較複雜時,垃圾回收所帶來的代價比較大,所以應該儘量減少垃圾回收。
對陣列進行優化: 在清空一個陣列時,最簡單的方法就是給其賦值為[ ],但是與此同時會建立一個新的空物件,可以將陣列的長度設定為0,以此來達到清空陣列的目的。
對object進行優化: 物件儘量複用,對於不再使用的物件,就將其設定為null,儘快被回收。
對函數進行優化: 在迴圈中的函數表示式,如果可以複用,儘量放在函數的外面。
(5)記憶體漏失是什麼
是指由於疏忽或錯誤造成程式未能釋放已經不再使用的記憶體
(6)哪些情況會導致記憶體漏失
以下四種情況會造成記憶體的洩漏:
意外的全域性變數: 由於使用未宣告的變數,而意外的建立了一個全域性變數,而使這個變數一直留在記憶體中無法被回收。
被遺忘的計時器或回撥函數: 設定了 setInterval 定時器,而忘記取消它,如果迴圈函數有對外部變數的參照的話,那麼這個變數會被一直留在記憶體中,而無法被回收。
脫離 DOM 的參照: 獲取一個 DOM 元素的參照,而後面這個元素被刪除,由於一直保留了對這個元素的參照,所以它也無法被回收。
閉包: 不合理的使用閉包,從而導致某些變數一直被留在記憶體當中。
3.1 什麼是函數語言程式設計
函數語言程式設計是一種"程式設計正規化"(programming paradigm),一種編寫程式的方法論
主要的程式設計正規化有三種:指令式程式設計,宣告式程式設計和函數語言程式設計
相比指令式程式設計,函數語言程式設計更加強調程式執行的結果而非執行的過程,倡導利用若干簡單的執行單元讓計算結果不斷漸進,逐層推導複雜的運算,而非設計一個複雜的執行過程
3.2 函數語言程式設計的優缺點
優點
更好的管理狀態:因為它的宗旨是無狀態,或者說更少的狀態,能最大化的減少這些未知、優化程式碼、減少出錯情況
更簡單的複用:固定輸入->固定輸出,沒有其他外部變數影響,並且無副作用。這樣程式碼複用時,完全不需要考慮它的內部實現和外部影響
更優雅的組合:往大的說,網頁是由各個元件組成的。往小的說,一個函數也可能是由多個小函陣列成的。更強的複用性,帶來更強大的組合性
隱性好處。減少程式碼量,提高維護性
缺點
效能:函數語言程式設計相對於指令式程式設計,效能絕對是一個短板,因為它往往會對一個方法進行過度包裝,從而產生上下文切換的效能開銷
資源佔用:在 JS 中為了實現物件狀態的不可變,往往會建立新的物件,因此,它對垃圾回收所產生的壓力遠遠超過其他程式設計方式
遞迴陷阱:在函數語言程式設計中,為了實現迭代,通常會採用遞迴操作
3.3 什麼是純函數,它有什麼優點
純函數是對給定的輸入返還相同輸出的函數,並且要求你所有的資料都是不可變的,即純函數=無狀態+資料不可變
特性:
函數內部傳入指定的值,就會返回確定唯一的值
不會造成超出作用域的變化,例如修改全域性變數或參照傳遞的引數
優勢:
使用純函數,我們可以產生可測試的程式碼
不依賴外部環境計算,不會產生副作用,提高函數的複用性
可讀性更強 ,函數不管是否是純函數 都會有一個語意化的名稱,更便於閱讀
可以組裝成複雜任務的可能性。符合模組化概念及單一職責原則
3.4 什麼是組合函數 (compose)
在函數語言程式設計中,有一個很重要的概念就是函陣列合,實際上就是把處理的函數資料像管道一樣連線起來,然後讓資料穿過管道連線起來,得到最終的結果。
組合函數,其實大致思想就是將 多個函陣列合成一個函數,c(b(a(a(1)))) 這種寫法簡寫為 compose(c, b, a, a)(x) 。但是注意這裡如果一個函數都沒有傳入,那就是傳入的是什麼就返回什麼,並且函數的執行順序是和傳入的順序相反的。
var compose = (...funcs) => { // funcs(陣列):記錄的是所有的函數 // 這裡其實也是利用了柯里化的思想,函數執行,生成一個閉包,預先把一些資訊儲存,供下級上下文使用 return (x) => { var len = funcs.length; // 如果沒有函數執行,直接返回結果 if (len === 0) return x; if (len === 1) funcs[0](x); return funcs.reduceRight((res, func) => { return func(res); }, x); }; }; var resFn = compose(c, b, a, a); resFn(1);
組合函數的思想,在很多框架中也被使用,例如:redux,實現效果來說是其實和上面的程式碼等價。
3.5 什麼是惰性函數
惰性載入表示函數執行的分支只會在函數第一次掉用的時候執行,在第一次呼叫過程中,該函數會被覆蓋為另一個按照合適方式執行的函數,這樣任何對原函數的呼叫就不用再經過執行的分支了
惰性函數相當於有記憶的功能一樣,當它已經判斷了一遍的話,第二遍就不會再判斷了。
比如現在要求寫一個test函數,這個函數返回首次呼叫時的new Date().getTime(),注意是首次,而且不允許有全域性變數的汙染
//一般會這樣實現 var test = (function () { var t = null; return function () { if (t) { return t; } t = new Date().getTime(); return t; } })(); // 用惰性函數實現 var test = function () { var t = new Date().getTime(); test = function () { return t; } return test(); } console.log(test()); console.log(test()); console.log(test());
3.6 什麼是高階函數
高階函數是指使用其他函數作為引數、或者返回一個函數作為結果的函數。
3.7 說說你對函數柯里化的理解
柯里化(Currying)是把接受多個引數的函數變換成接受一個單一引數(最初函數的第一個引數)的函數,並且返回接受餘下的引數且返回結果的新函數的技術。
函數柯里化的好處:
(1)引數複用:需要輸入多個引數,最終只需輸入一個,其餘通過 arguments 來獲取
(2)提前確認:避免重複去判斷某一條件是否符合,不符合則 return 不再繼續執行下面的操作
(3)延遲執行:避免重複的去執行程式,等真正需要結果的時候再執行
3.8 什麼是箭頭函數,有什麼特徵
使用 "箭頭" ( => ) 來定義函數. 箭頭函數相當於匿名函數, 並且簡化了函數定義
箭頭函數的特徵:
箭頭函數沒有this, this指向定義箭頭函數所處的外部環境
箭頭函數的this永遠不會變,call、apply、bind也無法改變
箭頭函數只能宣告成匿名函數,但可以通過表示式的方式讓箭頭函數具名
箭頭函數沒有原型prototype
箭頭函數不能當做一個建構函式 因為 this 的指向問題
箭頭函數沒有 arguments 在箭頭函數內部存取這個變數存取的是外部環境的arguments, 可以使用 ...代替
3.9 說說你對遞迴函數的理解
如果一個函數在內部呼叫自身本身,這個函數就是遞迴函數
其核心思想是把一個大型複雜的問題層層轉化為一個與原問題相似的規模較小的問題來求解
一般來說,遞迴需要有邊界條件、遞迴前進階段和遞迴返回階段。當邊界條件不滿足時,遞迴前進;當邊界條件滿足時,遞迴返回
優點:結構清晰、可讀性強
缺點:效率低、呼叫棧可能會溢位,其實每一次函數呼叫會在記憶體棧中分配空間,而每個程序的棧的容量是有限的,當呼叫的層次太多時,就會超出棧的容量,從而導致棧溢位。
3.10 什麼是尾遞迴
尾遞迴,即在函數尾位置呼叫自身(或是一個尾呼叫本身的其他函數等等)。
在遞迴呼叫的過程當中系統為每一層的返回點、區域性量等開闢了棧來儲存,遞迴次數過多容易造成棧溢位
這時候,我們就可以使用尾遞迴,即一個函數中所有遞迴形式的呼叫都出現在函數的末尾,對於尾遞回來說,由於只存在一個呼叫記錄,所以永遠不會發生"棧溢位"錯誤
3.11 函數傳參,傳遞複雜資料型別和簡單資料型別有什麼區別
傳遞複雜資料型別傳遞的是參照的地址,修改會改變
簡單資料型別傳遞的是具體的值,不會相互影響
/* let a = 8 function fn(a) { a = 9 } fn(a) console.log(a) // 8 */ let a = { age: 8 } function fn(a) { a.age = 9 } fn(a) console.log(a.age) // 9
3.12 函數宣告與函數表示式的區別
函數宣告: funtion開頭,有函數提升
函數表示式: 不是funtion開頭,沒有函數提升
3.13 什麼是函數快取,如何實現?
概念
函數快取,就是將函數運算過的結果進行快取
本質上就是用空間(快取儲存)換時間(計算過程)
常用於快取資料計算結果和快取物件
如何實現
實現函數快取主要依靠閉包、柯里化、高階函數
應用場景
對於昂貴的函數呼叫,執行復雜計算的函數
對於具有有限且高度重複輸入範圍的函數
對於具有重複輸入值的遞迴函數
對於純函數,即每次使用特定輸入呼叫時返回相同輸出的函數
3.14 call、apply、bind三者的異同
共同點 :
都可以改變this指向;
三者第一個引數都是this要指向的物件,如果如果沒有這個引數或引數為undefined或null,則預設指向全域性window
不同點:
call 和 apply 會呼叫函數, 並且改變函數內部this指向.
call 和 apply傳遞的引數不一樣,call傳遞引數使用逗號隔開,apply使用陣列傳遞,且apply和call是一次性傳入引數,而bind可以分為多次傳入
bind是返回繫結this之後的函數
應用場景
call 經常做繼承.
apply經常跟陣列有關係. 比如藉助於數學物件實現陣列最大值最小值
bind 不呼叫函數,但是還想改變this指向. 比如改變定時器內部的this指
【相關推薦:、】
以上就是JavaScript萬字面試總結的詳細內容,更多請關注TW511.COM其它相關文章!