本篇文章給大家整理總結(jié)一些JavaScript 面試題,帶你搞定高頻知識點,希望對大家有所幫助!
【資料圖】
| 區(qū)別 | let | const | var |
|---|---|---|---|
| 重復聲明 | 不能重復聲明,會報SyntaxError錯 | const 定義常量,值不能修改的變量叫做常量,一定要賦初始值,因為不能修改。 | 可以重復聲明 |
| 塊級作用域 | 擁有 | 擁有 | 不擁有 |
| 會不會污染全局變量(掛載在window上) | 不會 | 不會 | 會 |
說明 1.let和const也存在變量提升,只是提升的方式不同
var變量提升:變量的聲明提升到頂部,值為undefinedlet、const變量提升: 變量聲明提升到頂部,只不過將該變量標記為尚未初始化let 和 const存在暫時性死區(qū),代碼執(zhí)行過程中的一段時間內(nèi),在此期間無法使用標識符,也不能引用外層作用域的變量。let answer;function fn(){//如果此時沒有將變量變量提升到這里,answer應該取外層answer的值console.log(answer); //Uncaught ReferenceError: Cannot access "answer" before initializationlet answer=42;}2.var創(chuàng)建的全局變量->全局對象的屬性,let和const在全局作用域聲明的變量->不是全局對象的屬性
3.如果常量是個數(shù)組或?qū)ο?,對其?nèi)部元素修改,不算對常量的修改,不會報錯。常量指向了一個地址,地址不變就不會報錯。
變量聲明升級 通過var定義(聲明)的變量,在定義語句之前的就可以訪問到 但是值是undefined
函數(shù)聲明提升 通過function聲明的函數(shù),在之前就可以直接調(diào)用。值是函數(shù)體
//變量提升先于函數(shù)提升,提升后被函數(shù)聲明function覆蓋,所以就算換了順序也是functionfunction a(){}var a ;console.log(typeof a); //functionvar f1 = function () { console.log(1);}function f1 () { console.log(2);}f1() ; //1//變量提升后var f1;//變量提升function f1(){};//函數(shù)提升f1 = function () { console.log(1);}f1() ;變量提升練習題
理解:一個代碼段所在的區(qū)域,是靜態(tài)的,在編寫代碼時就確定了。 作用:變量綁定在這個作用域內(nèi)有效,隔離變量,不同作用域下同名變量不會有沖突。 作用域分類
全局作用域
函數(shù)作用域
塊級作用域
作用域鏈:多個作用域嵌套,就近選擇,先在自己作用域找,然后去就近的作用域找。
函數(shù)的作用域在聲明的時候就已經(jīng)決定了,與調(diào)用位置無關所以執(zhí)行aaa()的時候先在aaa的作用域里面找,沒有找到a,再去父級作用域window里面找,找到a=10
var a = 10; function aaa() { alert(a);}function bbb() { var a = 20; aaa();}bbb();對當前JavaScript的執(zhí)行環(huán)境的抽象,每當JavaScript開始執(zhí)行的時候,它都在執(zhí)行上下文中運行。
全局執(zhí)行上下文:在執(zhí)行全局代碼前將window確定為全局執(zhí)行上下文對全局數(shù)據(jù)進行預處理
var定義的全局變量 --> undefined,添加為window的屬性
function聲明的全局函數(shù) --> 賦值(函數(shù)體),添加為window的方法
this --> 賦值window
開始執(zhí)行全局代碼
函數(shù)執(zhí)行上下文:在調(diào)用函數(shù),準備執(zhí)行函數(shù)體之前,創(chuàng)建對應的函數(shù)執(zhí)行上下文對象
對局部數(shù)據(jù)進行預處理
形參變量 --> 賦值(實參)–> 添加到函數(shù)執(zhí)行上下文的屬性arguments(形參列表封裝成的偽數(shù)組)–>賦值(實參列表),添加到函數(shù)執(zhí)行上下文的屬性var定義的局部變量–>undefined,添加為函數(shù)執(zhí)行上下文的屬性function聲明的函數(shù)–>賦值(函數(shù)體),添加為函數(shù)執(zhí)行上下文的方法this–>賦值(調(diào)用函數(shù)的對象)開始執(zhí)行函數(shù)體代碼執(zhí)行上下文棧1.在全局代碼執(zhí)行前,JS引擎就會創(chuàng)建一個棧來存儲管理所有的執(zhí)行上下文對象 2.在全局執(zhí)行上下文(window)確定后,將其添加到棧中(壓棧) 3.在函數(shù)執(zhí)行上下文創(chuàng)建后,將其添加到棧中(壓棧) 4.在當前函數(shù)執(zhí)行完成后,將棧頂?shù)膶ο笠瞥?出棧) 5.當所有的代碼執(zhí)行完后,棧中只剩下window
| 作用域 | 執(zhí)行上下文 |
|---|---|
| 定義了幾個函數(shù) + 1 = 幾個作用域 | 執(zhí)行了幾個函數(shù) + 1 = 幾個執(zhí)行上下文 |
| 函數(shù)定義時就確定了,一直存在,不會再變化,是靜態(tài)的 | 全局執(zhí)行上下文環(huán)境實在全局作用域確定之后,js代碼執(zhí)行之前創(chuàng)建的調(diào)用函數(shù)時創(chuàng)建,函數(shù)調(diào)用結(jié)束被釋放,是動態(tài)的 |
var foo = 1;function bar () { console.log(foo); var foo = 10; console.log(foo);}bar();//變量提升后var foo = 1;function bar () {var foo = undefined; console.log(foo); //undefined foo = 10; console.log(foo);//10}bar();let:使用立即執(zhí)行函數(shù)創(chuàng)造出一個塊級作用域
(function(){ var a = 1; console.log("內(nèi)部a:", a);})();const1.使用立即執(zhí)行函數(shù)創(chuàng)造出一個塊級作用域。 2.對于不可變性,可以利用Object.defineProperty將變量掛載在對象上
var __const = function __const(data, value) {this.data = value // 把要定義的data掛載到某個對象,并賦值valueObject.defineProperty(this,data, { // 利用Object.defineProperty的能力劫持當前對象,并修改其屬性描述符enumerable: false,configurable: false,get: function () {return value},set: function (data) {if (data !== value) { // 當要對當前屬性進行賦值時,則拋出錯誤!throw new TypeError("Assignment to constant variable.")} else {return value}}})}//然后和立即執(zhí)行函數(shù)結(jié)合(function(){ var obj = {}_const.call(obj,"a",10)})()//當執(zhí)行完畢后,全局上就不會有obj,也不會有obj.a這個變量,進而實現(xiàn)了塊級作用域的功能function a(){ a.name ="aaa"; return this.name;}var b = { a, name:"bbb", getName:function(){ return this.name; }}var c =b.getName;console.log(a()); //this指向window,window上沒有name,所以輸出undefinedconsole.log(b.a()); //b.a 是function,b調(diào)用a函數(shù),所以this指向b,所以輸出"bbb"console.log(b.getName);//通過b調(diào)用getName,所以getName指向b,所以輸出"bbb"console.log(c());//c是function,this指向window,window上沒有name,所以輸出undefined筆記鏈接
JS數(shù)據(jù)類型有哪些介紹一下Symbol和Bigint如何判斷一個數(shù)據(jù)類型Object.prototype.toString.call() 的缺點?各個方法的原理是什么typeof(NaN) typeof(Null)手寫 instanceof 方法null==undefined 和 null===undefined隱式轉(zhuǎn)換規(guī)則 === 和 == 的區(qū)別map和weakmap區(qū)別map和object區(qū)別for in、for of 區(qū)別,分別對對象和數(shù)組使用問結(jié)果講一下數(shù)組的遍歷方法,filter與map的使用場景,some,every的區(qū)別map的操作原理map和forEach的區(qū)別使用迭代器實現(xiàn)for-of手寫數(shù)組去重手寫數(shù)組扁平化map和filter的區(qū)別數(shù)組的常用方法用reduce實現(xiàn)map| ES5 function類 | ES6 class |
|---|---|
| 可以new可以調(diào)用 | 必須new調(diào)用,不能直接執(zhí)行 |
| function存在變量提升 | class不存在變量提升 |
| static靜態(tài)方法只能通過類調(diào)用,不會出現(xiàn)在實例上 |
apply、call、bind 函數(shù)可以改變 this 的指向。
| 區(qū)別 | call | apply | bind |
|---|---|---|---|
| 調(diào)用函數(shù) | √ | √ | × |
| 參數(shù) | 從第二個參數(shù)開始依次傳遞 | 封裝成數(shù)組傳遞 | 從第二個參數(shù)開始依次傳遞 |
bind函數(shù)的特殊點多次綁定,只指向第一次綁定的obj對象。 多次綁定,一次生效。 原因:返回函數(shù),后續(xù)bind修改的是返回函數(shù)的this
call函數(shù)的實現(xiàn)
//從第二個參數(shù)開始依次傳入,所以接收時使用rest參數(shù)Function.prototype.call=function(obj,...args){obj = obj || window;args = args ? args : [];//給obj新增一個獨一無二的屬性以免覆蓋原有屬性 const key = Symbol()obj[key] = this;const res = obj[key](...args);delete obj[key];return res;}apply函數(shù)的實現(xiàn)
Function.prototype.apply=function(obj,args){obj = obj || window;args = args ? args : [];//給obj新增一個獨一無二的屬性以免覆蓋原有屬性 const key = Symbol()obj[key] = this;const res = obj[key](...args);delete obj[key];return res;}bind函數(shù)的實現(xiàn)
Function.prototype.bind=function(obj,...args){obj = obj || window;args = args ? args : [];return (...args2) => {return this.apply(obj,[...args,...args2])}}箭頭函數(shù)的作用:確保函數(shù)內(nèi)部的this和外部的this是一樣的
箭頭函數(shù)是普通函數(shù)的語法糖,書寫要更加簡潔
| 區(qū)別 | 一般函數(shù) | 箭頭函數(shù) |
|---|---|---|
| this指向 | 調(diào)用時確定 | 定義時確定,沒有自己的this,沿著作用域鏈找父級的this |
| 改變this指向 | call,apply,bind | 不能改變,靜態(tài) |
| arguments | 有 | 沒有,可以用rest參數(shù)代替 |
| 作為構造函數(shù) | √ | × 沒有prototype屬性 |
| 匿名函數(shù) | 可以匿名可以不匿名 | 匿名函數(shù) |
是什么閉包就是在函數(shù)中能夠讀取其他函數(shù)內(nèi)部變量
function foo(){var a=2;function bar(){ //覆蓋foo()內(nèi)部作用域的閉包console.log(a++);}return bar;}var bar = foo(); //foo執(zhí)行創(chuàng)建一個執(zhí)行上下文環(huán)境,由于bar引用了其內(nèi)部變量,也就是bar持有foo本次執(zhí)行上下文的引用,foo本次的執(zhí)行上下文不會被銷魂bar();//2bar();//3var fn = foo(); //foo執(zhí)行創(chuàng)建一個新的執(zhí)行上下文環(huán)境,fn持有了foo本次執(zhí)行上下文的引用fn();//2有什么用1.可以讀取函數(shù)內(nèi)部的變量 2.使函數(shù)的內(nèi)部變量執(zhí)行完后,仍然存活在棧內(nèi)存中(延長了局部變量的生命周期)。
JavaScript閉包就是在另一個作用域中保存了一份它從上一級函數(shù)或者作用域得到的變量,而這些變量是不會隨上一級函數(shù)的執(zhí)行完成而銷毀
常用場景:節(jié)流防抖缺點是什么1.函數(shù)執(zhí)行完后,函數(shù)內(nèi)的局部變量沒有釋放,占用內(nèi)存時間會變長 2.容易造成內(nèi)存泄露怎么解決及時釋放:讓內(nèi)部函數(shù)成為垃圾對象(將閉包手動設置為null)–> 回收閉包
作用是:控制回調(diào)函數(shù)觸發(fā)的頻率,進行性能優(yōu)化 參數(shù): 控制觸發(fā)頻率的回調(diào)函數(shù)和時間wait 輸出: 到時間后,返回callback函數(shù)
節(jié)流:在函數(shù)被頻繁觸發(fā)時, 函數(shù)執(zhí)行一次后,只有大于設定的執(zhí)行周期后才會執(zhí)行第二次。一個時間段,只觸發(fā)一次語法:throttle(callback, wait) 常用場景:比如拖動、滾動和輸入框聯(lián)想
//使用形式,綁定時候throttle函數(shù)就會執(zhí)行,所以this是windowwindow.addEventListener("scroll",throttle(()=>{},500))/*思路需要記錄上一次觸發(fā)的時間,才可以和當前時間比較,是否超過了間隔時間第一次必然立刻觸發(fā)*/function throttle(callback,wait){let pre = new Date();//這里的this是windowreturn function(...args){//這里的this是綁定的DOMconst now = new Date();if(now-pre>=wait){callback.apply(this,args);pre = now;}}}/*使用setTimeout實現(xiàn)第一次需要延遲delay后觸發(fā)*/function throttle(callback,delay){let timer = null;//這里的this是windowreturn function(...args){if(timer){//說明已經(jīng)觸發(fā)了return;}timer = setTimeout(()=>{callback.apply(this,args);timer = null;},delay)}}函數(shù)防抖:指定時間間隔內(nèi)只會執(zhí)行一次任務。如果在等待的過程中再一次觸發(fā)了事件,計時器重新開始計時,直到達到時間后執(zhí)行最后一次的回調(diào) 語法:debounce(callback, wait) 常用場景: 登錄、發(fā)短信等按鈕避免用戶點擊太快,以致于發(fā)送了多次請求,需要防抖。
function debounce(callback,delay){let timer = null;//這里的this是windowreturn function(){if(timer){//說明已經(jīng)觸發(fā)了clearTimeout(timer);}timer = setTimeout(()=>{callback.apply(this,arguments);timer = null;},delay)}}//立即執(zhí)行function debounce(func,delay) { let timeout; return function (...args) { if (timeout) clearTimeout(timeout); const callNow = !timeout; timeout = setTimeout(() => { timeout = null; }, delay) if (callNow) func.apply(this, args) }}筆記鏈接
原型和原型鏈繼承筆記鏈接
內(nèi)容
進程和線程進程的通信方式瀏覽器多進程架構如何實現(xiàn)瀏覽器多標簽之間的通訊H5 Web Workers JS多線程運行瀏覽器的事件循環(huán)機制Node的事件循環(huán)機制node事件循環(huán)代碼輸出題 用于理解代碼輸出題筆記
內(nèi)容
DOM的渲染過程DOM渲染的時機與渲染進程的概述 -瀏覽器的渲染流程CSS、JS、DOM解析和渲染阻塞問題JS加載阻塞DOM渲染問題,怎么解決? - 異步JS,JS三種異步加載的方式script 標簽中的 async 和 defer 屬性DOMContentLoaded和LoadDOM渲染優(yōu)化筆記
內(nèi)容
什么是重繪和回流回流和重繪觸發(fā)的時機優(yōu)化方案GPU加速,如何開啟GPU加速JS優(yōu)化減少重繪和回流的觸發(fā)筆記
內(nèi)容
setTimeout(cb, 0)會立刻執(zhí)行嗎?settimeout定時的時間準確嗎? 為什么不準確? 怎么解決?setTimeout和requestAnimation的區(qū)別requestAnimationFrame講一下你的理解setTimeout實際延遲時間用setTimeout實現(xiàn)setInterval,實現(xiàn)一個隨時停止的版本setTimeout 和 setInterval區(qū)別JS實現(xiàn)動畫的方式requestAnimationFrame與requestIdleCallback分別是什么?requestAnimationFrame的執(zhí)行時機?requestanimationframe回調(diào)函數(shù)中進行大量計算,會阻塞頁面的渲染嗎每隔一秒輸出一個數(shù)字觀察者是軟件設計模式中的一種,但發(fā)布訂閱只是軟件架構中的一種消息范式
觀察者模式
觀察者模式定義對象間的一種一對多的依賴關系,當一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都得到通知并被自動更新。
被依賴的對象叫做subject,依賴的對象叫做觀察者Observer 。被依賴的對象首先需要提供一個添加觀察者方法,以供觀察者調(diào)用。所以還需要維護一個觀察者列表,自身發(fā)生變化后,依次通知觀察者。
//subject 被觀察者class Subject { constructor() { this.observerList = []; } addObserver(observer) { this.observerList.push(observer); } removeObserver(observer) { const index = this.observerList.findIndex(o => o.name === observer.name); this.observerList.splice(index, 1); } notifyObservers(message) { const observers = this.observeList; observers.forEach(observer => observer.notified(message)); }}//Observer 觀察者class Observer { constructor(name, subject) { this.name = name; if (subject) { subject.addObserver(this); } } notified(message) { console.log(this.name, "got message", message); }}//使用const subject = new Subject();const observerA = new Observer("observerA", subject);const observerB = new Observer("observerB");subject.addObserver(observerB);subject.notifyObservers("Hello from subject");subject.removeObserver(observerA);subject.notifyObservers("Hello again");發(fā)布訂閱機制發(fā)布者和訂閱者不直接進行通信,通過事件調(diào)度中心進行管理。發(fā)布者將要發(fā)布的消息交由事件調(diào)度中心管理,訂閱者也是根據(jù)自己的情況,按需訂閱事件調(diào)度中心的消息。
//事件調(diào)度中心class PubSub { constructor() { // 存儲格式: warTask: [], routeTask: [] // {訂閱事件:[回調(diào)1,回調(diào)2...],訂閱事件2:[回調(diào)1,回調(diào)2..]} this.events = {} } // 訂閱方法 訂閱哪個類型type就把對應的回調(diào)函數(shù)放入 subscribe(type, cb) { if (!this.events[type]) { this.events[type] = []; } this.events[type].push(cb); } // 發(fā)布方法 publish(type, ...args) { if (this.events[type]) { this.events[type].forEach(cb => cb(...args)) } }// 取消訂閱方法 的某一個類型的某一個回調(diào) unsubscribe(type, cb) { if (this.events[type]) { const cbIndex = this.events[type].findIndex(e=> e === cb) if (cbIndex != -1) { this.events[type].splice(cbIndex, 1); } } if (this.events[type].length === 0) { delete this.events[type]; } }}//測試let pubsub = new PubSub();//訂閱pubsub.subscribe("warTask", function (taskInfo){ console.log("宗門殿發(fā)布戰(zhàn)斗任務,任務信息:" + taskInfo);})pubsub.subscribe("routeTask", function (taskInfo) { console.log("宗門殿發(fā)布日常任務,任務信息:" + taskInfo);});pubsub.subscribe("allTask", function (taskInfo) { console.log("宗門殿發(fā)布五星任務,任務信息:" + taskInfo);});//發(fā)布pubsub.publish("warTask", "獵殺時刻");pubsub.publish("allTask", "獵殺時刻");pubsub.publish("routeTask", "種樹澆水");pubsub.publish("allTask", "種樹澆水");區(qū)別
| 類型 | 描述 | 特點 |
|---|---|---|
| 觀察者模式 | 觀察者和被觀察者互相知道身份,目標直接將通知分發(fā)到觀察者身上 | 高耦合 |
| 發(fā)布訂閱機制 | 發(fā)布訂閱機制通過事件調(diào)度中心來協(xié)調(diào),訂閱者和發(fā)布者互相不知道身份 | 低耦合 |
筆記鏈接
筆記內(nèi)容
Generator生成器函數(shù)Generator生成器函數(shù)使用上的補充 了解基于Promise對象的簡單自動執(zhí)行器iterator迭代器async/await是什么? 使用場景是什么?await/async與generator函數(shù)的區(qū)別await/async內(nèi)部實現(xiàn)原理 Generator函數(shù)和自動執(zhí)行器async錯誤捕獲方式promise概述promise知識點 了解promise.then、catch、finally的原理與實現(xiàn)Promise.all/Promise.race/Promise.allSettled的原理和實現(xiàn)手寫題:請求五秒未完成則終止promise實現(xiàn)并發(fā)的異步任務調(diào)度器深淺拷貝只是針對引用數(shù)據(jù)類型區(qū)分點: 復制之后的副本進行修改會不會影響到原來的
淺拷貝:修改拷貝以后的數(shù)據(jù)會影響原數(shù)據(jù)。使得原數(shù)據(jù)不安全。(只拷貝一層)深拷貝:修改拷貝以后的數(shù)據(jù)不會影響原數(shù)據(jù),拷貝的時候生成新數(shù)據(jù)。淺拷貝
擴展運算符,適用于數(shù)組/對象
Aarry.prototype.concat(拷貝對象1,拷貝對象2...)數(shù)組的合并方法,將多個數(shù)組或?qū)ο罂截愡M目標數(shù)組,返回新數(shù)組。
Object.assign(目標對象1,拷貝對象1,拷貝對象2.....)對象的合并方法,將拷貝對象拷貝進目標對象
方式一: JSON.parse(JSON.stringify())
JSON.stringify():將JavaScript對象轉(zhuǎn)換為JSON字符串JSON.parse():可以將JSON字符串轉(zhuǎn)為一個對象。問題1: 函數(shù)屬性會丟失,不能克隆方法 問題2: 循環(huán)引用會出錯
//循環(huán)引用:b中引用了c,c中又有bobj = {b:["a","f"],c:{h:20}}obj.b.push(obj.c);obj.c.j = obj.b;b:["a","f",{h:20,j:[]}],c:{h:20,j:["a","f",[]]}function deepClone1(target) { //通過數(shù)組創(chuàng)建JSON格式的字符串 let str = JSON.stringify(target); //將JSON格式的字符串轉(zhuǎn)換為JS數(shù)據(jù) let data = JSON.parse(str); return data;}方式二:遞歸+map遞歸:實現(xiàn)深拷貝,不丟失屬性 map:存儲已經(jīng)拷貝過的對象,解決循環(huán)引用問題
//map存放已經(jīng)拷貝過的對象,key為需要拷貝的對象,value為拷貝后的對象function deepClone(target,map=new Map()){//1.判斷是否是引用類型if(typeof target === "object" && target !==null ){if(map.has(target))return map.get(target); //說明已經(jīng)拷貝過了let isArr = Array.isArray(target);let res = isArr?[]:{};map.set(target,res)if(isArr){//拷貝的是數(shù)組target.forEach((item,index) => {res[index] = deepClone(item,map);});}else{//拷貝的是對象Object.keys(target).forEach(key=>{res[key]=deepClone(target[key],map);})} return res; //返回的是一個數(shù)組或?qū)ο髛else{return target;}}//測試console.log(deepClone([1,[1,2,[3,4]]]))筆記鏈接
內(nèi)容
commonJs實現(xiàn)原理模塊執(zhí)行的原理require模塊加載原理ES6 module的特性import() 函數(shù) 動態(tài)引入commonJs和es module的區(qū)別【推薦學習:javascript高級教程】
以上就是看看這些前端面試題,帶你搞定高頻知識點(八)的詳細內(nèi)容,更多請關注php中文網(wǎng)其它相關文章!
關鍵詞: javascript