您好,登錄后才能下訂單哦!
今天小編給大家分享一下web前端高頻知識點面試題有哪些的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
區別 | let | const | var |
---|---|---|---|
重復聲明 | 不能重復聲明,會報SyntaxError錯 | const 定義常量,值不能修改的變量叫做常量,一定要賦初始值,因為不能修改。 | 可以重復聲明 |
塊級作用域 | 擁有 | 擁有 | 不擁有 |
會不會污染全局變量(掛載在window上) | 不會 | 不會 | 會 |
說明
1.let和const也存在變量提升,只是提升的方式不同
var變量提升:變量的聲明提升到頂部,值為undefined
let、const變量提升: 變量聲明提升到頂部,只不過將該變量標記為尚未初始化
let 和 const存在暫時性死區
,代碼執行過程中的一段時間內,在此期間無法使用標識符,也不能引用外層作用域的變量。
let answer;
function fn(){
//如果此時沒有將變量變量提升到這里,answer應該取外層answer的值
console.log(answer); //Uncaught ReferenceError: Cannot access 'answer' before initialization
let answer=42;
}
2.var創建的全局變量->全局對象的屬性,let和const在全局作用域聲明的變量->不是全局對象的屬性
3.如果常量是個數組或對象,對其內部元素修改,不算對常量的修改,不會報錯。常量指向了一個地址,地址不變就不會報錯。
變量聲明升級
通過var定義(聲明)的變量,在定義語句之前的就可以訪問到
但是值是undefined
函數聲明提升
通過function聲明的函數,在之前就可以直接調用。
值是函數體
//變量提升先于函數提升,提升后被函數聲明function覆蓋,所以就算換了順序也是function
function a(){
}
var a ;
console.log(typeof a); //function
var f1 = function () {
console.log(1);
}
function f1 () {
console.log(2);
}
f1() ; //1
//變量提升后
var f1;//變量提升
function f1(){};//函數提升
f1 = function () {
console.log(1);
}
f1() ;
變量提升練習題
理解:一個代碼段所在的區域,是靜態的,在編寫代碼時就確定了。
作用:變量綁定在這個作用域內有效,隔離變量,不同作用域下同名變量不會有沖突。
作用域分類
全局作用域
函數作用域
塊級作用域
作用域鏈:多個作用域嵌套,就近選擇,先在自己作用域找,然后去就近的作用域找。
函數的作用域在聲明的時候就已經決定了,與調用位置無關
所以執行aaa()的時候先在aaa的作用域里面找,沒有找到a,再去父級作用域window里面找,找到a=10
var a = 10;
function aaa() {
alert(a);
}
function bbb() {
var a = 20;
aaa();
}
bbb();
對當前JavaScript的執行環境的抽象,每當JavaScript開始執行的時候,它都在執行上下文中運行。
全局執行上下文:在執行全局代碼前將window確定為全局執行上下文
對全局數據進行預處理
var定義的全局變量 --> undefined,添加為window的屬性
function聲明的全局函數 --> 賦值(函數體),添加為window的方法
this --> 賦值window
開始執行全局代碼
函數執行上下文:在調用函數,準備執行函數體之前,創建對應的函數執行上下文對象
對局部數據進行預處理
形參變量 --> 賦值(實參)–> 添加到函數執行上下文的屬性
arguments(形參列表封裝成的偽數組)–>賦值(實參列表),添加到函數執行上下文的屬性
var定義的局部變量–>undefined,添加為函數執行上下文的屬性
function聲明的函數–>賦值(函數體),添加為函數執行上下文的方法
this–>賦值(調用函數的對象)
開始執行函數體代碼
執行上下文棧
1.在全局代碼執行前,JS引擎就會創建一個棧來存儲管理所有的執行上下文對象
2.在全局執行上下文(window)確定后,將其添加到棧中(壓棧)
3.在函數執行上下文創建后,將其添加到棧中(壓棧)
4.在當前函數執行完成后,將棧頂的對象移除(出棧)
5.當所有的代碼執行完后,棧中只剩下window
作用域 | 執行上下文 |
---|---|
定義了幾個函數 + 1 = 幾個作用域 | 執行了幾個函數 + 1 = 幾個執行上下文 |
函數定義時就確定了,一直存在,不會再變化,是靜態的 | 全局執行上下文環境實在全局作用域確定之后,js代碼執行之前創建的 調用函數時創建,函數調用結束被釋放,是動態的 |
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
:使用立即執行函數創造出一個塊級作用域
(function(){
var a = 1;
console.log('內部a:', a);
})();
const
1.使用立即執行函數創造出一個塊級作用域。
2.對于不可變性,可以利用Object.defineProperty
將變量掛載在對象上
var __const = function __const(data, value) {
this.data = value // 把要定義的data掛載到某個對象,并賦值value
Object.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
}
}
})
}
//然后和立即執行函數結合
(function(){
var obj = {}
_const.call(obj,'a',10)
})()
//當執行完畢后,全局上就不會有obj,也不會有obj.a這個變量,進而實現了塊級作用域的功能
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,所以輸出undefined
console.log(b.a()); //b.a 是function,b調用a函數,所以this指向b,所以輸出'bbb'
console.log(b.getName);//通過b調用getName,所以getName指向b,所以輸出'bbb'
console.log(c());//c是function,this指向window,window上沒有name,所以輸出undefined
筆記鏈接
JS數據類型有哪些
介紹一下Symbol和Bigint
如何判斷一個數據類型
Object.prototype.toString.call() 的缺點?
各個方法的原理是什么
typeof(NaN) typeof(Null)
手寫 instanceof 方法
null==undefined 和 null===undefined
隱式轉換規則 === 和 == 的區別
map和weakmap區別
map和object區別
for in、for of 區別,分別對對象和數組使用問結果
講一下數組的遍歷方法,filter與map的使用場景,some,every的區別
map的操作原理
map和forEach的區別
使用迭代器實現for-of
手寫數組去重
手寫數組扁平化
map和filter的區別
數組的常用方法
用reduce實現map
ES5 function類 | ES6 class |
---|---|
可以new 可以調用 | 必須new調用,不能直接執行 |
function存在變量提升 | class不存在變量提升 |
static靜態方法只能通過類調用,不會出現在實例上 |
一般函數中this的指向會在調用時向函數傳遞執行上下文對象中設置。
以函數形式調用,指向window
以方法形式調用,this指向調用的方法
以構造函數的形式調用,this是新創建的對象
箭頭函數:本身沒有this,它的this可以沿作用域鏈(定義時就確定了的)查找
apply、call、bind 函數可以改變 this 的指向。
區別 | call | apply | bind |
---|---|---|---|
調用函數 | √ | √ | × |
參數 | 從第二個參數開始依次傳遞 | 封裝成數組傳遞 | 從第二個參數開始依次傳遞 |
bind函數的特殊點
多次綁定,只指向第一次綁定的obj對象。
多次綁定,一次生效。
原因:返回函數,后續bind修改的是返回函數的this
call函數的實現
//從第二個參數開始依次傳入,所以接收時使用rest參數
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函數的實現
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函數的實現
Function.prototype.bind=function(obj,...args){
obj = obj || window;
args = args ? args : [];
return (...args2) => {
return this.apply(obj,[...args,...args2])
}
}
箭頭函數的作用:確保函數內部的this和外部的this是一樣的
箭頭函數是普通函數的語法糖,書寫要更加簡潔
區別 | 一般函數 | 箭頭函數 |
---|---|---|
this指向 | 調用時確定 | 定義時確定,沒有自己的this,沿著作用域鏈找父級的this |
改變this指向 | call,apply,bind | 不能改變,靜態 |
arguments | 有 | 沒有,可以用rest參數代替 |
作為構造函數 | √ | × 沒有prototype屬性 |
匿名函數 | 可以匿名可以不匿名 | 匿名函數 |
是什么
閉包就是在函數中能夠讀取其他函數內部變量
本質就是上級作用域內變量的生命周期,因為被下級作用域內引用,而沒有被釋放。
正常情況下,代碼執行完成之后,函數的執行上下文出棧被回收。但是如果當前函數執行上下文執行完成之后中的某個東西被執行上下文以外的東西占用,則當前函數執行上下文就不會出棧釋放,也就是形成了不被銷毀的上下文,閉包。
function foo(){
var a=2;
function bar(){ //覆蓋foo()內部作用域的閉包
console.log(a++);
}
return bar;
}
var bar = foo(); //foo執行創建一個執行上下文環境,由于bar引用了其內部變量,也就是bar持有foo本次執行上下文的引用,foo本次的執行上下文不會被銷魂
bar();//2
bar();//3
var fn = foo(); //foo執行創建一個新的執行上下文環境,fn持有了foo本次執行上下文的引用
fn();//2
有什么用
1.可以讀取函數內部的變量
2.使函數的內部變量執行完后,仍然存活在棧內存中(延長了局部變量的生命周期)。
JavaScript閉包就是在另一個作用域中保存了一份它從上一級函數或者作用域得到的變量,而這些變量是不會隨上一級函數的執行完成而銷毀
常用場景:節流防抖
缺點是什么
1.函數執行完后,函數內的局部變量沒有釋放,占用內存時間會變長
2.容易造成內存泄露
怎么解決
及時釋放:讓內部函數成為垃圾對象(將閉包手動設置為null)–> 回收閉包
作用是:控制回調函數觸發的頻率,進行性能優化
參數: 控制觸發頻率的回調函數和時間wait
輸出: 到時間后,返回callback函數
節流:在函數被頻繁觸發時, 函數執行一次后,只有大于設定的執行周期后才會執行第二次。一個時間段,只觸發一次
語法:throttle(callback, wait)
常用場景:比如拖動、滾動和輸入框聯想
//使用形式,綁定時候throttle函數就會執行,所以this是window
window.addEventListener('scroll',throttle(()=>{},500))
/*
思路
需要記錄上一次觸發的時間,才可以和當前時間比較,是否超過了間隔時間
第一次必然立刻觸發
*/
function throttle(callback,wait){
let pre = new Date();
//這里的this是window
return function(...args){
//這里的this是綁定的DOM
const now = new Date();
if(now-pre>=wait){
callback.apply(this,args);
pre = now;
}
}
}
/*
使用setTimeout實現
第一次需要延遲delay后觸發
*/
function throttle(callback,delay){
let timer = null;
//這里的this是window
return function(...args){
if(timer){//說明已經觸發了
return;
}
timer = setTimeout(()=>{
callback.apply(this,args);
timer = null;
},delay)
}
}
函數防抖:指定時間間隔內只會執行一次任務。如果在等待的過程中再一次觸發了事件,計時器重新開始計時,直到達到時間后執行最后一次的回調
語法:debounce(callback, wait)
常用場景: 登錄、發短信等按鈕避免用戶點擊太快,以致于發送了多次請求,需要防抖。
function debounce(callback,delay){
let timer = null;
//這里的this是window
return function(){
if(timer){//說明已經觸發了
clearTimeout(timer);
}
timer = setTimeout(()=>{
callback.apply(this,arguments);
timer = null;
},delay)
}
}
//立即執行
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)
}
}
筆記鏈接
原型和原型鏈
繼承
筆記鏈接
內容
進程和線程
進程的通信方式
瀏覽器多進程架構
如何實現瀏覽器多標簽之間的通訊
H5 Web Workers JS多線程運行
瀏覽器的事件循環機制
Node的事件循環機制
node事件循環代碼輸出題 用于理解
代碼輸出題
筆記
內容
DOM的渲染過程
DOM渲染的時機與渲染進程的概述
-瀏覽器的渲染流程
CSS、JS、DOM解析和渲染阻塞問題
JS加載阻塞DOM渲染問題,怎么解決? - 異步JS,JS三種異步加載的方式
script 標簽中的 async 和 defer 屬性
DOMContentLoaded和Load
DOM渲染優化
筆記
內容
什么是重繪和回流
回流和重繪觸發的時機
優化方案
GPU加速,如何開啟GPU加速
JS優化減少重繪和回流的觸發
筆記
內容
setTimeout(cb, 0)會立刻執行嗎?
settimeout定時的時間準確嗎? 為什么不準確? 怎么解決?
setTimeout和requestAnimation的區別
requestAnimationFrame講一下你的理解
setTimeout實際延遲時間
用setTimeout實現setInterval,實現一個隨時停止的版本
setTimeout 和 setInterval區別
JS實現動畫的方式
requestAnimationFrame與requestIdleCallback分別是什么?
requestAnimationFrame的執行時機?
requestanimationframe回調函數中進行大量計算,會阻塞頁面的渲染嗎
每隔一秒輸出一個數字
觀察者是軟件設計模式中的一種,但發布訂閱只是軟件架構中的一種消息范式
觀察者模式
觀察者模式定義對象間的一種一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴于它的對象都得到通知并被自動更新。
被依賴的對象叫做subject
,依賴的對象叫做觀察者Observer
。被依賴的對象首先需要提供一個添加觀察者方法,以供觀察者調用。所以還需要維護一個觀察者列表,自身發生變化后,依次通知觀察者。
//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');
發布訂閱機制
發布者和訂閱者不直接進行通信,通過事件調度中心進行管理。發布者將要發布的消息交由事件調度中心管理,訂閱者也是根據自己的情況,按需訂閱事件調度中心的消息。
//事件調度中心
class PubSub {
constructor() {
// 存儲格式: warTask: [], routeTask: []
// {訂閱事件:[回調1,回調2...],訂閱事件2:[回調1,回調2..]}
this.events = {}
}
// 訂閱方法 訂閱哪個類型type就把對應的回調函數放入
subscribe(type, cb) {
if (!this.events[type]) {
this.events[type] = [];
}
this.events[type].push(cb);
}
// 發布方法
publish(type, ...args) {
if (this.events[type]) {
this.events[type].forEach(cb => cb(...args))
}
}
// 取消訂閱方法 的某一個類型的某一個回調
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("宗門殿發布戰斗任務,任務信息:" + taskInfo);
})
pubsub.subscribe('routeTask', function (taskInfo) {
console.log("宗門殿發布日常任務,任務信息:" + taskInfo);
});
pubsub.subscribe('allTask', function (taskInfo) {
console.log("宗門殿發布五星任務,任務信息:" + taskInfo);
});
//發布
pubsub.publish('warTask', "獵殺時刻");
pubsub.publish('allTask', "獵殺時刻");
pubsub.publish('routeTask', "種樹澆水");
pubsub.publish('allTask', "種樹澆水");
區別
類型 | 描述 | 特點 |
---|---|---|
觀察者模式 | 觀察者和被觀察者互相知道身份,目標直接將通知分發到觀察者身上 | 高耦合 |
發布訂閱機制 | 發布訂閱機制通過事件調度中心來協調,訂閱者和發布者互相不知道身份 | 低耦合 |
筆記鏈接
筆記內容
Generator生成器函數
Generator生成器函數使用上的補充 了解
基于Promise對象的簡單自動執行器
iterator迭代器
async/await是什么? 使用場景是什么?
await/async與generator函數的區別
await/async內部實現原理 Generator函數和自動執行器
async錯誤捕獲方式
promise概述
promise知識點 了解
promise.then、catch、finally的原理與實現
Promise.all/Promise.race/Promise.allSettled的原理和實現
手寫題:請求五秒未完成則終止
promise實現并發的異步任務調度器
深淺拷貝只是針對引用數據類型
區分點: 復制之后的副本進行修改會不會影響到原來的
淺拷貝:修改拷貝以后的數據會影響原數據。使得原數據不安全。(只拷貝一層)
深拷貝:修改拷貝以后的數據不會影響原數據,拷貝的時候生成新數據。
淺拷貝
擴展運算符,適用于數組/對象
Aarry.prototype.concat(拷貝對象1,拷貝對象2...)
數組的合并方法,將多個數組或對象拷貝進目標數組,返回新數組。
Object.assign(目標對象1,拷貝對象1,拷貝對象2.....)
對象的合并方法,將拷貝對象拷貝進目標對象
方式一: JSON.parse(JSON.stringify())
JSON.stringify()
:將JavaScript對象轉換為JSON字符串
JSON.parse()
:可以將JSON字符串轉為一個對象。
問題1: 函數屬性會丟失,不能克隆方法
問題2: 循環引用會出錯
//循環引用:b中引用了c,c中又有b
obj = {
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) {
//通過數組創建JSON格式的字符串
let str = JSON.stringify(target);
//將JSON格式的字符串轉換為JS數據
let data = JSON.parse(str);
return data;
}
方式二:遞歸+map
遞歸:實現深拷貝,不丟失屬性
map:存儲已經拷貝過的對象,解決循環引用問題
//map存放已經拷貝過的對象,key為需要拷貝的對象,value為拷貝后的對象
function deepClone(target,map=new Map()){
//1.判斷是否是引用類型
if(typeof target === 'object' && target !==null ){
if(map.has(target))return map.get(target); //說明已經拷貝過了
let isArr = Array.isArray(target);
let res = isArr?[]:{};
map.set(target,res)
if(isArr){//拷貝的是數組
target.forEach((item,index) => {
res[index] = deepClone(item,map);
});
}else{//拷貝的是對象
Object.keys(target).forEach(key=>{
res[key]=deepClone(target[key],map);
})
}
return res; //返回的是一個數組或對象
}else{
return target;
}
}
//測試
console.log(deepClone([1,[1,2,[3,4]]]))
以上就是“web前端高頻知識點面試題有哪些”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。