導讀: 如下是收集整理的 JS 相關問題,若是文中有什麼錯誤,歡迎大家指正批評,願與大家在交流之中共同進步。愈激烈,愈深刻。 1.使用 typeof bar "object" 來確定 obj 是否是對象的缺陷是什麼?如何避免? 在類型檢測中,typeof 適合原始數據類型和 function 的檢測 ...
導讀:
如下是收集整理的 JS 相關問題,若是文中有什麼錯誤,歡迎大家指正批評,願與大家在交流之中共同進步。愈激烈,愈深刻。
1.使用 typeof bar === "object" 來確定 obj 是否是對象的缺陷是什麼?如何避免?
在類型檢測中,typeof 適合原始數據類型和 function 的檢測。遇到 null 時會失效,會和數組的檢測一樣僅返回“Object”。所以在檢測 obj 是否時對象時,也應該檢測 obj 的值是否為 null。
var obj = { };
console.log((obj !== null) && (typeof obj === "object")); // logs ture
2.下麵的代碼控制台將輸出什麼,為什麼?
(function(){
var a = b = 3;
})();
console.log(b);//logs 3
console.log(a);//報錯
常常人們會認為 a、b 是在函數作用域中申明的局部變數,在全局作用域中是訪問不到的,所以會認為上面兩者都會輸出 undefined。然而在非嚴格模式下,上面申明的變數 b 是全局變數。
3.下麵的代碼控制台將輸出什麼,為什麼?
var obj = {
text:"something",
test:function(){
var obj2 = this;
console.log(this.text);
console.log(obj2.text);
(function(){
console.log(this.text);
console.log(obj2.text);
})()
}
};
obj.test()
//logs:something/something/undefined/something
在函數上下文中 this 的值決定於函數的調用方式,函數test 定義了 obj2 的值等於當前 this 的值,且該函數作為 obj 的方法被調用,所以 this.text 和 obj2.text 的值為“something”。而其中的自執行函數中的 obj2 的值在作用域鏈中可以找到值為“something”,因為該函數是直接調用的,所以 this 的值為 window。如下所示:
var text = "other something";
var obj = {
text:"something",
test:function(){
var obj2 = this;
console.log(this.text);
console.log(obj2.text);
(function(){
console.log(this.text);
console.log(obj2.text);
})()
}
};
obj.test()
//logs:something/something/other something/something
4.為什麼使用嚴格模式?
"嚴格模式"體現了Javascript更合理、更安全、更嚴謹的發展方向,另一方面,同樣的代碼,在"嚴格模式"中,可能會有不一樣的運行結果;一些在"正常模式"下可以運行的語句,在"嚴格模式"下將不能運行。具體體現在:
- 使調試更加容易。那些被忽略或默默失敗了的代碼錯誤,會產生錯誤或拋出異常,因此儘早提醒你代碼中的問題,你才能更快地指引到它們的源代碼。
- 防止意外的全局變數。如果沒有嚴格模式,將值分配給一個未聲明的變數會自動創建該名稱的全局變數。這是JavaScript中最常見的錯誤之一。在嚴格模式下,這樣做的話會拋出錯誤。
- 消除 this 強制。如果沒有嚴格模式,引用null或未定義的值到 this 值會自動強制到全局變數。這可能會導致許多令人頭痛的問題和讓人恨不得拔自己頭髮的bug。在嚴格模式下,引用 null或未定義的 this 值會拋出錯誤。
- 不允許重覆的屬性名稱或參數值。當檢測到對象(例如,var object = {foo: "bar", foo: "baz"};)中重覆命名的屬性,或檢測到函數中(例如,function foo(val1, val2, val1){})重覆命名的參數時,嚴格模式會拋出錯誤,因此捕捉幾乎可以肯定是代碼中的bug可以避免浪費大量的跟蹤時間。
- 使eval() 更安全。在嚴格模式和非嚴格模式下,eval() 的行為方式有所不同。最顯而易見的是,在嚴格模式下,變數和聲明在 eval() 語句內部的函數不會在包含範圍內創建(它們會在非嚴格模式下的包含範圍中被創建,這也是一個常見的問題源)。
- 在 delete使用無效時拋出錯誤。delete操作符(用於從對象中刪除屬性)不能用在對象不可配置的屬性上。當試圖刪除一個不可配置的屬性時,非嚴格代碼將默默地失敗,而嚴格模式將在這樣的情況下拋出異常。
5.封裝JavaScript源文件的全部內容到一個函數塊有什麼意義及理由?
這是一個越來越普遍的做法,被許多流行的JavaScript庫(jQuery,Node.js等)採用。這種技術創建了一個圍繞文件全部內容的閉包,也許是最重要的是,創建了一個私有的命名空間,從而有助於避免不同JavaScript模塊和庫之間潛在的名稱衝突。
這種技術的另一個特點是,允許一個易於引用的(假設更短的)別名用於全局變數。這通常用於,例如,jQuery插件中。jQuery允許你使用jQuery.noConflict(),來禁用 $ 引用到jQuery命名空間。在完成這項工作之後,你的代碼仍然可以使用$ 利用這種閉包技術,如下所示:
(function($) { /* jQuery plugin code referencing $ */ } )(jQuery);
6.思考以下兩個函數。它們會返回相同的東西嗎? 為什麼相同或為什麼不相同?
function test1()
{
return {
text: "something"
};
}
function test2()
{
return
{
text: "something"
};
}
test1()
//logs {text: "something"}
test2()
//logs undefined
因為分號在 JavaScript 中是一個可選項(儘管省略它們通常是非常糟糕的形式)。其結果就是,當碰到 test2()中包含 return語句的代碼行(代碼行上沒有其他任何代碼),分號會立即自動插入到返回語句之後。也不會拋出錯誤,因為代碼的其餘部分是完全有效的,即使它沒有得到調用或做任何事情(相當於它就是是一個未使用的代碼塊,定義了等同於字元串 "something"的屬性 text)。這種行為也支持放置左括弧於 JavaScript 代碼行的末尾,而不是新代碼行開頭的約定。
7.NaN 是什麼?它的類型是什麼?你如何可靠地測試一個值是否等於 NaN ?
NaN 屬性是代表非數字值的特殊值。這個特殊的值是因為運算不能執行而導致的,不能執行的原因要麼是因為其中的運算對象之一非數字,要麼是因為運算的結果非數字(例如,除數為零)。
NaN 雖然不是一個“數字”,但是它的類型是 Number。
console.log(typeof NaN === "number"); // logs "true"
NaN 與所有值都不相等,包括它自己。所以需要使用 isNaN() 來判斷一個值是否是數字。
8.下麵的代碼控制台將輸出什麼,為什麼?
console.log(0.1 + 0.2);
console.log(0.1 + 0.2 == 0.3);//logs 0.30000000000000004/false
JavaScript 採用 IEEE754 標准定義的 64 位浮點格式表示數字,並使用IEEE 754標準《二進位浮點數演算法》。由於存在二進位和十進位的轉換問題,具體的位數會發生變化,因此在 JavaScript 中小數做四則運算時,精度會丟失。
9.寫一個 isInteger函數,用於判斷 x 是否是整數。
ECMAScript 6引入了一個新的用來判斷給定的參數是否為整數的 Number.isInteger() 函數。註意 NaN 和正負 Infinity 不是整數。而 ECMAScript6 之前的解決方法如下:
function isInteger(x) { return (x^0) === x; }
或者:
function isInteger(x) { return Math.round(x) === x; }
//Math.ceil() 和 Math.floor() 在上面的實現中等同於 Math.round()
或者:
function isInteger(x) { return (typeof x === 'number') && (x % 1 === 0);
//以parseInt(string, radix)為基礎的方法在string取許多值時都能工作良好,但string取值相當大的時候,就會無法正常工作。
10.寫一個 test方法,在使用下麵任一語法調用時,都可以正常工作。
console.log(test(2,3)); // logs 5
console.log(test(2)(3)); // logs 5
首先在 JavaScript 中,函數可以提供到 arguments 對象的訪問,arguments 對象提供對傳遞到函數的實際參數的訪問。所以可使用 length 屬性來確定傳遞給函數的參數數量。其次 JavaScript 不要求參數的數目匹配函數定義中的參數數量。多則被忽略。而缺少的則在引用時會給一個 undefined值。因此對應的兩種方法分別為:
//方法一:
function test(x) {
if (arguments.length == 2) {
return arguments[0] + arguments[1];
} else {
return function(y) { return x + y; };
}
}
//方法二:
function test(x, y) {
if (y !== undefined) {
return x + y;
} else {
return function(y) { return x + y; };
}
}
11.寫一個簡單的函數,要求返回一個布爾值指明字元串是否為迴文結構。例:
function isPalindrome(str) {
str = str.replace(/W/g, '').toLowerCase();
return (str == str.split('').reverse().join(''));
}
或者用 for迴圈 來處理:
function isPalindrome(str) {
var i = 0,
newStr = str.replace(/[\W_]/g).toLowerCase();
for ( i ; i< newStr.length ;i++) {
return ( newStr[i] === newStr[newStr.length-1-i])
}
}
12.創建一個給定頁面上的一個DOM元素,就會去訪問元素本身及其所有子元素(不只是它的直接子元素)的函數。對於每個被訪問的元素,函數應該傳遞元素到提供的回調函數。例:
function test(element,callback) {
callback(element);
var list = element.children;
for (var i = 0; i < list.length; i++) {
test(list[i],callback); // recursive call
}
}
13.下麵的代碼控制台將輸出什麼,為什麼?
var obj = {
name: "Anani",
getName: function (){
return this.name;
}
};
var stoleName = obj.getName;
console.log(stoleName());//logs undefined
console.log(obj.getName());//logs Anani
當函數以對象里的方法的方式調用函數時,它們的 this 是調用該函數的對象。所以後者的 this 指向對象 obj,在其中找到了變數 name 的值並輸出。而前者 getName 函數被賦值到了另一個變數中,並沒有作為 obj 的一個屬性被調用,那麼 this 的值就是 window,而變數 name 在全局環境中沒有定義,所以輸出 undefined。
14.下列代碼行1-4如何排序,使之能夠在執行代碼時輸出到控制台? 為什麼?
(function() {
console.log(1);
setTimeout(function(){console.log(2)}, 1000);
setTimeout(function(){console.log(3)}, 0);
console.log(4);
})();
序號為:1、4、3、2。1 和 4 沒有任何延遲輸出的,所以放在前面,2 之所以放在 3 的後面,是因為輸出 2 的延遲更久。
15.下麵的代碼控制台將輸出什麼,為什麼?
(function(x) {
return (function(y) {
console.log(x);
})(2)
})(1);//logs 1
在Javascript語言中,內部函數可以直接讀取外部函數的變數。所以 x 雖然在函數內部中未定義,卻能在外部函數中取得變數 x 的值。
16.下麵的代碼控制台將輸出什麼,為什麼?
console.log(
(function test(x){
return ((x > 1) ? x * test(x-1) : x)
})(10)
);//logs 3628800
函數 test 遞歸地調用本身,這也是遞歸函數的經典用法,用來實現階乘。當 x=1 時,函數結束並輸出 10 的階乘即:3628800。
17.JavaScript中的“閉包”是什麼?並舉例?
閉包是指有權訪問另一個函數作用域(當某個函數被調用時,會創建一個執行環境及相應的作用域鏈。)中的變數的函數。例:
function demo(){
var name = "Anani";
return function displayName(){
console.log(name);
}
}
var demoTest = demo();
demoTest();//閉包
更多關於閉包可以閱讀這篇文章 JavaScript 閉包。
18.下麵的代碼控制台將輸出什麼,為什麼?
var a={},
b={text:'b'},
c={text:'c'};
a[b]="something";
a[c]="other something";
console.log(a[b]);//logs other something
當設置對象屬性時,JavaScript 會字元串化參數值。由於 b 和 c 都是對象,因此它們都將被轉換為"[object Object]"。所以 a[b] 和 a[c] 均相當於 a["[object Object]"] ,並可以互換使用。於是設置或引用 a[c] 或 a[b] 完全相同。
19.下麵的代碼控制台將輸出什麼,為什麼?
console.log(false == '0')//logs true
console.log(false === '0')//logs false
在 JavaScript 中使用運算符”==“時,兩邊值類型不同的時候,會先進行類型轉換,再比較。而運算符”===“表示嚴格等於,只當類型和值都相等時返回”true“。
20.下麵的代碼控制台將輸出什麼,為什麼?
console.log("0 || 1 = "+(0 || 1));
console.log("1 || 2 = "+(1 || 2));
console.log("0 && 1 = "+(0 && 1));
console.log("1 && 2 = "+(1 && 2));
//logs 0 || 1 = 1
//logs 1 || 2 = 1
//logs 0 && 1 = 0
//logs 1 && 2 = 2
如果布爾對象無初始值或者其值為:
- 0
- -0
- null
- ""
- false
- undefined
- NaN
那麼對象的值為 false。否則,其值為 true(即使當變數值為字元串 "false" 時)。“||”運算如果為第一個為:true,則取第一個的值,如果第一個為false,則取第二個的值,也就是常說的”短路“原理。同樣“&&” 運算如果為第一個為:true,則取第二個的值,如果第一個為false,則取第一個的值。
21.下麵的代碼控制台將輸出什麼,為什麼?
for (var i = 0; i < 3; i++) {
setTimeout(function() { console.log(i); }, i * 1000 );
}//logs 3,3,3
因為 settimeout 是非同步執行,會在指定的時間後往任務隊列裡面添加一個任務,只有主線上的任務全部執行完,才會執行任務隊列里的任務,而當主線執行完成後,迴圈中的值已經變成了 3,所以幾個函數取得的 i 的值皆為 3。
如果要得到預期的輸出值:0,1 和 2,其中一個解決辦法就是為每個函數創建一個額外的封閉環境來保存每一次迴圈對應產生的 i 的值:
for (var i = 0; i < 3; i++) {
(function(x) {
setTimeout(function() { console.log(x); }, x * 1000 );
})(i);
}//logs 0,1,2
另外,一個非常簡單的辦法就是使用 let 來代替 var,因為 let 聲明的是塊級作用域,因此每次 for-loop 的迭代都會創建一個新的標識符綁定。例:
for (let i = 0; i < 3; i++) {
setTimeout(function() { console.log(i); }, i * 1000 );
}//logs 0,1,2
22.根據下麵的代碼片段,回答相應的問題。
for (var i = 0; i < 3; i++) {
var btn = document.createElement('button');
btn.appendChild(document.createTextNode('Button ' + i));
btn.addEventListener('click', function(){ console.log(i); });
document.body.appendChild(btn);
}
- 當用戶點擊“Button 2”的時候會輸出什麼到控制台,為什麼?
答:無論用戶點擊什麼按鈕,數字 3 將總會輸出到控制台。這是因為,當 onclick 方法被調用(對於任何按鈕)的時候, for 迴圈已經結束,變數 i 已經獲得了 3 的值。 - 提供一個或多個備用的可按預期工作的解決方案。
解答的思路大致同 21題。因為 Javascript 在 ES6 之前沒有塊級作用域,匿名函數中訪問的 i 是全局作用域中的 i,其值在迴圈後為 3。所以要得到預期的結果需要創建一個新的作用域用於保存每一次迴圈後對應產生的 i 的值。例:
//方法1 創建一個新的作用域:
for (var i = 0; i < 3; i++) {
var btn = document.createElement('button');
btn.appendChild(document.createTextNode('Button ' + i));
btn.addEventListener('click', (function(i) {
return function() { console.log(i); };
})(i));
document.body.appendChild(btn);
}
//方法2 封裝全部調用到在新匿名函數中的 btn.addEventListener:
for (var i = 0; i < 3; i++) {
var btn = document.createElement('button');
btn.appendChild(document.createTextNode('Button ' + i));
(function (i) {
btn.addEventListener('click', function() { console.log(i); });
})(i);
document.body.appendChild(btn);
}
//方法3 用數組對象的本地 forEach 方法來替代 for 迴圈:
['x', 'y', 'z'].forEach(function (value, i) {
var btn = document.createElement('button');
btn.appendChild(document.createTextNode('Button ' + i));
btn.addEventListener('click', function() { console.log(i); });
document.body.appendChild(btn);
});
23.下麵的代碼控制台將輸出什麼,為什麼?
var arr1 = "john".split('');
var arr2 = arr1.reverse();
var arr3 = "jones".split('');
arr2.push(arr3);
console.log("array 1.length:" + arr1.length + " result:" + arr1.slice(-1));
//logs array 1.length:5 result:j,o,n,e,s
console.log("array 2.length:" + arr2.length + " result:" + arr2.slice(-1));
//logs array 2.length:5 result:j,o,n,e,s
console.log(arr1===arr2);//logs true
- 調用數組對象的 reverse() 方法反轉數組的元素順序。
- reverse() 方法返回一個到數組本身的引用。在此,arr2 僅僅是一個到 arr1的引用。因為 arr1 和 arr2 引用的是同一個對象。所以對其中一個做什麼事情都會影響彼此。
- 傳遞數組到另一個數組的 push() 方法會讓整個數組作為單個元素映射到數組的末端。
24.下麵的代碼控制台將輸出什麼,為什麼?
console.log(1 + "2" + "2");//logs 122
console.log(1 + +"2" + "2");//logs 32
console.log(1 + -"1" + "2");//logs 02
console.log(+"1" + "1" + "2");//logs 112
console.log( "a" - "b" + "2");//logs NaN2
console.log( "a" - "b" + 2);//logs NaN
JavaScript 是一種數據類型是非常弱的弱類型語言。在使用算術運算符時,它可對值進行自動類型轉換,以適應正在執行的操作。字元串和數字相加結果是字元串,而-, *, /,和%等算術運算符都會把操作數轉換成數字。在第三行和第四行的代碼中,都含有一元運算符(位於其操作數前面,計算其操作數的數值,如果操作數不是一個數值,會嘗試將其轉換成一個數值)。而且一元運算符的優先順序高於加減運算符,所以對於第三行代碼,要執行的第一個運算是 +"2"(第一個 "2" 前面的額外 + 被視為一元運算符)。因此,JavaScript將 "2" 的類型轉換為數字,然後應用一元 + 號(即,將其視為一個正數)。其結果是,接下來的運算就是 1 + 2 ,這當然是 3。然後我們需要在一個數字和一個字元串之間進行運算(即, 3 和 "2"),同樣的,JavaScript會將數值類型轉換為字元串,並執行字元串的連接,產生 "32"。第四行代碼同理。
25.下麵的遞歸代碼在數組列表偏大的情況下會導致堆棧溢出。在保留遞歸模式的基礎上,怎麼解決這個問題?
var list = readHugeArray();
var nextListItem = function() {
var item = list.pop();
if (item) {
// process the list item...
nextListItem();
}
};
//避免方法:
var list = readHugeArray();
var nextListItem = function() {
var item = list.pop();
if (item) {
// process the list item...
setTimeout( nextListItem, 0);
}
};
首先要瞭解導致堆棧溢出的原因,函數調用的參數是通過棧空間來傳遞的,在調用過程中會占用線程的棧資源。而遞歸調用,只有走到最後的結束點後函數才能依次退出,而未到達最後的結束點之前,占用的棧空間一直沒有釋放,如果遞歸調用次數過多,就可能導致占用的棧資源超過線程的最大值,從而導致棧溢出,導致程式的異常退出。
提供的解決方法使得堆棧溢出之所以會被消除,是因為事件迴圈操縱了遞歸,而不是調用堆棧。當 nextListItem 運行時,如果 item不為空,timeout函數(nextListItem)就會被推到事件隊列,該函數退出,因此就清空調用堆棧。當事件隊列運行其timeout事件,且進行到下一個 item 時,定時器被設置為再次調用 nextListItem。因此,該方法從頭到尾都沒有直接的遞歸調用,所以無論迭代次數的多少,調用堆棧保持清空的狀態。