JavaScript:面試頻繁出現的幾個易錯點

守候i發表於2018-03-26

1.前言

這段時間,金三銀四,很多人面試,很多人分享面試題。在前段時間,我也臨時擔任面試官,為了大概瞭解面試者的水平,我也寫了一份題目,面試了幾個前端開發者。在這段時間裡面,我在學,在寫設計模式的一些知識,想不到的設計模式的這些知識,就是面試題裡面,頻繁讓人掉坑的考點。所以,今天就總結一下,那些讓人掉坑的考點。

2.物件導向程式設計

關於物件導向和麵向過程,個人覺得這兩者不是絕對獨立的,而是相互相成的關係。至於什麼時候用物件導向,什麼時候用程式導向,具體情況,具體分析。

針對於物件導向程式設計的。知乎上有一個高贊回答:

物件導向: 狗.吃(屎)
程式導向: 吃.(狗,屎)

但是這個例子覺得不太優雅,我改一下了,舉一個優雅些的小例子說明一下物件導向和麵向過程的區別。

需求:定義‘守候吃火鍋

物件導向的思想是:守候.動作(吃火鍋)

程式導向的思想是:動作(守候,吃火鍋)

程式碼實現方面:

//物件導向
//定義人(姓名)
let People=function(name){
    this.name=name;
}
//動作
People.prototype={
    eat:function(someThing){
        console.log(`${this.name}${someThing}`);
    }
}
//守候是個人,所以要建立一個人(new一次People)
let shouhou=new People('守候','男',24);
shouhou.eat('火鍋');

//程式導向
let eat=function(who,someThing){
    console.log(`${who}${someThing}`);
}
eat('守候','火鍋');複製程式碼

結果都一樣,都是輸出‘守候吃火鍋’。但是萬一我現在吃飽了,準備寫程式碼了。這下怎麼實現呢?看程式碼

//物件導向
shouhou.coding=function(){
    console.log(this.name+'寫程式碼');
}
shouhou.coding();
//程式導向
let coding=function(who){
    console.log(who+'寫程式碼');
}
coding('守候');複製程式碼

結果也一樣:‘守候寫程式碼’

但是不難發現物件導向更加的靈活,複用性和擴充套件性更加。因為物件導向就是針對物件(例子中的:‘守候’)來進行執行某些動作。這些動作可以自定義擴充套件。
而程式導向是定義很多的動作,來指定誰來執行這個動作。

好了,物件導向的簡單說明就到這裡了,至於物件導向的三大特性:繼承,封裝,多型這個自行上網查詢資料。

3.this

使用 JavaScript 開發的時候,很多開發者多多少少會被 this 的指向搞蒙圈,但是實際上,關於 this 的指向,記住最核心的一句話:哪個物件呼叫函式,函式裡面的this指向哪個物件。

下面分幾種情況談論下

3-1.普通函式呼叫

這個情況沒特殊意外,就是指向全域性物件-window。

let username='守候'
function fn(){
    alert(this.username);//undefined
}
fn();複製程式碼

可能大家會困惑,為什麼不是輸出守候,但是在細看一看,我宣告的方式是let,不會是window物件
如果輸出守候,要這樣寫

var username='守候'
function fn(){
    alert(this.username);//守候
}
fu();
//---------------
window.username='守候'
function fn(){
    alert(this.username);//守候
}
fn();
//可以理解為
//window.fn();複製程式碼

3-2.物件函式呼叫

這個相信不難理解,就是那個函式呼叫,this指向哪裡

window.b=2222
let obj={
    a:111,
    fn:function(){
        alert(this.a);//111
        alert(this.b);//undefined
    }
}
obj.fn();複製程式碼

很明顯,第一次就是輸出obj.a,就是111。而第二次,obj沒有b這個屬性,所以輸出undefined,因為this指向obj

但是下面這個情況得注意

let obj1={
    a:222
};
let obj2={
    a:111,
    fn:function(){
        alert(this.a);
    }
}
obj1.fn=obj2.fn;
obj1.fn();//222複製程式碼

這個相信也不難理解,雖然obj1.fn是從obj2.fn賦值而來,但是呼叫函式的是obj1,所以this指向obj1

3-3.建構函式呼叫

let TestClass=function(){
    this.name='111';
}
let subClass=new TestClass();
subClass.name='守候';
console.log(subClass.name);//守候
let subClass1=new TestClass();
console.log(subClass1.name)//111複製程式碼

