
壹 ❀ 引
在7月21交接完所有工作后,我也進入了休年假的階段(沒用完的8天年假),看似休息內心的緊張感反而瞬間加倍,到今天為止也面了幾家,好消息是工作機會特別特別多,一封簡歷沒投,面試邀請源源不斷,待下周一將三家面試走完后,后面我也要主動挑選一些感興趣的公司進行投遞,進一步確認自己在當前行業的定位。
好消息是,通過前面幾輪面試積累與反饋,可以確信自己JavaScript基礎還算不錯,離職前的焦慮與不安少了些許。壞消息是當前環境對於vue的需求成了硬性指標,而我之前公司一直用的angular1,當下基本被淘汰(只針對框架使用率),當然angular的知識體系確實對於我學習vue有極大的幫助,這點我倒不是很擔心。之前一直不夠自信,JavaScript覺得學的不好,http知識得補,糾結angularjs還要不要復習,vue也得學等等,心里亂如麻,現在心里倒是清晰了不少,松了口氣,以后也得加倍努力。
這篇文章原本應該是前幾天發布,因為面試耽誤了幾天,起因是在掘金瞟到一篇考察JS基礎的題目,原題網址在此。我將44題大致過了一遍,部分考點較為陳舊,也就是常說的用JS缺陷以及部分不規范寫法作為考點來問你為什么,對於這種題目我一般是較為排斥的,因為較好的編程習慣,使用嚴格模式,ES6語法,typescript等等都能很輕易避開這些問題,技術是進步的,止步不前反復去研究缺陷我覺得是開倒車的行為。
當然題目中也存在一些平日容易忽略的點,比如哈希數組遍歷,正則可能會踩的坑等等,所以整體下來還是有收獲的,本篇文章將只記錄我感興趣或者覺得有復習意義的題,完整題目大家可以點擊上方鏈接,那么本文開始。
貳 ❀ 快速過題,快速復習
第一題
["1", "2", "3"].map(parseInt)//?
出現頻率較高的一題,答案我就直說了,為[1, NaN, NaN]
,以上代碼等同於:
["1", "2", "3"].map((item, index) => parseInt(item, index));
本題考察點其實就是parseInt
第二參數,parseInt(string,radix)
接受2個參數,第一個參數大家熟悉,就是要轉數字的字符串,而第二個參數則是轉為數字的基礎,比如是按照二進制還是十進制進行轉換,關於第二參數大致分為如下幾種情況:

