天天看點

【知識總結】JavaScript核心問題彙總

資料類型

JavaScript有哪些資料類型,差別是什麼?

undefined、null、boolean、number、string、object、symbol、bigint

其中 Symbol 和 BigInt 是ES6 中新增的資料類型:

  • Symbol 代表建立後獨一無二且不可變的資料類型,它主要是為了解決可能出現的全局變量沖突的問題。
  • BigInt 是一種數字類型的資料,它可以表示任意精度格式的整數,使用 BigInt 可以安全地存儲和操作大整數,即使這個數已經超出了 Number 能夠表示的安全整數範圍。

這些資料可以分為原始資料類型和引用資料類型:

  • 棧:原始資料類型(Undefined、Null、Boolean、Number、String)
  • 堆:引用資料類型(對象、數組和函數)

資料類型檢測方式有哪些?

1)typeof

其中數組、對象、null都會被判斷為object,其他判斷都正确。

2)instanceof

instanceof可以正确判斷對象的類型,其内部運作機制是判斷在其原型鍊中能否找到該類型的原型。

console.log(2 instanceof Number);                    // false
console.log(true instanceof Boolean);                // false 
console.log('str' instanceof String);                // false 
 
console.log([] instanceof Array);                    // true
console.log(function(){} instanceof Function);       // true
console.log({} instanceof Object);                   // true
           

可以看到,instanceof隻能正确判斷引用資料類型,而不能判斷基本資料類型。instanceof 運算符可以用來測試一個對象在其原型鍊中是否存在一個構造函數的 prototype 屬性。

3)constructor

  • 判斷資料的類型
  • 對象執行個體通過 constrcutor 對象通路它的構造函數

4)Object.prototype.toString.call()

Object.prototype.toString.call()

使用 Object 對象的原型方法 toString 來判斷資料類型。

同樣是檢測對象obj調用toString方法,

obj.toString()

的結果和

Object.prototype.toString.call(obj)

的結果不一樣,這是為什麼?

這是因為toString是Object的原型方法,而Array、function等類型作為Object的執行個體,都重寫了toString方法。不同的對象類型調用toString方法時,根據原型鍊的知識,調用的是對應的重寫之後的toString方法(function類型傳回内容為函數體的字元串,Array類型傳回元素組成的字元串…),而不會去調用Object上原型toString方法(傳回對象的具體類型),是以采用obj.toString()不能得到其對象類型,隻能将obj轉換為字元串類型;是以,在想要得到對象的具體類型時,應該調用Object原型上的toString方法。

判斷數組的方式有哪些?

1)Object.prototype.toString.call()

2)原型鍊

3)Array.isArray()

4)instanceof

5)Array.prototype.isPrototypeOf

null和undefined差別是什麼?

首先 Undefined 和 Null 都是基本資料類型,這兩個基本資料類型分别都隻有一個值,就是 undefined 和 null。

undefined 代表的含義是未定義,null 代表的含義是空對象。一般變量聲明了但還沒有定義的時候會傳回 undefined,null主要用于指派給一些可能會傳回對象的變量,作為初始化。

undefined 在 JavaScript 中不是一個保留字,這意味着可以使用 undefined 來作為一個變量名,但是這樣的做法是非常危險的,它會影響對 undefined 值的判斷。我們可以通過一些方法獲得安全的 undefined 值,比如說 void 0。

當對這兩種類型使用 typeof 進行判斷時,Null 類型化會傳回 “object”,這是一個曆史遺留的問題。當使用雙等号對兩種類型的值進行比較時會傳回 true,使用三個等号時會傳回 false。

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。

intanceof 操作符的實作原理及實作?

instanceof 運算符用于判斷構造函數的 prototype 屬性是否出現在對象的原型鍊中的任何位置。

function myInstanceof(left,right){
	let proto = Object.getPrototypeOf(left) // 擷取對象的原型
	let prototype = right.prototype;  擷取構造函數的 prototype 對象
	while(true){ // 判斷構造函數的 prototype 對象是否在對象的原型鍊上
		if(!proto) return false;
		if(proto === prototype) return true;
		proto = Object.getPrototypeOf(proto);  // 如果沒有找到,就繼續從其原型上找,Object.getPrototypeOf方法用來擷取指定對象的原型
	}
}
``

