日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

20道JavaScript经典面试题

發(fā)布時間:2023/12/29 javascript 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 20道JavaScript经典面试题 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

該篇文章整理了一些前端經(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"
console.log(typeof 6); // 'number' console.log(typeof true); // 'boolean' console.log(typeof 'nanjiu'); // 'string' console.log(typeof []); // 'object' []數(shù)組的數(shù)據(jù)類型在 typeof 中被解釋為 object console.log(typeof function(){}); // 'function' console.log(typeof {}); // 'object' console.log(typeof undefined); // 'undefined' console.log(typeof null); // 'object' null 的數(shù)據(jù)類型被 typeof 解釋為 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); // true

constructor(構(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

function F(){} let f = new F()f.constructor === F // true new Number(1).constructor === Number //true new Function().constructor === Function // true true.constructor === Boolean //true ''.constructor === String // true new Date().constructor === Date // true [].constructor === Array

??注意:

  • null和undefined是無效的對象,所以他們不會有constructor屬性
  • 函數(shù)的construct是不穩(wěn)定的,主要是因為開發(fā)者可以重寫prototype,原有的construction引用會丟失,constructor會默認為Object
function F(){} F.prototype = {}let f = new F() f.constructor === F // falseconsole.log(f.constructor) //function 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
Number('1') // 1 Number(true) // 1 Number('123s') // NaN Number({}) //NaN
  • parseInt(string,radix):解析一個字符串并返回指定基數(shù)的十進制整數(shù),radix是2-36之間的整數(shù),表示被解析字符串的基數(shù)。
parseInt('2') //2 parseInt('2',10) // 2 parseInt('2',2) // NaN parseInt('a123') // NaN 如果第一個字符不是數(shù)字或者符號就返回NaN parseInt('123a') // 123
  • parseFloat(string):解析一個參數(shù)并返回一個浮點數(shù)
parseFloat('123a') //123 parseFloat('123a.01') //123 parseFloat('123.01') //123.01 parseFloat('123.01.1') //123.01
  • 隱式轉(zhuǎn)換
let str = '123' let res = str - 1 //122str+1 // '1231' +str+1 // 124

轉(zhuǎn)換為字符串

  • .toString() ??注意:null,undefined不能調(diào)用
Number(123).toString() //'123' [].toString() //'' true.toString() //'true'
  • String() 都能轉(zhuǎn)
String(123) //'123' String(true) //'true' String([]) //'' String(null) //'null' String(undefined) //'undefined' String({}) //'[object Object]'
  • 隱式轉(zhuǎn)換:當+兩邊有一個是字符串,另一個是其它類型時,會先把其它類型轉(zhuǎn)換為字符串再進行字符串拼接,返回字符串
let a = 1 a+'' // '1'

轉(zhuǎn)換為布爾值

0, ‘’(空字符串), null, undefined, NaN會轉(zhuǎn)成false,其它都是true

  • Boolean()
Boolean('') //false Boolean(0) //false Boolean(1) //true Boolean(null) //false Boolean(undefined) //false Boolean(NaN) //false Boolean({}) //true Boolean([]) //true
  • 條件語句
let a if(a) {//... //這里a為undefined,會轉(zhuǎn)為false,所以該條件語句內(nèi)部不會執(zhí)行 }
  • 隱式轉(zhuǎn)換 !!
let str = '111' console.log(!!str) // true

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 是對象的類型。
({}).valueOf() //{} ({}).toString() //'[object Object]' [].valueOf() //[] [].toString() //''

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綁定.

  • 函數(shù)是否在 new 中調(diào)用(new綁定),如果是,那么 this 綁定的是new中新創(chuàng)建的對象。
  • 函數(shù)是否通過 call,apply 調(diào)用,或者使用了 bind (即硬綁定),如果是,那么this綁定的就是指定的對象。
  • 函數(shù)是否在某個上下文對象中調(diào)用(隱式綁定),如果是的話,this 綁定的是那個上下文對象。一般是 obj.foo()
  • 如果以上都不是,那么使用默認綁定。如果在嚴格模式下,則綁定到 undefined,否則綁定到全局對象。
  • 如果把 null 或者 undefined 作為 this 的綁定對象傳入 call、apply 或者 bind, 這些值在調(diào)用時會被忽略,實際應(yīng)用的是默認綁定規(guī)則。
  • 箭頭函數(shù)沒有自己的 this, 它的this繼承于上一層代碼塊的this。
  • 詳細知識可以看我之前的文章: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 5

    forEach

    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ù)。

    事件傳播(三個階段)

  • 捕獲階段–事件從 window 開始,然后向下到每個元素,直到到達目標元素事件或event.target。
  • 目標階段–事件已達到目標元素。
  • 冒泡階段–事件從目標元素冒泡,然后上升到每個元素,直到到達 window。
  • 事件捕獲

    當事件發(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)點

  • 可以從內(nèi)部函數(shù)訪問外部函數(shù)的作用域中的變量,且訪問到的變量長期駐扎在內(nèi)存中,可供之后使用
  • 避免變量污染全局
  • 把變量存到獨立的作用域,作為私有成員存在
  • 缺點

  • 對內(nèi)存消耗有負面影響。因內(nèi)部函數(shù)保存了對外部變量的引用,導致無法被垃圾回收,增大內(nèi)存使用量,所以使用不當會導致內(nèi)存泄漏
  • 對處理速度具有負面影響。閉包的層級決定了引用的外部變量在查找時經(jīng)過的作用域鏈長度
  • 可能獲取到意外的值(captured value)
  • 應(yīng)用場景

    • 模塊封裝,防止變量污染全局
    var Person = (function(){var name = '南玖'function Person() {console.log('work for qtt')}Person.prototype.work = function() {}return Person })()
    • 循環(huán)體中創(chuàng)建閉包,保存變量
    for(var i=0;i<5;i++){(function(j){setTimeOut(() => {console.log(j)},1000)})(i) }

    推薦閱讀: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。
    '1' == 1 // true '1' === 1 // false NaN == NaN //false +0 == -0 //true +0 === -0 // true Object.is(+0,-0) //false Object.is(NaN,NaN) //true

    17.call與apply、bind的區(qū)別?

    實際上call與apply的功能是相同的,只是兩者的傳參方式不一樣,而bind傳參方式與call相同,但它不會立即執(zhí)行,而是返回這個改變了this指向的函數(shù)。

    推薦閱讀:this指向與call,apply,bind

    18.說說你了解哪些前端本地存儲?

    推薦閱讀:這一次帶你徹底了解前端本地存儲

    19.說說JavaScript數(shù)組常用方法

    向數(shù)組添加元素的方法:
  • push:向數(shù)組的末尾追加 返回值是添加數(shù)據(jù)后數(shù)組的新長度,改變原有數(shù)組
  • unshift:向數(shù)組的開頭添加 返回值是添加數(shù)據(jù)后數(shù)組的新長度,改變原有數(shù)組
  • splice:向數(shù)組的指定index處插入 返回的是被刪除掉的元素的集合,會改變原有數(shù)組
  • 向數(shù)組刪除元素的方法:
  • pop():從尾部刪除一個元素 返回被刪除掉的元素,改變原有數(shù)組
  • shift():從頭部刪除一個元素 返回被刪除掉的元素,改變原有數(shù)組
  • splice:在index處刪除howmany個元素 返回的是被刪除掉的元素的集合,會改變原有數(shù)組
  • 數(shù)組排序的方法:
  • reverse():反轉(zhuǎn),倒置 改變原有數(shù)組
  • sort():按指定規(guī)則排序 改變原有數(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) //false

    some()
    對數(shù)組中的每一運行給定的函數(shù),如果該函數(shù)有一項返回true,就返回true,所有項返回false才返回false

    var arr2 = [10,20,32,45,36,94,75] var result2 = arr2.some((item,index,arr)=>{return item<10 }) console.log(result2) //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)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。