乘興裸辭心甚爽,面試工作屢遭難。
幸得每日一題伴,點選關注莫偷懶。
又要到金九銀十的跳槽季了,為了讓更多的小夥伴可以在面試的時候取的更好的offer
,所以自上月起我每天都會在自己的公眾號【前端有的玩】裡面推送一到兩道面試題,方便找工作的小夥伴每日都會有新的收穫。本文就是小編將前期的一些比較經典的每日一題進行了梳理,歡迎大家一起來看看。本文內容首發於公眾號【前端有的玩】,關注 ===
學會。
類陣列面試題
什麼是類陣列,類陣列就是 擁有length
屬性,且其他屬性(索引)為非負整數的物件,且不具備陣列所用於的方法。比如我們常用的document.querySelector
返回的NodeLists
就是一個類陣列。這道題就是和類陣列相關的內容.
題目
請說出以下程式碼輸出的內容,需要區分nodejs
,chrome
以及chrome
去掉splice
之後的輸出內容
var obj = {
'2': 3,
'3': 4,
'length': 2,
'splice': Array.prototype.splice,
'push': Array.prototype.push
}
obj.push(1)
obj.push(2)
console.log(obj)
答案
這道題一共問了三種情況下面的輸出,下面依次說明答案
node
下面輸出{ '2': 1, '3': 2, length: 4, splice: [Function: splice], push: [Function: push] }
chrome
下面輸出[empty × 2, 1, 2, splice: ƒ, push: ƒ]
chrome
去掉splice
下面輸出{2: 1, 3: 2, length: 4, push: ƒ}
通過上面輸出的內容,可以看出相同的程式碼,不同情況輸出的內容是有所不同的,下面進行詳細分解。
題解
在解答題目之前,我們再看看這段程式碼
const arr = new Array(2)
// 輸出 2 [empty * 2]
console.log(arr.length, arr)
arr.push(1)
// 輸出 3 [empty * 2, 1]
console.log(arr.length, arr)
可以看到push
方法會將陣列的length + 1
, 然後將值放在索引為length - 1
的位置,比如上面的程式碼,因為在初始化陣列的時候,已經將陣列長度指定為了2
, 所以在push
之後length
就變成了3
,然後arr[3 - 1] = 1
在MDN
上面對push
的方法的解釋是:
push
方法具有通用性。該方法和call()
或apply()
一起使用時,可應用在類似陣列的物件上。push
方法根據length
屬性來決定從哪裡開始插入給定的值。如果length
不能被轉成一個數值,則插入的元素索引為 0,包括length
不存在時。當length
不存在時,將會建立它。
根據MDN
解釋,push
既可以使用到陣列中,也可以使用到類陣列中。而根據前文中對類陣列的解釋,可以看到題目中的obj
就是一個標準的類陣列,那就可以在obj
上面使用陣列的push
方法。
再看obj.push(1)
, 因為obj.length = 2
, 所以會將length + 1
就變成了3
, 這時候 索引值時obj[3 - 1] = 1
即obj[2] = 1
, 同理 obj.push(2)
也一樣的。因為在obj
中已經有了屬性(索引)2
和3
,所以在push
的時候會覆蓋掉2
和3
上面的預設值。
所以在nodejs
中就會輸出
{ '2': 1,
'3': 2,
length: 4,
splice: [Function: splice],
push: [Function: push] }
但是在chrome
控制檯中輸出
[empty × 2, 1, 2, splice: ƒ, push: ƒ]
很奇怪,為什麼會輸出這樣呢?這一塊有一個很特殊的陷阱,就是chrome
控制檯是如何判斷列印的內容是陣列還是其他物件呢?對於這個,chrome
就是通過判斷物件上面是否有splice
和length
這兩個屬性來判斷的,所以如果你將splice
去掉之後,就會輸出以下內容
{2: 1, 3: 2, length: 4, push: ƒ}
你也可以試試下面的程式碼:
console.log({splice:function(){},length:1})
console.log({slice:function(){},length:1})
邏輯面試題之小鼠喝毒藥
小編當年畢業的時候面試就遇到過好幾次邏輯類的面試題,這道題就是一道邏輯類的面試題,一起來看看。
題目
有16瓶水,其中只有一瓶水有毒,小白鼠喝一滴之後一小時會死,請問最少用多少隻小白鼠,在1小時內一定可以找出有毒的水?
答案與題解
答案是至少需要4
只小鼠,怎麼理解呢?我們可以用二進位制去推理一下:
假設有4只小鼠,分別是甲乙丙丁,使用二進位制來表示小鼠喝藥的順序,1
代表喝藥,0
代表不喝藥
甲: 1111 1111 0000 0000
乙: 1111 0000 1111 0000
丙: 1100 1100 1100 1100
丁: 1010 1010 1010 1010
那麼我們就可以這樣去判斷:
- 甲乙丙丁都死了,說明第一瓶有毒
- 甲乙丙死了,說明第二瓶有毒
- 甲乙丁死了,說明第三瓶有毒
- 甲乙死了,說明第四瓶有毒
- 甲丙丁死了,說明第五瓶有毒
- 。。。 依次類推
其實對於這道題,可以使用2
的n
次方來判斷,比如有32
瓶水,那麼就是2
的5
次方,所以就需要5
只小鼠。
arguments 面試題
在ES6
中,我們如果一個函式引數個數不確定,我們一般會使用擴充套件運算子即function(...rest){}
,得到一個引數陣列rest
,但是在ES6
之前,我們是不能使用擴充套件運算子的,這時候就需要考慮使用arguments
題目
請說出以下程式輸出的內容(chrome輸出內容)
let obj = {
arg: 18,
foo: function(func) {
func()
arguments[0]()
}
}
var age = 10
function fn() {
console.log(this.age)
}
obj.foo(fn)
答案
本題的答案是:
// 第一個輸出 10
func()
// 第一個輸出 undefined
arguments[0]()
有點出乎意料了嗎?
先來解釋一下第一個,為什麼不是輸出18
呢,雖然func()
是在foo
函式裡面呼叫的,但是並沒有顯式指明作用域,這時候會使用預設作用域window
,而對於瀏覽器來說,在全域性通過var
宣告的變數會自動掛載到window
上面,所以var age = 10
相當於window.age = 10
, 而第一個func()
裡面的this.age
相當於window.age
第二個可能許多人有點蒙,為啥是undefined
,先看一下下面的程式碼
const arr = [function() {console.log(this[1])}, '我是子君']
// 輸出 我是子君
console.log(arr[0]())
我們通過arr[0]
獲取到函式,這時候函式的作用域就是這個陣列,所以再呼叫的時候,this
就是arr
, 所以this[1]
就是陣列第二項。
這時候回過頭來看arguments
,這個其實是一個類陣列,裡面存的是函式傳入的引數,第一項就是傳入的函式,和上面例子一樣,arguments[0]
的作用域就是arguments
,而arguments
上面並沒有age
屬性,所以是undefined
this指向問題
this
指向問題一直是比較混亂的,在箭頭函式出現之前,this
的指向與程式碼在哪裡定義並沒有關係,而是取決於是被誰執行的,正因為此,所以許多開發人員經常會搞不清楚this
到底是誰。下面的兩道題都是和this
指向相關的問題。
題目一(青銅)
請說出以下程式碼輸出的內容
let num = 1;
let obj = {
num: 2,
add: function() {
this.num = 3;
(function() {
console.log(this.num);
this.num = 4;
})();
console.log(this.num);
},
sub: function() {
console.log(this.num)
}
}
obj.add();
console.log(obj.num);
console.log(num);
const sub = obj.sub;
sub();
題目二(黃金)
請說出以下程式碼輸出的內容
var num = 10
const obj = {num: 20}
obj.fn = (function (num) {
this.num = num * 3
num++
return function (n) {
this.num += n
num++
console.log(num)
}
})(obj.num)
var fn = obj.fn
fn(5)
obj.fn(10)
console.log(num, obj.num)
答案
題目一
輸出結果: 1
,3
,3
,4
,4
, 你答對了嗎?下面我們來看看程式碼解析
var num = 1;
let obj = {
num: 2,
add: function() {
this.num = 3;
// 這裡的立即指向函式,因為我們沒有手動去指定它的this指向,所以都會指向window
(function() {
// 所有這個 this.num 就等於 window.num
console.log(this.num);
this.num = 4;
})();
console.log(this.num);
},
sub: function() {
console.log(this.num)
}
}
// 下面逐行說明列印的內容
/**
* 在通過obj.add 呼叫add 函式時,函式的this指向的是obj,這時候第一個this.num=3
* 相當於 obj.num = 3 但是裡面的立即指向函式this依然是window,
* 所以 立即執行函式裡面console.log(this.num)輸出1,同時 window.num = 4
*立即執行函式之後,再輸出`this.num`,這時候`this`是`obj`,所以輸出3
*/
obj.add() // 輸出 1 3
// 通過上面`obj.add`的執行,obj.name 已經變成了3
console.log(obj.num) // 輸出3
// 這個num是 window.num
console.log(num) // 輸出4
// 如果將obj.sub 賦值給一個新的變數,那麼這個函式的作用域將會變成新變數的作用域
const sub = obj.sub
// 作用域變成了window window.num 是 4
sub() // 輸出4
題目二
輸出結果為: 22
23
65
30
, 你答對了嗎? 下面我們解析一下
var num = 10
const obj = {num: 20}
obj.fn = (function (num) {
this.num = num * 3
num++
return function (n) {
this.num += n
num++
console.log(num)
}
})(obj.num)
var fn = obj.fn
fn(5)
obj.fn(10)
console.log(num, obj.num)
我們把上面的程式碼分為以下幾步進行分析
- 先看第三行程式碼,是一個賦值操作,我們知道賦值操作是從右向左的,而
=
號右邊是一個立即執行函式,所以會優先執行立即執行函式,立即執行函式沒有手動指定this
,這時候this = window
,而立即函式的引數num
是傳進來的obj.num
,所以num
引數預設值是20
- 第四行相當於
window.num = 20 * 3
- 第五行為傳入的引數加一,所以
num = 20 + 1
第六行
return
了一個函式,而這個函式就是obj.fn
的值, 但是因為return
的函式引用了立即執行函式裡面的num
,所以形成了閉包。這時候obj.fn = function(n) { this.num += n // 這個num是立即執行函式裡面的num num++ console.log(num) }
var fn = obj.fn
, 將obj.fn
賦值給新的變數,而這個變數的作用域是window
- 在呼叫
fn(5)
的時候, 在第二步,window.num
的值已經變成了60
, 然後因為這時候fn
的this
是window
,this.num += n
相當於window.num += n
, 即window.num = 65
num++
, 因為閉包的原因,第三步num
是21
,所以這一步num
變成了22
, 同時輸出22
- 然後
obj.fn(10)
,這時候fn
的this
為obj
,obj.num
預設值是20
,this.num += n
相當於obj.num += 10
- 和第七步一樣,
num + 1
輸出23
console.log(num, obj.num)
相當於console.log(window.num, obj.num)
,從上面幾步可知,window.num = 65
,obj.num = 30
。
擴充套件題
如果將上面兩道題的 var
改成 let
, 又會輸出什麼結果呢?
資料型別轉換問題
雖然在日常開發中,我們隱氏型別轉換用的比較少(不一定),但是這個還是面試常問問題,掌握還是要掌握的,一起來看看這道題目吧.
題目(王炸/青銅,我也不知道)
請說出以下程式碼輸出的內容
console.log([] + [])
console.log({} + [])
console.log([] == ![])
console.log(true + false)
答案
一起來看看答案吧
- 第一行程式碼
// 輸出 "" 空字串
console.log([] + [])
這行程式碼輸出的是空字串""
, 包裝型別在運算的時候,會先呼叫valueOf
方法,如果valueOf
返回的還是包裝型別,那麼再呼叫toString
方法
// 還是 陣列
const val = [].valueOf()
// 陣列 toString 預設會將陣列各項使用逗號 "," 隔開, 比如 [1,2,3].toSting 變成了"1,2,3",空陣列 toString 就是空字串
const val1 = val.toString() // val1 是空字串
所以上面的程式碼相當於
console.log("" + "")
第二行程式碼
// 輸出 "[object Object]" console.log({} + [])
和第一題道理一樣,物件
{}
隱氏轉換成了[object Object]
,然後與""
相加第三行程式碼
// 輸出 true console.log([] == ![])
對於
===
, 會嚴格比較兩者的值,但是對於==
就不一樣了- 比如
null == undefined
- 如果非
number
與number
比較,會將其轉換為number
- 如果比較的雙方中由一方是
boolean
,那麼會先將boolean
轉換為number
- 比如
所以對於上面的程式碼,看下面一步一步分析
// 這個輸出 false
console.log(![])
// 套用上面第三條 將 false 轉換為 數值
// 這個輸出 0
console.log(Number(false))
// 包裝型別與 基本型別 == 先將包裝型別通過 valueOf toString 轉換為基本型別
// 輸出 ""
console.log([].toString())
// 套用第2條, 將空字串轉換為數值、
// 輸出 0
console.log(Number(""))
// 所以
console.log(0 == 0)
第四行程式碼
// 輸出 1 console.log(true + false)
兩個基本型別相加,如果其中一方是字元,則將其他的轉換為字元相加,否則將型別轉換為
Number
,然後相加,Number(true)
是1
,Number(false)
是0
, 所以結果是1
總結
面試造火箭,工作擰螺絲。雖然我只想擰螺絲,但是我卻需要通過造火箭來找到擰螺絲的工作,每日一題,每天都有新的面試題目,歡迎關注公眾號【前端有的玩】,拉你進入前端技術交流群,每日一題等著你來一起答題。
結語
不要吹滅你的靈感和你的想象力; 不要成為你的模型的奴隸。 ——文森特・梵高