#### 7、為什麼0.1+0.2 ! == 0.3,如何讓其相等?
 

```javascript
(n1 + n2).toFixed(2) // toFixed(num) 方法可把 Number 四舍五入為指定小數位數的數字。
           

計算機是通過二進制的方式存儲資料的,是以計算機計算0.1+0.2的時候,實際上是計算的兩個數的二進制的和。0.1的二進制是0.0001100110011001100…(1100循環),0.2的二進制是:0.00110011001100…(1100循環),這兩個數的二進制都是無限循環的數。那JavaScript是如何處理無限循環的二進制小數呢?

一般我們認為數字包括整數和小數,但是在 JavaScript 中隻有一種數字類型:Number,它的實作遵循IEEE 754标準,使用64位固定長度來表示,也就是标準的double雙精度浮點數。在二進制科學表示法中,雙精度浮點數的小數部分最多隻能保留52位,再加上前面的1,其實就是保留53位有效數字,剩餘的需要舍去,遵從“0舍1入”的原則。

如何實作0.1+0.2=0.3呢?隻要判斷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
           

8、如何擷取安全的undefin值?

因為 undefined 是一個辨別符,是以可以被當作變量來使用和指派,但是這樣會影響 undefined 的正常判斷。表達式 void ___ 沒有傳回值,是以傳回結果是 undefined。void 并不改變表達式的結果,隻是讓表達式不傳回值。是以可以用 void 0 來獲得 undefined。

typeof NaN 的結果是什麼?

NaN 指“不是一個數字”(not a number),NaN 是一個“警戒值”(sentinel value,有特殊用途的正常值),用于指出數字類型中的錯誤情況,即“執行數學運算沒有成功,這是失敗後傳回的結果”。

NaN 是一個特殊值,它和自身不相等,是唯一一個非自反(自反,reflexive,即 x === x 不成立)的值。而 NaN !== NaN 為 true。

isNaN 和 Number.isNaN 函數的差別?

  • 函數

    isNaN

    接收參數後,會嘗試将這個參數轉換為數值,任何不能被轉換為數值的的值都會傳回 true,是以非數字值傳入也會傳回 true ,會影響 NaN 的判斷。
  • 函數

    Number.isNaN

    會首先判斷傳入參數是否為數字,如果是數字再繼續判斷是否為 NaN ,不會進行資料類型的轉換,這種方法對于 NaN 的判斷更為準确。

== 操作符的強制類型轉換規則?

對于 == 來說,如果對比雙方的類型不一樣,就會進行類型轉換。假如對比 x 和 y 是否相同,就會進行如下判斷流程:

  1. 首先會判斷兩者類型是否相同,相同的話就比較兩者的大小;
  2. 類型不相同的話,就會進行類型轉換;
  3. 會先判斷是否在對比 null 和 undefined,是的話就會傳回 true
  4. 判斷兩者類型是否為 string 和 number,是的話就會将字元串轉換為 number
  5. 判斷其中一方是否為 boolean,是的話就會把 boolean 轉為 number 再進行判斷
  6. 判斷其中一方是否為 object 且另一方為 string、number 或者 symbol,是的話就會把 object 轉為原始類型再進行判斷

其他值到字元串的轉換規則?

  • Null 和 Undefined 類型 ,null 轉換為 “null”,undefined 轉換為 “undefined”
  • Boolean 類型,true 轉換為 “true”,false 轉換為 “false”。
  • Number 類型的值直接轉換,不過那些極小和極大的數字會使用指數形式。
  • Symbol 類型的值直接轉換,但是隻允許顯式強制類型轉換,使用隐式強制類型轉換會産生錯誤。
  • 對普通對象來說,除非自行定義 toString() 方法,否則會調用 toString()(Object.prototype.toString())來傳回内部屬性 [[Class]] 的值,如"[object Object]"。如果對象有自己的 toString() 方法,字元串化時就會調用該方法并使用其傳回值。

其他值到數字值的轉換規則?

  • Undefined 類型的值轉換為 NaN。
  • Null 類型的值轉換為 0。
  • Boolean 類型的值,true 轉換為 1,false 轉換為 0。
  • String 類型的值轉換如同使用 Number() 函數進行轉換,如果包含非數字值則轉換為 NaN,空字元串為 0。
  • Symbol 類型的值不能轉換為數字,會報錯。
  • 對象(包括數組)會首先被轉換為相應的基本類型值,如果傳回的是非數字的基本類型值,則再遵循以上規則将其強制轉換為數字。

Object.is() 與比較操作符 “ === ” 和 “ == ” 的差別?

  • 使用雙等号(==)進行相等判斷時,如果兩邊的類型不一緻,則會進行強制類型轉化後再進行比較。
  • 使用三等号(===)進行相等判斷時,如果兩邊的類型不一緻時,不會做強制類型準換,直接傳回 false。
  • 使用 Object.is 來進行相等判斷時,一般情況下和三等号的判斷相同,它處理了一些特殊的情況,比如 -0 和 +0 不再相等,兩個 NaN 是相等的。

object.assign和擴充運算法是深拷貝還是淺拷貝,兩者差別

擴充運算符:

let outObj = {
  inObj: {a: 1, b: 2}
}
let newObj = {...outObj}
newObj.inObj.a = 2
console.log(outObj) // {inObj: {a: 2, b: 2}}
           

Object.assign():

let outObj = {
  inObj: {a: 1, b: 2}
}
let newObj = Object.assign({}, outObj)
newObj.inObj.a = 2
console.log(outObj) // {inObj: {a: 2, b: 2}}
           

ES6

let、const、var差別

1)塊級作用域:塊作用域由 { }包括,let和const具有塊級作用域,var不存在塊級作用域。塊級作用域解決了ES5中的兩個問題:

  1. 内層變量可能覆寫外層變量
  2. 用來計數的循環變量洩露為全局變量

2)變量提升:var存在變量提升,let和const不存在變量提升,即在變量隻能在聲明之後使用,否在會報錯。

3)給全局添加屬性:浏覽器的全局對象是window,Node的全局對象是global。var聲明的變量為全局變量,并且會将該變量添加為全局對象的屬性,但是let和const不會。

4)重複聲明:var聲明變量時,可以重複聲明變量,後聲明的同名變量會覆寫之前聲明的周遊。const和let不允許重複聲明變量。

5)暫時性死區:在使用let、const指令聲明變量之前,該變量都是不可用的。這在文法上,稱為暫時性死區。使用var聲明的變量不存在暫時性死區。

6)初始值設定:在變量聲明時,var 和 let 可以不用設定初始值。而const聲明變量必須設定初始值。

7)指針指向:let和const都是ES6新增的用于建立變量的文法。 let建立的變量是可以更改指針指向(可以重新指派)。但const聲明的變量是不允許改變指針的指向。

【知識總結】JavaScript核心問題彙總

const對象的屬性可以修改嗎?

const保證的并不是變量的值不能改動,而是變量指向的那個記憶體位址不能改動。對于基本類型的資料(數值、字元串、布爾值),其值就儲存在變量指向的那個記憶體位址,是以等同于常量。

但對于引用類型的資料(主要是對象和數組)來說,變量指向資料的記憶體位址,儲存的隻是一個指針,const隻能保證這個指針是固定不變的,至于它指向的資料結構是不是可變的,就完全不能控制了。

如果new一個箭頭函數的會怎麼樣?

箭頭函數是ES6中的提出來的,它沒有prototype,也沒有自己的this指向,更不可以使用arguments參數,是以不能New一個箭頭函數。

new操作符的實作步驟如下:

  • 建立一個對象
  • 将構造函數的作用域賦給新對象(也就是将對象的__proto__屬性指向構造函數的prototype屬性)
  • 指向構造函數中的代碼,構造函數中的this指向該對象(也就是為這個對象添加屬性和方法)
  • 傳回新的對象

    是以,上面的第二、三步,箭頭函數都是沒有辦法執行的。

箭頭函數與普通函數的差別

1)箭頭函數比普通函數更加簡潔

  • 如果沒有參數,就直接寫一個空括号即可
  • 如果隻有一個參數,可以省去參數的括号
  • 如果有多個參數,用逗号分割
  • 如果函數體的傳回值隻有一句,可以省略大括号
  • 如果函數體不需要傳回值,且隻有一句話,可以給這個語句前面加一個void關鍵字。最常見的就是調用一個函數:

2)箭頭函數沒有自己的this

箭頭函數不會建立自己的this, 是以它沒有自己的this,它隻會在自己作用域的上一層繼承this。是以箭頭函數中this的指向在它在定義時已經确定了,之後不會改變。

3)箭頭函數繼承來的this指向永遠不會改變

var id = 'GLOBAL';
var obj = {
  id: 'OBJ',
  a: function(){
    console.log(this.id);
  },
  b: () => {
    console.log(this.id);
  }
};
obj.a();    // 'OBJ'
obj.b();    // 'GLOBAL'
new obj.a()  // undefined
new obj.b()  // Uncaught TypeError: obj.b is not a constructor
           

對象obj的方法b是使用箭頭函數定義的,這個函數中的this就永遠指向它定義時所處的全局執行環境中的this,即便這個函數是作為對象obj的方法調用,this依舊指向Window對象。需要注意,定義對象的大括号{}是無法形成一個單獨的執行環境的,它依舊是處于全局執行環境中。

箭頭函數的this指向哪⾥?

箭頭函數不同于傳統JavaScript中的函數,箭頭函數并沒有屬于⾃⼰的this,它所謂的this是捕獲其所在上下⽂的 this 值,作為⾃⼰的 this 值,并且由于沒有屬于⾃⼰的this,是以是不會被new調⽤的,這個所謂的this也不會被改變。

說一下閉包

一句話可以概括:閉包就是能夠讀取其他函數内部變量的函數,或者子函數在外調用,子函數所在的父函數的作用域不會被釋放。

繼承

原型鍊繼承

  • 特點:基于原型鍊,既是父類的執行個體,也是子類的執行個體
  • 缺點:無法實作多繼承

構造繼承:使用父類的構造函數來增強子類執行個體,等于是複制父類的執行個體屬性給子類(沒用到原型)

  • 特點:可以實作多繼承
  • 缺點:隻能繼承父類執行個體的屬性和方法,不能繼承原型上的屬性和方法。

執行個體繼承:為父類執行個體添加新特性,作為子類執行個體傳回

拷貝繼承:拷貝父類元素上的屬性和方法

組合繼承:相當于構造繼承和原型鍊繼承的組合體。通過調用父類構造,繼承父類的屬性并保留傳參的優點,然後通過将父類執行個體作為子類原型,實作函數複用

  • 特點:可以繼承執行個體屬性/方法,也可以繼承原型屬性/方法
  • 缺點:調用了兩次父類構造函數,生成了兩份執行個體

寄生組合繼承:通過寄生方式,砍掉父類的執行個體屬性,這樣,在調用兩次父類的構造的時候,就不會初始化兩次執行個體方法/屬性。

如何解決異步回調地獄

promise、generator、async/await

如何讓事件先冒泡後捕獲

在DOM标準事件模型中,是先捕獲後冒泡。但是如果要實作先冒泡後捕獲的效果,對于同一個事件,監聽捕獲和冒泡,分别對應相應的處理函數,監聽到捕獲事件,先暫緩執行,直到冒泡事件被捕獲後再執行捕獲之間。

說一下事件委托

事件委托指的是,不在事件的發生地(直接dom)上設定監聽函數,而是在其父元素上設定監聽函數,通過事件冒泡,父元素可以監聽到子元素上事件的觸發,通過判斷事件發生元素DOM的類型,來做出不同的響應。

好處:比較合适動态元素的綁定,新添加的子元素也會有監聽函數,也可以有事件觸發機制。

說一下圖檔的懶加載和預加載

預加載:提前加載圖檔,當使用者需要檢視時可直接從本地緩存中渲染。

懶加載:懶加載的主要目的是作為伺服器前端的優化,減少請求數或延遲請求數。

mouseover和mouseenter的差別

mouseover:當滑鼠移入元素或其子元素都會觸發事件,是以有一個重複觸發,冒泡的過程。對應的移除事件是mouseout

mouseenter:當滑鼠移除元素本身(不包含元素的子元素)會觸發事件,也就是不會冒泡,對應的移除事件是mouseleave

js的new操作符做了哪些事情

new 操作符建立了一個空對象,這個對象原型指向構造函數的prototype,執行構造函數後傳回這個對象。

改變函數内部this指針的指向函數(bind,apply,call的差別)

通過apply和call改變函數的this指向,他們兩個函數的第一個參數都是一樣的表示要改變指向的那個對象,第二個參數,apply是數組,而call則是arg1,arg2…這種形式。通過bind改變this作用域會傳回一個新的函數,這個函數不會馬上執行。

異步加載js的方法

defer:隻支援IE如果您的腳本不會改變文檔的内容,可将 defer 屬性加入到script标簽中,以便加快處理文檔的速度。因為浏覽器知道它将能夠安全地讀取文檔的剩餘部分而不用執行腳本,它将推遲對腳本的解釋,直到文檔已經顯示給使用者為止。

async,HTML5屬性僅适用于外部腳本,并且如果在IE中,同時存在defer和async,那麼defer的優先級比較高,腳本将在頁面完成時執行。

JS中的垃圾回收機制

由于字元串、對象和數組沒有固定大小,所有當他們的大小已知時,才能對他們進行動态的存儲配置設定。JavaScript程式每次建立字元串、數組或對象時,解釋器都必須配置設定記憶體來存儲那個實體。隻要像這樣動态地配置設定了記憶體,最終都要釋放這些記憶體以便他們能夠被再用,否則,JavaScript的解釋器将會消耗完系統中所有可用的記憶體,造成系統崩潰。

eval是做什麼的

它的功能是将對應的字元串解析成js并執行,應該避免使用js,因為非常消耗性能(2次,一次解析成js,一次執行)

如何了解前端子產品化

前端子產品化就是複雜的檔案程式設計一個一個獨立的子產品,比如js檔案等等,分成獨立的子產品有利于重用(複用性)和維護(版本疊代),這樣會引來子產品之間互相依賴的問題,是以有了commonJS規範,AMD,CMD規範等等,以及用于js打包(編譯等處理)的工具webpack。

說一下Commonjs、AMD和CMD

一個子產品是能實作特定功能的檔案,有了子產品就可以友善的使用别人的代碼,想要什麼功能就能加載什麼子產品。

Commonjs:開始于伺服器端的子產品化,同步定義的子產品化,每個子產品都是一個單獨的作用域,子產品輸出,modules.exports,子產品加載require()引入子產品。

AMD:中文名異步子產品定義的意思。

實作一個兩列等高布局,講講思路

為了實作兩列等高,可以給每列加上

padding-bottom:9999px;margin-bottom:-9999px;

同時父元素設定

overflow:hidden;

js判斷類型

判斷方法:typeof(),instanceof,Object.prototype.toString.call()等

數組常用方法

push(),pop(),shift(),unshift(),splice(),sort(),reverse(),map()等

數組去重

  • indexOf循環去重
  • ES6 Set去重;Array.from(new Set(array))
  • Object 鍵值對去重;把數組的值存成 Object 的 key 值,比如 Object[value1] =

    true,在判斷另一個值的時候,如果 Object[value2]存在的話,就說明該值是重複的。

性能優化

減少HTTP請求、使用内容釋出網絡(CDN)、添加本地緩存、壓縮資源檔案、避免使用CSS表達式、減少DNS查詢、使用外部javascript和CSS、避免重定向、圖檔懶加載

Js基本資料類型

undefined、null、number、boolean、string、symbol

跨域的原理

跨域,是指浏覽器不能執行其他網站的腳本。它是由浏覽器的同源政策造成的,是浏覽器對JavaScript實施的安全限制,那麼隻要協定、域名、端口有任何一個不同,都被當作是不同的域。跨域原理,即是通過各種方式,避開浏覽器的安全限制。

webpack用來幹什麼的

webpack 是一個現代 JavaScript 應用程式的靜态子產品打包器(module bundler)。當 webpack 處理應用程式時,它會遞歸地建構一個依賴關系圖(dependency graph),其中包含應用程式需要的每個子產品,然後将所有這些子產品打包成一個或多個bundle。

vue的生命周期

Vue執行個體有一個完整的生命周期,也就是從開始建立、初始化資料、編譯模闆、挂載Dom、渲染→更新→渲染、銷毀等一系列過程,我們稱這是Vue的生命周期。通俗說就是Vue執行個體從建立到銷毀的過程,就是生命周期。

簡單介紹一下symbol

Symbol是ES6 的新增屬性,代表用給定名稱作為唯一辨別,這種類型的值可以這樣建立,let id=symbol(“id”)

介紹一下promise,及其底層如何實作

Promise是一個對象,儲存着未來将要結束的事件,有兩個特征:

  • 對象的狀态不受外部影響,Promise對象代表一個異步操作,有三種狀态,pending進行中,fulfilled已成功,rejected已失敗,隻有異步操作的結果,才可以決定目前是哪一種狀态,任何其他操作都無法改變這個狀态,這也就是promise名字的由來。
  • 一旦狀态改變,就不會再變,promise對象狀态改變隻有兩種可能,從pending改到fulfilled或者從pending改到rejected,隻要這兩種情況發生,狀态就凝固了,不會再改變,這個時候就稱為定型resolved。

js字元串轉數字的方法

通過函數parseInt(),可解析一個字元串,并傳回一個整數,文法為parseInt(string ,radix)

  • string:被解析的字元串
  • radix:表示要解析的數字的基數,預設是十進制,如果radix<2或>36,則傳回NaN

##let const var的差別

提起這三個最明顯的差別是var聲明的變量是全局或者整個函數塊的,而let,const聲明的變量是塊級的變量,var聲明的變量存在變量提升,let,const不存在,let聲明的變量允許重新指派,const不允許。

ES6箭頭函數的特性

箭頭函數與普通函數的差別在于:

  • 箭頭函數沒有this,是以需要通過查找作用域鍊來确定this的值,這就意味着如果箭頭函數被非箭頭函數包含,this綁定的就是最近一層非箭頭函數的this
  • 箭頭函數沒有自己的arguments對象,但是可以通路外圍函數的arguments對象
  • 不能通過new關鍵字調用,同樣也沒有new.target值和原型

簡單講一講ES6的一些新特性

  • 塊級作用域:ES5隻有全局作用域和函數作用域,塊級作用域的好處是不再需要立即執行的函數表達式,循環體中的閉包不再有問題。
  • rest參數:用于擷取函數的多餘參數,這樣就不需要使用arguments對象了。
  • promise:一種異步程式設計的解決方案,比傳統的解決方案回調函數和事件更合理強大

    子產品化:其子產品功能主要有兩個指令構成,export和import,export指令用于規定子產品的對外接口,import指令用于輸入其他子產品提供的功能

call和apply是用來做什麼?

Call和apply的作用是一模一樣的,隻是傳參的形式有差別而已

1、改變this的指向

2、借用别的對象的方法,

3、調用函數,因為apply,call方法會使函數立即執行

知道private和public嗎

public:public表明該資料成員、成員函數是對所有使用者開放的,所有使用者都可以直接進行調用

private:private表示私有,私有的意思就是除了class自己之外,任何人都不可以直接使用

async和await具體該怎麼用?

(async () = > {
await new promise();
})()
           

繼續閱讀