您好,登錄后才能下訂單哦!
這篇“JavaScript面試的知識點有哪些”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“JavaScript面試的知識點有哪些”文章吧。
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面試的知識點有哪些”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。