javascript
20道JavaScript经典面试题
該篇文章整理了一些前端經(jīng)典面試題,附帶詳解,涉及到JavaScript多方面知識點,滿滿都是干貨~建議收藏閱讀
前言
如果這篇文章有幫助到你,??關(guān)注+點贊??鼓勵一下作者,文章公眾號首發(fā),關(guān)注 前端南玖 第一時間獲取最新的文章~
1.說一說JavaScript的數(shù)據(jù)類型以及存儲方式
JavaScript一共有8種數(shù)據(jù)類型
其中有7種基本數(shù)據(jù)類型:
ES5的5種:Null,undefined,Boolean,Number,String,
ES6新增:Symbol 表示獨一無二的值
ES10新增:BigInt 表示任意大的整數(shù)
一種引用數(shù)據(jù)類型:
Object(本質(zhì)上是由一組無序的鍵值對組成)
包含function,Array,Date等。JavaScript不支持創(chuàng)建任何自定義類型的數(shù)據(jù),也就是說JavaScript中所有值的類型都是上面8中之一。
存儲方式
- 基本數(shù)據(jù)類型:直接存儲在棧內(nèi)存中,占據(jù)空間小,大小固定,屬于被頻繁使用的數(shù)據(jù)。
- 引用數(shù)據(jù)類型:同時存儲在棧內(nèi)存與堆內(nèi)存中,占據(jù)空間大,大小不固定。引用數(shù)據(jù)類型將指針存在棧中,將值存在堆中。當我們把對象值賦值給另外一個變量時,復(fù)制的是對象的指針,指向同一塊內(nèi)存地址。
null 與 undefined的異同
相同點:
- Undefined 和 Null 都是基本數(shù)據(jù)類型,這兩個基本數(shù)據(jù)類型分別都只有一個值,就是 undefined 和 null
不同點:
-
undefined 代表的含義是未定義, null 代表的含義是空對象。
-
typeof null 返回’object’,typeof undefined 返回’undefined’
- null == undefined // true null === undefined // false
-
其實 null 不是對象,雖然 typeof null 會輸出 object,但是這只是 JS 存在的一個悠久 Bug。在 JS 的最初版本中使用的是 32 位系統(tǒng),為了性能考慮使用低位存儲變量的類型信息,000 開頭代表是對象,然而 null 表示為全零,所以將它錯誤的判斷為 object 。雖然現(xiàn)在的內(nèi)部類型判斷代碼已經(jīng)改變了,但是對于這個 Bug 卻是一直流傳下來。
2.說說JavaScript中判斷數(shù)據(jù)類型的幾種方法
typeof
- typeof一般用來判斷基本數(shù)據(jù)類型,除了判斷null會輸出"object",其它都是正確的
- typeof判斷引用數(shù)據(jù)類型時,除了判斷函數(shù)會輸出"function",其它都是輸出"object"
對于引用數(shù)據(jù)類型的判斷,使用typeof并不準確,所以可以使用instanceof來判斷引用數(shù)據(jù)類型
instanceof
Instanceof 可以準確的判斷引用數(shù)據(jù)類型,它的原理是檢測構(gòu)造函數(shù)的prototype屬性是否在某個實例對象的原型鏈上
原型知識點具體可以看我之前的文章:你一定要懂的JavaScript之原型與原型鏈
語法:
object instanceof constructorconsole.log(6 instanceof Number); // false console.log(true instanceof Boolean); // false console.log('nanjiu' instanceof String); // false console.log([] instanceof Array); // true console.log(function(){} instanceof Function); // true console.log({} instanceof Object); // trueconstructor(構(gòu)造函數(shù))
當一個函數(shù)被定義時,JS引擎會為函數(shù)添加prototype屬性,然后在prototype屬性上添加一個constructor屬性,并讓其指向該函數(shù)。
當執(zhí)行 let f = new F()時,F被當成了構(gòu)造函數(shù),f是F的實例對象,此時F原型上的constructor屬性傳遞到了f上,所以f.constructor===F
??注意:
- null和undefined是無效的對象,所以他們不會有constructor屬性
- 函數(shù)的construct是不穩(wěn)定的,主要是因為開發(fā)者可以重寫prototype,原有的construction引用會丟失,constructor會默認為Object
為什么會變成Object?
因為prototype被重新賦值的是一個{},{}是new Object()的字面量,因此 new Object()會將 Object 原型上的constructor 傳遞給 { },也就是 Object 本身。
因此,為了規(guī)范開發(fā),在重寫對象原型時一般都需要重新給 constructor 賦值,以保證對象實例的類型不被篡改。
Object.prototype.toString.call()
toString() 是 Object 的原型方法,調(diào)用該方法,默認返回當前對象的 [[Class]] 。這是一個內(nèi)部屬性,其格式為 [object Xxx] ,其中 Xxx 就是對象的類型。
對于 Object 對象,直接調(diào)用 toString() 就能返回 [object Object] 。而對于其他對象,則需要通過 call / apply 來調(diào)用才能返回正確的類型信息。
Object.prototype.toString.call('') ; // [object String] Object.prototype.toString.call(1) ; // [object Number] Object.prototype.toString.call(true) ; // [object Boolean] Object.prototype.toString.call(Symbol()); //[object Symbol] Object.prototype.toString.call(undefined) ; // [object Undefined] Object.prototype.toString.call(null) ; // [object Null] Object.prototype.toString.call(new Function()) ; // [object Function] Object.prototype.toString.call(new Date()) ; // [object Date] Object.prototype.toString.call([]) ; // [object Array] Object.prototype.toString.call(new RegExp()) ; // [object RegExp] Object.prototype.toString.call(new Error()) ; // [object Error] Object.prototype.toString.call(document) ; // [object HTMLDocument] Object.prototype.toString.call(window) ; //[object global] window 是全局對象 global 的引用3.js數(shù)據(jù)類型轉(zhuǎn)換
在JavaScript中類型轉(zhuǎn)換有三種情況:
- 轉(zhuǎn)換為數(shù)字(調(diào)用Number(),parseInt(),parseFloat()方法)
- 轉(zhuǎn)換為字符串(調(diào)用.toString()或String()方法)
- 轉(zhuǎn)換為布爾值(調(diào)用Boolean()方法)
null、undefined沒有.toString方法
轉(zhuǎn)換為數(shù)字
- Number():可以把任意值轉(zhuǎn)換成數(shù)字,如果要轉(zhuǎn)換的字符串中有不是數(shù)字的值,則會返回NaN
- parseInt(string,radix):解析一個字符串并返回指定基數(shù)的十進制整數(shù),radix是2-36之間的整數(shù),表示被解析字符串的基數(shù)。
- parseFloat(string):解析一個參數(shù)并返回一個浮點數(shù)
- 隱式轉(zhuǎn)換
轉(zhuǎn)換為字符串
- .toString() ??注意:null,undefined不能調(diào)用
- String() 都能轉(zhuǎn)
- 隱式轉(zhuǎn)換:當+兩邊有一個是字符串,另一個是其它類型時,會先把其它類型轉(zhuǎn)換為字符串再進行字符串拼接,返回字符串
轉(zhuǎn)換為布爾值
0, ‘’(空字符串), null, undefined, NaN會轉(zhuǎn)成false,其它都是true
- Boolean()
- 條件語句
- 隱式轉(zhuǎn)換 !!
4.{}和[]的valueOf和toString的返回結(jié)果?
- valueOf:返回指定對象的原始值
| Array | 返回數(shù)組對象本身。 |
| Boolean | 布爾值。 |
| Date | 存儲的時間是從 1970 年 1 月 1 日午夜開始計的毫秒數(shù) UTC。 |
| Function | 函數(shù)本身。 |
| Number | 數(shù)字值。 |
| Object | 對象本身。這是默認情況。 |
| String | 字符串值。 |
| Math 和 Error 對象沒有 valueOf 方法。 |
- toString:返回一個表示對象的字符串。默認情況下,toString() 方法被每個 Object 對象繼承。如果此方法在自定義對象中未被覆蓋,toString() 返回 “[object type]”,其中 type 是對象的類型。
5.let,const,var的區(qū)別?
- 變量提升:let,const定義的變量不會出現(xiàn)變量提升,而var會
- 塊級作用域:let,const 是塊作用域,即其在整個大括號 {} 之內(nèi)可見,var:只有全局作用域和函數(shù)作用域概念,沒有塊級作用域的概念。
- 重復(fù)聲明:同一作用域下let,const聲明的變量不允許重復(fù)聲明,而var可以
- 暫時性死區(qū):let,const聲明的變量不能在聲明之前使用,而var可以
- const 聲明的是一個只讀的常量,不允許修改
6.JavaScript作用域與作用域鏈
作用域:
簡單來說,作用域是指程序中定義變量的區(qū)域,它決定了當前執(zhí)行代碼對變量的訪問權(quán)限
作用域鏈:
當可執(zhí)行代碼內(nèi)部訪問變量時,會先查找當前作用域下有無該變量,有則立即返回,沒有的話則會去父級作用域中查找…一直找到全局作用域。我們把這種作用域的嵌套機制稱為作用域鏈
詳細知識可以看我之前的文章:JavaScript深入之作用域與閉包
7.如何正確的判斷this指向?
this的綁定規(guī)則有四種:默認綁定,隱式綁定,顯式綁定,new綁定.
詳細知識可以看我之前的文章:this指向與call,apply,bind
8.for…of,for…in,forEach,map的區(qū)別?
for…of(不能遍歷對象)
在可迭代對象(具有 iterator 接口)(Array,Map,Set,String,arguments)上創(chuàng)建一個迭代循環(huán),調(diào)用自定義迭代鉤子,并為每個不同屬性的值執(zhí)行語句,不能遍歷對象
let arr=["前端","南玖","ssss"];for (let item of arr){console.log(item)} //前端 南玖 ssss//遍歷對象 let person={name:"南玖",age:18,city:"上海"} for (let item of person){console.log(item) } // 我們發(fā)現(xiàn)它是不可以的 我們可以搭配Object.keys使用 for(let item of Object.keys(person)){console.log(person[item]) } // 南玖 18 上海for…in
for…in循環(huán):遍歷對象自身的和繼承的可枚舉的屬性, 不能直接獲取屬性值。可以中斷循環(huán)。
let person={name:"南玖",age:18,city:"上海"}let text=""for (let i in person){text+=person[i]}// 輸出:南玖18上海//其次在嘗試一些數(shù)組let arry=[1,2,3,4,5]for (let i in arry){console.log(arry[i])} //1 2 3 4 5forEach
forEach: 只能遍歷數(shù)組,不能中斷,沒有返回值(或認為返回值是undefined)。
let arr=[1,2,3]; const res = arr.forEach(item=>{console.log(item*3) }) // 3 6 9 console.log(res) //undefined console.log(arr) // [1,2,3]map
map: 只能遍歷數(shù)組,不能中斷,返回值是修改后的數(shù)組。
let arr=[1,2,3]; const res = arr.map(item=>{return res+1 }) console.log(res) //[2,3,4] console.log(arr) // [1,2,3]總結(jié)
- forEach 遍歷列表值,不能使用 break 語句或使用 return 語句
- for in 遍歷對象鍵值(key),或者數(shù)組下標,不推薦循環(huán)一個數(shù)組
- for of 遍歷列表值,允許遍歷 Arrays(數(shù)組), Strings(字符串), Maps(映射), Sets(集合)等可迭代的數(shù)據(jù)結(jié)構(gòu)等.在 ES6 中引入的 for of 循環(huán),以替代 for in 和 forEach() ,并支持新的迭代協(xié)議。
- for in循環(huán)出的是key,for of循環(huán)出的是value;
- for of是ES6新引入的特性。修復(fù)了ES5的for in的不足;
- for of不能循環(huán)普通的對象,需要通過和Object.keys()搭配使用。
9.說說你對原型鏈的理解?
每個函數(shù)(類)天生自帶一個屬性prototype,屬性值是一個對象,里面存儲了當前類供實例使用的屬性和方法 「(顯示原型)」
在瀏覽器默認給原型開辟的堆內(nèi)存中有一個constructor屬性:存儲的是當前類本身(??注意:自己開辟的堆內(nèi)存中默認沒有constructor屬性,需要自己手動添加)「(構(gòu)造函數(shù))」
每個對象都有一個__proto__屬性,這個屬性指向當前實例所屬類的原型(不確定所屬類,都指向Object.prototype)「(隱式原型)」
當你試圖獲取一個對象的某個屬性時,如果這個對象本身沒有這個屬性,那么它會去它的隱式原型__proto__(也就是它的構(gòu)造函數(shù)的顯示原型prototype)中查找。「(原型鏈)」
詳細知識可以看我之前的文章:你一定要懂的JavaScript之原型與原型鏈
10.說一說三種事件模型?
事件模型
DOM0級模型: ,這種模型不會傳播,所以沒有事件流的概念,但是現(xiàn)在有的瀏覽器支持以冒泡的方式實現(xiàn),它可以在網(wǎng)頁中直接定義監(jiān)聽函數(shù),也可以通過 js屬性來指定監(jiān)聽函數(shù)。這種方式是所有瀏覽器都兼容的。
IE 事件模型: 在該事件模型中,一次事件共有兩個過程,事件處理階段,和事件冒泡階段。事件處理階段會首先執(zhí)行目標元素綁定的監(jiān)聽事件。然后是事件冒泡階段,冒泡指的是事件從目標元素冒泡到 document,依次檢查經(jīng)過的節(jié)點是否綁定了事件監(jiān)聽函數(shù),如果有則執(zhí)行。這種模型通過 attachEvent 來添加監(jiān)聽函數(shù),可以添加多個監(jiān)聽函數(shù),會按順序依次執(zhí)行。
DOM2 級事件模型: 在該事件模型中,一次事件共有三個過程,第一個過程是事件捕獲階段。捕獲指的是事件從 document 一直向下傳播到目標元素,依次檢查經(jīng)過的節(jié)點是否綁定了事件監(jiān)聽函數(shù),如果有則執(zhí)行。后面兩個階段和 IE 事件模型的兩個階段相同。這種事件模型,事件綁定的函數(shù)是 addEventListener,其中第三個參數(shù)可以指定事件是否在捕獲階段執(zhí)行。
事件委托
事件委托指的是把一個元素的事件委托到另外一個元素上。一般來講,會把一個或者一組元素的事件委托到它的父層或者更外層元素上,真正綁定事件的是外層元素,當事件響應(yīng)到需要綁定的元素上時,會通過事件冒泡機制從而觸發(fā)它的外層元素的綁定事件上,然后在外層元素上去執(zhí)行函數(shù)。
事件傳播(三個階段)
事件捕獲
當事件發(fā)生在 DOM 元素上時,該事件并不完全發(fā)生在那個元素上。在捕獲階段,事件從window開始,一直到觸發(fā)事件的元素。window----> document----> html----> body ---->目標元素
事件冒泡
事件冒泡剛好與事件捕獲相反,當前元素---->body ----> html---->document ---->window。當事件發(fā)生在DOM元素上時,該事件并不完全發(fā)生在那個元素上。在冒泡階段,事件冒泡,或者事件發(fā)生在它的父代,祖父母,祖父母的父代,直到到達window為止。
如何阻止事件冒泡
w3c的方法是e.stopPropagation(),IE則是使用e.cancelBubble = true。例如:
window.event?window.event.cancelBubble = true : e.stopPropagation();return false也可以阻止冒泡。
11.JS延遲加載的方式
JavaScript會阻塞DOM的解析,因此也就會阻塞DOM的加載。所以有時候我們希望延遲JS的加載來提高頁面的加載速度。
- 把JS放在頁面的最底部
- script標簽的defer屬性:腳本會立即下載但延遲到整個頁面加載完畢再執(zhí)行。該屬性對于內(nèi)聯(lián)腳本無作用 (即沒有 「src」 屬性的腳本)。
- Async是在外部JS加載完成后,瀏覽器空閑時,Load事件觸發(fā)前執(zhí)行,標記為async的腳本并不保證按照指定他們的先后順序執(zhí)行,該屬性對于內(nèi)聯(lián)腳本無作用 (即沒有 「src」 屬性的腳本)。
- 動態(tài)創(chuàng)建script標簽,監(jiān)聽dom加載完畢再引入js文件
12.說說什么是模塊化開發(fā)?
模塊化的開發(fā)方式可以提高代碼復(fù)用率,方便進行代碼的管理。通常一個文件就是一個模塊,有自己的作用域,只向外暴露特定的變量和函數(shù)。
幾種模塊化方案
-
第一種是CommonJS 方案,它通過 require 來引入模塊,通過 module.exports 定義模塊的輸出接口。
-
第二種是 AMD 方案,這種方案采用異步加載的方式來加載模塊,模塊的加載不影響后面語句的執(zhí)行,所有依賴這個模塊的語句都定義在一個回調(diào)函數(shù)里,等到加載完成后再執(zhí)行回調(diào)函數(shù)。require.js 實現(xiàn)了 AMD 規(guī)范。
-
第三種是 CMD 方案,這種方案和 AMD 方案都是為了解決異步模塊加載的問題,sea.js 實現(xiàn)了 CMD 規(guī)范。它和require.js的區(qū)別在于模塊定義時對依賴的處理不同和對依賴模塊的執(zhí)行時機的處理不同。
-
第四種方案是ES6 提出的方案,使用 import 和 export 的形式來導入導出模塊。
CommonJS
Node.js是commonJS規(guī)范的主要踐行者。這種模塊加載方案是服務(wù)器端的解決方案,它是以同步的方式來引入模塊的,因為在服務(wù)端文件都存儲在本地磁盤,所以讀取非常快,所以以同步的方式加載沒有問題。但如果是在瀏覽器端,由于模塊的加載是使用網(wǎng)絡(luò)請求,因此使用異步加載的方式更加合適。
// 定義模塊a.js var title = '前端'; function say(name, age) {console.log(`我是${name},今年${age}歲,歡迎關(guān)注我~`); } module.exports = { //在這里寫上需要向外暴露的函數(shù)、變量say: say,title: title }// 引用自定義的模塊時,參數(shù)包含路徑,可省略.js var a = require('./a'); a.say('南玖', 18); //我是南玖,今年18歲,歡迎關(guān)注我~AMD與require.js
AMD規(guī)范采用異步方式加載模塊,模塊的加載不影響它后面語句的運行。所有依賴這個模塊的語句,都定義在一個回調(diào)函數(shù)中,等到加載完成之后,這個回調(diào)函數(shù)才會運行。這里介紹用require.js實現(xiàn)AMD規(guī)范的模塊化:用require.config()指定引用路徑等,用define()定義模塊,用require()加載模塊。
CMD與sea.js
CMD是另一種js模塊化方案,它與AMD很類似,不同點在于:AMD 推崇依賴前置、提前執(zhí)行,CMD推崇依賴就近、延遲執(zhí)行。此規(guī)范其實是在sea.js推廣過程中產(chǎn)生的。
ES6 Module
ES6 在語言標準的層面上,實現(xiàn)了模塊功能,而且實現(xiàn)得相當簡單,旨在成為瀏覽器和服務(wù)器通用的模塊解決方案。其模塊功能主要由兩個命令構(gòu)成:export和import。export命令用于規(guī)定模塊的對外接口,import命令用于輸入其他模塊提供的功能。
// 定義模塊a.js var title = '前端'; function say(name, age) {console.log(`我是${name},今年${age}歲,歡迎關(guān)注我~`); } export { //在這里寫上需要向外暴露的函數(shù)、變量say,title }// 引用自定義的模塊時,參數(shù)包含路徑,可省略.js import {say,title} from "./a" say('南玖', 18); //我是南玖,今年18歲,歡迎關(guān)注我~CommonJS 與 ES6 Module 的差異
CommonJS 模塊輸出的是一個值的拷貝,ES6 模塊輸出的是值的引用。
- CommonJS 模塊輸出的是值的拷貝,也就是說,一旦輸出一個值,模塊內(nèi)部的變化就影響不到這個值。
- ES6 模塊的運行機制與 CommonJS 不一樣。JS 引擎對腳本靜態(tài)分析的時候,遇到模塊加載命令import,就會生成一個只讀引用。等到腳本真正執(zhí)行時,再根據(jù)這個只讀引用,到被加載的那個模塊里面去取值。換句話說,ES6 的import有點像 Unix 系統(tǒng)的“符號連接”,原始值變了,import加載的值也會跟著變。因此,ES6 模塊是動態(tài)引用,并且不會緩存值,模塊里面的變量綁定其所在的模塊。
CommonJS 模塊是運行時加載,ES6 模塊是編譯時輸出接口。
- 運行時加載: CommonJS 模塊就是對象;即在輸入時是先加載整個模塊,生成一個對象,然后再從這個對象上面讀取方法,這種加載稱為“運行時加載”。
- 編譯時加載: ES6 模塊不是對象,而是通過 export 命令顯式指定輸出的代碼,import時采用靜態(tài)命令的形式。即在import時可以指定加載某個輸出值,而不是加載整個模塊,這種加載稱為“編譯時加載”。
CommonJS 加載的是一個對象(即module.exports屬性),該對象只有在腳本運行完才會生成。而 ES6 模塊不是對象,它的對外接口只是一種靜態(tài)定義,在代碼靜態(tài)解析階段就會生成。
推薦閱讀前端模塊化理解
13.說說JS的運行機制
推薦閱讀探索JavaScript執(zhí)行機制
14.如何在JavaScript中比較兩個對象?
對于兩個非基本類型的數(shù)據(jù),我們用==或===都指示檢查他們的引用是否相等,并不會檢查實際引用指向的值是否相等。
例如,默認情況下,數(shù)組將被強制轉(zhuǎn)換成字符串,并使用逗號連接所有元素
let a = [1,2,3] let b = [1,2,3] let c = "1,2,3" a == b // false a == c // true b == c // true一般比較兩個對象會采用遞歸來比較
15.說說你對閉包的理解,以及它的原理和應(yīng)用場景?
一個函數(shù)和對其周圍(詞法環(huán)境)的引用捆綁在一起(或者說函數(shù)被引用包圍),這樣一個組合就是閉包(「closure」)
閉包原理
函數(shù)執(zhí)行分成兩個階段(預(yù)編譯階段和執(zhí)行階段)。
- 在預(yù)編譯階段,如果發(fā)現(xiàn)內(nèi)部函數(shù)使用了外部函數(shù)的變量,則會在內(nèi)存中創(chuàng)建一個“閉包”對象并保存對應(yīng)變量值,如果已存在“閉包”,則只需要增加對應(yīng)屬性值即可。
- 執(zhí)行完后,函數(shù)執(zhí)行上下文會被銷毀,函數(shù)對“閉包”對象的引用也會被銷毀,但其內(nèi)部函數(shù)還持用該“閉包”的引用,所以內(nèi)部函數(shù)可以繼續(xù)使用“外部函數(shù)”中的變量
利用了函數(shù)作用域鏈的特性,一個函數(shù)內(nèi)部定義的函數(shù)會將包含外部函數(shù)的活動對象添加到它的作用域鏈中,函數(shù)執(zhí)行完畢,其執(zhí)行作用域鏈銷毀,但因內(nèi)部函數(shù)的作用域鏈仍然在引用這個活動對象,所以其活動對象不會被銷毀,直到內(nèi)部函數(shù)被燒毀后才被銷毀。
優(yōu)點
缺點
應(yīng)用場景
- 模塊封裝,防止變量污染全局
- 循環(huán)體中創(chuàng)建閉包,保存變量
推薦閱讀:JavaScript深入之作用域與閉包
16.Object.is()與比較操作符==、===的區(qū)別?
- ==會先進行類型轉(zhuǎn)換再比較
- ===比較時不會進行類型轉(zhuǎn)換,類型不同則直接返回false
- Object.is()在===基礎(chǔ)上特別處理了NaN,-0,+0,保證-0與+0不相等,但NaN與NaN相等
==操作符的強制類型轉(zhuǎn)換規(guī)則
- 字符串和數(shù)字之間的相等比較,將字符串轉(zhuǎn)換為數(shù)字之后再進行比較。
- 其他類型和布爾類型之間的相等比較,先將布爾值轉(zhuǎn)換為數(shù)字后,再應(yīng)用其他規(guī)則進行比較。
- null 和 undefined 之間的相等比較,結(jié)果為真。其他值和它們進行比較都返回假值。
- 對象和非對象之間的相等比較,對象先調(diào)用 ToPrimitive 抽象操作后,再進行比較。
- 如果一個操作值為 NaN ,則相等比較返回 false( NaN 本身也不等于 NaN )。
- 如果兩個操作值都是對象,則比較它們是不是指向同一個對象。如果兩個操作數(shù)都指向同一個對象,則相等操作符返回true,否則,返回 false。
17.call與apply、bind的區(qū)別?
實際上call與apply的功能是相同的,只是兩者的傳參方式不一樣,而bind傳參方式與call相同,但它不會立即執(zhí)行,而是返回這個改變了this指向的函數(shù)。
推薦閱讀:this指向與call,apply,bind
18.說說你了解哪些前端本地存儲?
推薦閱讀:這一次帶你徹底了解前端本地存儲
19.說說JavaScript數(shù)組常用方法
向數(shù)組添加元素的方法:
向數(shù)組刪除元素的方法:
數(shù)組排序的方法:
數(shù)組迭代方法
參數(shù): 每一項上運行的函數(shù), 運行該函數(shù)的作用域?qū)ο?#xff08;可選)
every()
對數(shù)組中的每一運行給定的函數(shù),如果該函數(shù)對每一項都返回true,則該函數(shù)返回true
var arr = [10,30,25,64,18,3,9] var result = arr.every((item,index,arr)=>{return item>3 }) console.log(result) //falsesome()
對數(shù)組中的每一運行給定的函數(shù),如果該函數(shù)有一項返回true,就返回true,所有項返回false才返回false
filter()
對數(shù)組中的每一運行給定的函數(shù),會返回滿足該函數(shù)的項組成的數(shù)組
// filter 返回滿足要求的數(shù)組項組成的新數(shù)組 var arr3 = [3,6,7,12,20,64,35] var result3 = arr3.filter((item,index,arr)=>{return item > 3 }) console.log(result3) //[6,7,12,20,64,35]map()
對數(shù)組中的每一元素運行給定的函數(shù),返回每次函數(shù)調(diào)用的結(jié)果組成的數(shù)組
// map 返回每次函數(shù)調(diào)用的結(jié)果組成的數(shù)組 var arr4 = [1,2,3,4,5,6] var result4 = arr4.map((item,index,arr)=>{return `<span>${item}</span>` }) console.log(result4) /*[ '<span>1</span>','<span>2</span>','<span>3</span>','<span>4</span>','<span>5</span>','<span>6</span>' ]*/forEach()
對數(shù)組中的每一元素運行給定的函數(shù),沒有返回值,常用來遍歷元素
// forEach var arr5 = [10,20,30] var result5 = arr5.forEach((item,index,arr)=>{console.log(item) }) console.log(result5) /* 10 20 30 undefined 該方法沒有返回值 */reduce()
reduce()方法對數(shù)組中的每個元素執(zhí)行一個由你提供的reducer函數(shù)(升序執(zhí)行),將其結(jié)果匯總為單個返回值
const array = [1,2,3,4] const reducer = (accumulator, currentValue) => accumulator + currentValue;// 1 + 2 + 3 + 4 console.log(array1.reduce(reducer));20.JavaScript為什么要進行變量提升,它導致了什么問題?
變量提升的表現(xiàn)是,在變量或函數(shù)聲明之前訪問變量或調(diào)用函數(shù)而不會報錯。
原因
JavaScript引擎在代碼執(zhí)行前有一個解析的過程(預(yù)編譯),創(chuàng)建執(zhí)行上線文,初始化一些代碼執(zhí)行時需要用到的對象。
當訪問一個變量時,會到當前執(zhí)行上下文中的作用域鏈中去查找,而作用域鏈的首端指向的是當前執(zhí)行上下文的變量對象,這個變量對象是執(zhí)行上下文的一個屬性,它包含了函數(shù)的形參、所有的函數(shù)和變量聲明,這個對象的是在代碼解析的時候創(chuàng)建的。
首先要知道,JS在拿到一個變量或者一個函數(shù)的時候,會有兩步操作,即解析和執(zhí)行。
-
在解析階段
JS會檢查語法,并對函數(shù)進行預(yù)編譯。解析的時候會先創(chuàng)建一個全局執(zhí)行上下文環(huán)境,先把代碼中即將執(zhí)行的變量、函數(shù)聲明都拿出來,變量先賦值為undefined,函數(shù)先聲明好可使用。在一個函數(shù)執(zhí)行之前,也會創(chuàng)建一個函數(shù)執(zhí)行上下文環(huán)境,跟全局執(zhí)行上下文類似,不過函數(shù)執(zhí)行上下文會多出this、arguments和函數(shù)的參數(shù)。
- 全局上下文:變量定義,函數(shù)聲明
- 函數(shù)上下文:變量定義,函數(shù)聲明,this,arguments
-
在執(zhí)行階段,就是按照代碼的順序依次執(zhí)行。
那為什么會進行變量提升呢?主要有以下兩個原因:
- 提高性能
- 容錯性更好
(1)提高性能 在JS代碼執(zhí)行之前,會進行語法檢查和預(yù)編譯,并且這一操作只進行一次。這么做就是為了提高性能,如果沒有這一步,那么每次執(zhí)行代碼前都必須重新解析一遍該變量(函數(shù)),而這是沒有必要的,因為變量(函數(shù))的代碼并不會改變,解析一遍就夠了。
在解析的過程中,還會為函數(shù)生成預(yù)編譯代碼。在預(yù)編譯時,會統(tǒng)計聲明了哪些變量、創(chuàng)建了哪些函數(shù),并對函數(shù)的代碼進行壓縮,去除注釋、不必要的空白等。這樣做的好處就是每次執(zhí)行函數(shù)時都可以直接為該函數(shù)分配棧空間(不需要再解析一遍去獲取代碼中聲明了哪些變量,創(chuàng)建了哪些函數(shù)),并且因為代碼壓縮的原因,代碼執(zhí)行也更快了。
(2)容錯性更好 變量提升可以在一定程度上提高JS的容錯性,看下面的代碼:
a = 1 var a console.log(a) //1如果沒有變量提升,這段代碼就會報錯
導致的問題
var tmp = new Date();function fn(){console.log(tmp);if(false){var tmp = 'hello nan jiu';} } fn(); // undefined在這個函數(shù)中,原本是要打印出外層的tmp變量,但是因為變量提升的問題,內(nèi)層定義的tmp被提到函數(shù)內(nèi)部的最頂部,相當于覆蓋了外層的tmp,所以打印結(jié)果為undefined。
var tmp = 'hello nan jiu';for (var i = 0; i < tmp.length; i++) {console.log(tmp[i]); } console.log(i); // 13由于遍歷時定義的i會變量提升成為一個全局變量,在函數(shù)結(jié)束之后不會被銷毀,所以打印出來13。
總結(jié)
- 解析和預(yù)編譯過程中的聲明提升可以提高性能,讓函數(shù)可以在執(zhí)行時預(yù)先為變量分配棧空間
- 聲明提升還可以提高JS代碼的容錯性,使一些不規(guī)范的代碼也可以正常執(zhí)行
- 函數(shù)是一等公民,當函數(shù)聲明與變量聲明沖突時,變量提升時函數(shù)優(yōu)先級更高,會忽略同名的變量聲明
覺得文章不錯,可以點個贊呀_ 歡迎關(guān)注南玖~
總結(jié)
以上是生活随笔為你收集整理的20道JavaScript经典面试题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python里的pypi是干什么用的_【
- 下一篇: Antlr4入门(六)实战之JSON