本篇文章給大家整理總結一些JavaScript 面試題,帶你搞定高頻知識點,希望對大家有所幫助!
區別 | let | const | var |
---|---|---|---|
重複宣告 | 不能重複宣告,會報SyntaxError錯 | const 定義常數,值不能修改的變數叫做常數,一定要賦初始值,因為不能修改。 | 可以重複宣告 |
塊級作用域 | 擁有 | 擁有 | 不擁有 |
會不會汙染全域性變數(掛載在window上) | 不會 | 不會 | 會 |
說明
1.let和const也存在變數提升,只是提升的方式不同
undefined
尚未初始化
暫時性死區
,程式碼執行過程中的一段時間內,在此期間無法使用識別符號,也不能參照外層作用域的變數。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開始執行的時候,它都在執行上下文中執行。
對全域性資料進行預處理
var定義的全域性變數 --> undefined,新增為window的屬性
function宣告的全域性函數 --> 賦值(函數體),新增為window的方法
this --> 賦值window
開始執行全域性程式碼
函數執行上下文:在呼叫函數,準備執行函數體之前,建立對應的函數執行上下文物件
對區域性資料進行預處理
執行上下文棧
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
登入後複製
ES5 function類 | ES6 class |
---|---|
可以new 可以呼叫 | 必須new呼叫,不能直接執行 |
function存在變數提升 | class不存在變數提升 |
static靜態方法只能通過類呼叫,不會出現在範例上 |
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)
}
}
登入後複製
內容
內容
內容
內容
觀察者是軟體設計模式中的一種,但釋出訂閱只是軟體架構中的一種訊息正規化
觀察者模式
觀察者模式定義物件間的一種一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都得到通知並被自動更新。
被依賴的物件叫做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', "種樹澆水");
登入後複製
區別
型別 | 描述 | 特點 |
---|---|---|
觀察者模式 | 觀察者和被觀察者互相知道身份,目標直接將通知分發到觀察者身上 | 高耦合 |
釋出訂閱機制 | 釋出訂閱機制通過事件排程中心來協調,訂閱者和釋出者互相不知道身份 | 低耦合 |
筆記內容
深淺拷貝只是針對參照資料型別
區分點: 複製之後的副本進行修改會不會影響到原來的
淺拷貝
擴充套件運運算元,適用於陣列/物件
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]]]))
登入後複製
內容
【推薦學習:】
以上就是看看這些前端面試題,帶你搞定高頻知識點(八)的詳細內容,更多請關注TW511.COM其它相關文章!