所以上述等同於執行三次,分別是:
parseInt('1', 0);// 1
parseInt('2', 1);// 超出范圍,為NaN
parseInt('3', 2);// 3無法用二進制規則解析,為NaN
第二題
[typeof null, null instanceof Object]//解析成什么?
首先我們知道null
的typeof
為Object
,這是因為JavaScript設計缺陷,具體原因是因為早期JavaScript的值由代表值類型和具體值兩部分組成,而null被設定成了0x00,而0恰好表示的對象類型,因此才造成了typeof
檢驗出錯的問題。
前面說了,null並不是對象,所以Object的原型並不會出現在null的原型鏈上,更何況null沒有原型(null是原型鏈的頂端),第二個自然是false了。
答案是['Object' , false]
;
第三題
[ [3,2,1].reduce(Math.pow), [].reduce(Math.pow) ]//解析成什么?
這里考察的其實就是reduce
的用法以及一些容易忽視的點,reduce
本質上是一個累加器,即每次遍歷的結果都會累積給下次遍歷使用,所以第一段代碼其實等同於如下:
Math.pow(Math.pow(3,2),1);//9
需要注意的是,像foreach
這個的API遍歷空數組,頂多就是不執行,但reduct
不同,它會報錯。不僅如此,如果數組只有一個元素,且我們沒為reduct
定義初始值同樣也會報錯,具體用法可以查看博主這篇文章JS reduce()方法詳解,使用reduce數組去重。
因此答案其實就是報錯不執行。
第四題
// 最終輸出什么?
var name = 'World!';
(function () {
if (typeof name === 'undefined') {
var name = 'Jack';
console.log('Goodbye ' + name);
} else {
console.log('Hello ' + name);
}
})();
老生常談的變量提升問題,上述代碼其實等同於:
var name = 'World!';
(function () {
var name;
if (typeof name === 'undefined') {
name = 'Jack';
console.log('Goodbye ' + name);
} else {
console.log('Hello ' + name);
}
})();
所以當執行到typeof
時name並沒有值,因此走了前面的分支,最終輸出Goodbye Jack
。
補充一點,怕大家弄混淆,下面代碼輸出什么?
var name = 'echo';
var name;
console.log(name)//?
有同學可能會說,是undefined
,其實還是echo
,這是因為在執行上下文創建時,會在標識符中記錄變量名,對於已存在的變量不會重復聲明,因此上述代碼第二次var name
其實是無意義的,不要把它理解成var name = undefined
。
那題目中為什么變量提升了name
不是World
呢?這是因為這里有兩個上下文啊,一個全局上下文,一個函數執行上下文,在兩個執行上下文中有兩個同名變量,本質並不是一個。
最簡單的證明方法是:
var name = 'echo';
function fn(){
var name;
console.log(name);
};
fn();//undefined
如果你對於變量提升比較懵,建議閱讀博主這篇文章【JS點滴】聲明提前,變量聲明提前,函數聲明提前,聲明提前的先后順序;當然,變量提升本質其實就是執行上下文問題,所以我還是推薦閱讀這篇文章一篇文章看懂JS執行上下文,畢竟知其然知其所以然。
第五題
var END = Math.pow(2, 53);
var START = END - 100;
var count = 0;
for (var i = START; i <= END; i++) {
count++;
}
console.log(count);//?
定眼一看,這里肯定輸出100啊,因為一開始就讓END減去了100然后進行for循環讓count
自增,但其實這里會陷入死循環。這是因為JavaScript中數字范圍最大也就是2^53
,因為當執行了100次之后2^53
自增加1仍然會是2^53
,而i <= END
因此進入死循環。
第六題
var ary = [0,1,2];
ary[10] = 10;
ary.filter(function(x) { return x === undefined;});//得到什么?
首先看到我們聲明了一個數組[0,1,2]
,隨后在索引10處添加了一個10,因此此時數組ary成了稀疏數組[0, 1, 2, empty × 7, 10]
,需要注意的是empty並不會執行,因此上述代碼中沒有一個與undefined
相同,因此返回空數組[]
;
第七題
// 以下兩段代碼分別輸出什么?
function showCase(value) {
switch(value) {
case 'A':
console.log('Case A');
break;
case 'B':
console.log('Case B');
break;
case undefined:
console.log('undefined');
break;
default:
console.log('Do not know!');
}
}
showCase(new String('A'));
function showCase2(value) {
switch(value) {
case 'A':
console.log('Case A');
break;
case 'B':
console.log('Case B');
break;
case undefined:
console.log('undefined');
break;
default:
console.log('Do not know!');
}
}
showCase2(String('A'));
本題說到底其實就是在問new String()
與String()
的區別,本質上也算是JavaScript缺陷了,這個缺陷就是構造函數竟可以new調用,也可以普通調用。而new調用其實是得到一個對象,普通調用時其實就是在做單純的類型轉換,具體如下圖:

所以第一段代碼其實拿的就是一個對象,因此輸出Do not know!
,第二段代碼傳遞的就是一個普通字符串,因此輸出Case A
。
ES6推出了class類,class只能被new調用,普通調用就會報錯。
第八題
// 下方代碼輸出什么?
function isOdd(num) {
return num % 2 == 1;
}
function isEven(num) {
return num % 2 == 0;
}
function isSane(num) {
return isEven(num) || isOdd(num);
}
var values = [7, 4, '13', -9, Infinity];
values.map(isSane);
考了個邏輯或||用於函數調用的情況,比如A||B,如果A為true則會跳過B判斷。如果A為false則會繼續判斷B;由此可知上述代碼其實就是將數組中每個元素分別丟到兩個函數中進行判斷。
唯一需要注意的幾個點是,字符串求余會被轉成數字,Infinity不是一個確切的數字,所以無法求余數,所以答案為[true,true,true,false,false]
;
第九題
Array.isArray( Array.prototype )//?
我印象里所有函數元素都是對象,包括了Object,String這些構造器,但是很遺憾,Array的原型還偏偏是個數組,只能說算是個冷知識吧,知道就行了。所以這里為true。
第十題
// 輸出什么?
var a = [0];
if ([0]) {
console.log(a == true);
} else {
console.log("wut");
}
考的隱式轉行,對於空數組,空對象,在if判斷中其實會被轉為true;a == true
這里的比較不能站在宏觀角度去理解,我之前理解是數組和布爾本來就不相等,所以為false。但a == false
會為真,經博客園用戶vuIgarian指出,這里引用知乎問題為何[0]==true 返回false, if([0]){alert('1')}可以被判斷為真彈出1?中,用戶不知道的回答:
[0] == true;
//分成以下步驟
//把true轉化成number,true變成1
[0] == 1;
//list是object
//先看[0].valueOf(),結果還是[0]
//再看[0].toString(),結果是“0” type是string
"0" == 1;
//把“0” string轉化成number,“0”變成0,0不等於1
0 == 1; //結果是false
所以實際開發中做數據對比,一定要用嚴格相等===
,就沒有這些亂七八糟的情況了。
第十一題
function sidEffecting(ary) {
ary[0] = ary[2];
}
function bar(a,b,c) {
c = 10
sidEffecting(arguments);
return a + b + c;
}
bar(1,1,1);//輸出多少?
這題考了函數的按值傳遞,第一次調用了bar
函數,傳遞了3個1,但執行時c被修改為10,所以此時abc分別為1,1,10。此時又調用了sidEffecting
函數,傳遞的是arguments對象,說到底就是前面abc的引用,在sidEffecting
中將a改成了10,所以此時abc分別為10,1,10,所以最終返回21。
注意,調用sidEffecting
傳遞的其實是形參abc的引用,所以修改后直接對原本的abc造成了影響,這也是解答本題的關鍵所在。
第十二題
[1 < 2 < 3, 3 < 2 < 1]//解析成什么?
這題考的是運算符優先級,不知道大家在編程時有沒有這樣寫過,結果大部分情況都沒按照自己的預期執行,這是因為上述代碼等同於:
1 < 2 < 3 => true < 3 => 1 < 3 => true
3 < 2 < 1 => false < 1 => 0 < 1 => true
所以這里輸出了2個true也就是[true,true]
。
第十三題
3.toString()//?
3..toString()//?
3...toString()//?
第一個報錯,JavaScript會解析成(3.)toString
,3.被理解成浮點數,所以沒有.能用於調用了,所以報錯。
第二個正常轉換為字符串3,因為會被解析為(3.).toString
。
第三個報錯,鬼會這么寫。
所以如果我們要將一個整數轉為字符串,除了上面第二種寫法外,還可以這樣:
(3).toString();//'3'
3+"";//'3'
第十四題
(function(){
var x = y = 1;
})();
console.log(y);//?
console.log(x);//?
上述代碼其實等同於:
var y = 1
(function(){
var x = y;
})();
console.log(y);//1
console.log(x);//局部變量函數外無法訪問
第十五題
var a = {}, b = Object.prototype;
[a.prototype === b, Object.getPrototypeOf(a) === b]// ?
這題毋庸置疑考原型,我們知道只有函數才有prototype,所以a只是一個普通對象,沒有prototype,第一個比較注定為false。
在看第二個比較,Object.getPrototypeOf(a)
用於獲取a的原型,a的原型不就是構造器Object()
的prototype,兩者指向同一個引用,所以第二個比較為true,答案為[false,true]
。
關於原型和原型鏈,可以閱讀博主這兩篇文章,保證整的明明白白
JS 疫情宅在家,學習不能停,七千字長文助你徹底弄懂原型與原型鏈
JS 究竟是先有雞還是有蛋,Object與Function究竟誰出現的更早,Function算不算Function的實例等問題雜談
第十六題
function f() {}
var a = f.prototype, b = Object.getPrototypeOf(f);
a === b;//?
同考原型,a取的其實就是函數f的原型,也就是一個對象。b取得是啥得知道getPrototypeOf取得是啥。Object.getPrototypeOf()
方法返回指定對象的原型 ( 即, 內部[[Prototype]]屬性)。所以b拿的其實是構造器Function()的原型,a和b取的不是一個東西,自然為false。
但下面這兩個是相同的:
function f() {};
var b = Object.getPrototypeOf(f);
var c = f.__proto__;
b === c//true
與第十五題一樣,推薦閱讀原型相關文章。
第十七題
function foo() { }
var oldName = foo.name;
foo.name = "bar";
[oldName, foo.name]
一個容易忽略的點,函數的name是只讀屬性不可修改,所以輸出兩個foo。
第十八題
"1 2 3".replace(/\d/g, parseInt)
這里不僅是考了parseInt第二參數,其實還考了replace第二參數其實可以是一個回調函數的用法,大家先看一個例子:
"1234 2345 3456".replace(/(\d)\d{2}(\d)/g, function (match, $1, $2, index, input) {
console.log([match, $1, $2, index, input]);
//輸出
["1234", "1", "4", 0, "1234 2345 3456"]
["2345", "2", "5", 5, "1234 2345 3456"]
["3456", "3", "6", 10, "1234 2345 3456"]
});
這里的參數分別表示為:
match:符合條件的匹配結果
$1:分組1的匹配內容
$2:分組2的匹配內容
index:每次匹配成功的索引
input:匹配輸入
但由於題目中的正則條件並沒有分組,所以$1與$2失效,再來看個例子:
"1234 2345 3456".replace(/\d\d{2}\d/g, function (match, $1, $2, index, input) {
console.log([match, $1, $2, index, input]);
//輸出
["1234", 0, "1234 2345 3456", undefined, undefined]
["2345", 5, "1234 2345 3456", undefined, undefined]
["3456", 10, "1234 2345 3456", undefined, undefined]
});
題目中提供的字符串為1 2 3
,帶空格,所以匹配了3次,等同於:
parseInt(1,0);//1
parseInt(2,2);//NaN
parseInt(3,4);//3
上面的0,2,4哪來的?其實就是每次匹配成功當前的下標。
如果這道題的題解看不懂,建議系統學習正則,可以查看博主正則專欄:從零開始學正則。
第十九題
var lowerCaseOnly = /^[a-z]+$/;
[lowerCaseOnly.test(null), lowerCaseOnly.test()]
輸出兩個true,也算是正則容易忽視的坑,test在檢測時會隱性將內容轉為字符串,所以上述檢測其實等同於:
[lowerCaseOnly.test('null'), lowerCaseOnly.test('undefined')]
第二十題
if ('http://giftwrapped.com/picture.jpg'.match('.gif')) {
'a gif file'
} else {
'not a gif file'
}
這題也是考的正則的隱式轉換,match第一個參數接受一個正則表達式或者一個字符串,但如果是字符串會隱式轉為正則,所以上述代碼等同於:
'http://giftwrapped.com/picture.jpg'.match(/.gif/)
而在正則中.表示通配符,所以成功匹配到/gif
,匹配成功,輸出a gif file
。