這個也是不難理解,回憶下(new的四個步驟)就差不多了!

但是有一個坑,雖然一般不會出現,但是有必要提一下。

在建構函式裡面返回一個物件,會直接返回這個物件,而不是執行建構函式後建立的物件

3-4.apply和call呼叫

apply和call簡單來說就是會改變傳入函式的this。

let obj1={
    a:222
};
let obj2={
    a:111,
    fn:function(){
        alert(this.a);
    }
}
obj2.fn.call(obj1);複製程式碼

此時雖然是 obj2 呼叫方法,但是使用 了call,動態的把 this 指向到 obj1。相當於這個 obj2.fn 這個執行環境是 obj1applycall 詳細內容在下面提及。

3-5.箭頭函式呼叫

首先不得不說,ES6 提供了箭頭函式,增加了我們的開發效率,但是在箭頭函式裡面,沒有 this ,箭頭函式裡面的 this 是繼承外面的環境。

一個例子

let obj={
    a:222,
    fn:function(){    
        setTimeout(function(){console.log(this.a)})
    }
};
obj.fn();//undefined複製程式碼

不難發現,雖然 fn() 裡面的 this 是指向 obj ,但是,傳給 setTimeout 的是普通函式, this 指向是 windowwindow 下面沒有 a ,所以這裡輸出 undefined

換成箭頭函式

let obj={
    a:222,
    fn:function(){    
        setTimeout(()=>{console.log(this.a)});
    }
};
obj.fn();//222複製程式碼

這次輸出 222 是因為,傳給 setTimeout 的是箭頭函式,然後箭頭函式裡面沒有 this ,所以要向上層作用域查詢,在這個例子上, setTimeout 的上層作用域是 fn。而 fn 裡面的 this 指向 obj ,所以 setTimeout 裡面的箭頭函式的 this ,指向 obj 。所以輸出 222

4.call和apply

callapply 的作用,完全一樣,唯一的區別就是在引數上面。
call 接收的引數不固定,第一個引數是函式體內 this 的指向,第二個引數以下是依次傳入的引數。
apply接收兩個引數,第一個引數也是函式體內 this 的指向。第二個引數是一個集合物件(陣列或者類陣列)

let fn=function(a,b,c){
console.log(a,b,c);
}
let arr=[1,2,3];複製程式碼

如上面這個例子

let obj1={
    a:222
};
let obj2={
    a:111,
    fn:function(){
        alert(this.a);
    }
}
obj2.fn.call(obj1);複製程式碼

callapply 兩個主要用途就是

1.改變 this 的指向(把 thisobj2 指向到 obj1

2.方法借用( obj1 沒有 fn ,只是借用 obj2 方法)

5.閉包

閉包這個可能大家是迷糊,但是必須要征服的概念!下面用一個例子簡單說下

let add=(function(){
let now=0;
return {
 doAdd:function(){
    now++;
    console.log(now);
}
}
})()複製程式碼

然後執行幾次!

上圖結果看到,now 這個變數,並沒有隨著函式的執行完畢而被回收,而是繼續儲存在記憶體裡面。
具體原因說下:剛開始進來,因為是自動執行函式,一開始進來會自動執行,這一塊

然後把這個物件賦值給 add 。由於 add 裡面有函式是依賴於 now 這個變數。所以 now不會被銷燬,回收。這就是閉包的用途之一(延續變數週期)。由於 now 在外面訪問不到,這就是閉包的另一個用途(建立區域性變數,保護區域性變數不會被訪問和修改)。

可能有人會有疑問,閉包會造成記憶體洩漏。但是大家想下,上面的例子,如果不用閉包,就要用全域性變數。把變數放在閉包裡面和放在全域性變數裡面,影響是一致的。使用閉包又可以減少全域性變數,所以上面的例子閉包更好!

6.小結

在學設計模式的時候,遇到的知識點就是這一些了,這些知識點,也是我在群聊,社群裡面,讓人掉坑比較多的考點。這些知識,可以說是開發常用,面試常考的知識,還是建議大家深入些學習。上面那裡也是簡單的過一下而已。不算深入。如果大家對文章有什麼建議,歡迎指點。

-------------------------華麗的分割線--------------------
想了解更多,關注關注我的微信公眾號:守候書閣

clipboard.png


相關文章