javascript面試題精講(二)

閒話少敘,直接上代碼~

1、使用typeof bar ===“object”來確定bar是否是一個對象時有什麼潛在的缺陷?這個陷阱如何避免?

儘管typeof bar ===“object”是檢查bar是否是對象的可靠方法,但JavaScript中令人驚訝的問題是null也被認爲是一個對象!

因此,對於大多數開發人員來說,下面的代碼會將真實(而不是錯誤)記錄到控制檯:

var bar = null;
console.log(typeof bar === "object");  // logs true!

只要知道這一點,就可以通過檢查bar是否爲空來輕鬆避免該問題:

console.log((bar !== null) && (typeof bar === "object"));  // logs false

爲了在我們的答案更加的完整,還有兩件事值得注意:

首先,如果bar是一個函數,上面的解決方案將返回false。在大多數情況下,這是所期望的行爲,但是在您希望函數返回true的情況下,您可以將上述解決方案修改爲:

console.log((bar !== null) && ((typeof bar === "object") || (typeof bar === "function")));

其次,如果bar是數組,則上述解決方案將返回true(例如,如果var bar = [];)。在大多數情況下,這是所希望的行爲,因爲數組確實是對象,但是在您想要對數組也是false的情況下,可以將上述解決方案修改爲:

console.log((bar !== null) && (typeof bar === "object") && (toString.call(bar) !== "[object Array]"));

但是,還有一個替代方法對空值,數組和函數返回false,但對於對象則爲true:

console.log((bar !== null) && (bar.constructor === Object));

或者,如果您使用jQuery:

console.log((bar !== null) && (typeof bar === "object") && (! $.isArray(bar)));

ES5使得數組的情況非常簡單,包括它自己的空檢查:

console.log(Array.isArray(bar));

2、下面的代碼將輸出到控制檯的是什麼,爲什麼?

(function(){
  var a = b = 3;
})();

console.log("a defined? " + (typeof a !== 'undefined'));
console.log("b defined? " + (typeof b !== 'undefined'));

由於a和b都在函數的封閉範圍內定義,並且由於它們所在的行以var關鍵字開頭,因此大多數JavaScript開發人員會希望typeof a和typeof b在上面的示例中都未定義。

但是,情況並非如此。這裏的問題是大多數開發人員錯誤地理解語句var a = b = 3;以下簡寫爲:

var b = 3;
var a = b;

但實際上,var a = b = 3;其實是速記:

b = 3;
var a = b;

因此(如果您不使用嚴格模式),代碼片段的輸出將爲:

a defined? false
b defined? true

但是如何在封閉函數的範圍之外定義b?那麼,因爲聲明var a = b = 3;是語句b = 3的簡寫;並且var a = b; b最終成爲一個全局變量(因爲它不在var關鍵字後面),因此它仍然在作用域內,即使在封閉函數之外。

注意,在嚴格模式下(即,使用strict),語句var a = b = 3;會產生一個ReferenceError的運行時錯誤:b沒有定義,從而避免了可能導致的任何頭headfakes/bugs。 (這就是爲什麼你應該在你的代碼中使用strict,一個重要的例子!)

3、下面的代碼將輸出到控制檯的是什麼?,爲什麼?

var myObject = {
    foo: "bar",
    func: function() {
        var self = this;
        console.log("outer func:  this.foo = " + this.foo);
        console.log("outer func:  self.foo = " + self.foo);
        (function() {
            console.log("inner func:  this.foo = " + this.foo);
            console.log("inner func:  self.foo = " + self.foo);
        }());
    }
};
myObject.func();

以上代碼將輸出到控制檯:

outer func:  this.foo = bar
outer func:  self.foo = bar
inner func:  this.foo = undefined
inner func:  self.foo = bar

在外部函數中,this和self都引用myObject,因此都可以正確地引用和訪問foo。

但在內部函數中,這不再指向myObject。因此,this.foo在內部函數中是未定義的,而對局部變量self的引用仍然在範圍內並且可以在那裏訪問。

4、在功能塊中封裝JavaScript源文件的全部內容的重要性和原因是什麼?

這是一種日益普遍的做法,被許多流行的JavaScript庫(jQuery,Node.js等)所採用。這種技術在文件的全部內容周圍創建一個閉包,這可能最重要的是創建一個私有名稱空間,從而有助於避免不同JavaScript模塊和庫之間的潛在名稱衝突。

這種技術的另一個特點是爲全局變量提供一個容易引用(可能更短)的別名。例如,這通常用於jQuery插件。 jQuery允許您使用jQuery.noConflict()來禁用對jQuery名稱空間的$引用。如果這樣做了,你的代碼仍然可以使用$使用閉包技術,如下所示:

(function($) { /* jQuery plugin code referencing $ */ } )(jQuery);

5、在JavaScript源文件的開頭包含'use strict'的意義和有什麼好處?

這裏最簡單也是最重要的答案是use strict是一種在運行時自動執行更嚴格的JavaScript代碼解析和錯誤處理的方法。如果代碼錯誤被忽略或失敗,將會產生錯誤或拋出異常。總的來說,這是一個很好的做法。

嚴格模式的一些主要優點包括:

  • 使調試更容易。 如果代碼錯誤本來會被忽略或失敗,那麼現在將會產生錯誤或拋出異常,從而更快地發現代碼中的問題,並更快地指引它們的源代碼。
  • 防止意外全局。 如果沒有嚴格模式,將值賦給未聲明的變量會自動創建一個具有該名稱的全局變量。這是JavaScript中最常見的錯誤之一。在嚴格模式下,嘗試這樣做會引發錯誤。
  • 消除隱藏威脅。在沒有嚴格模式的情況下,對null或undefined的這個值的引用會自動強制到全局。這可能會導致許多headfakespull-out-your-hair類型的錯誤。在嚴格模式下,引用null或undefined的這個值會引發錯誤。
  • 不允許重複的參數值。 嚴格模式在檢測到函數的重複命名參數(例如,函數foo(val1,val2,val1){})時會引發錯誤,從而捕獲代碼中幾乎可以肯定存在的錯誤,否則您可能會浪費大量的時間追蹤。
    • 注意:它曾經是(在ECMAScript 5中)strict模式將禁止重複的屬性名稱(例如var object = {foo:“bar”,foo:“baz”};)但是從ECMAScript 2015 開始,就不再有這種情況了。

 

  • 使eval()更安全。 eval()在嚴格模式和非嚴格模式下的行爲方式有些不同。最重要的是,在嚴格模式下,在eval()語句內部聲明的變量和函數不會在包含範圍中創建(它們是以非嚴格模式在包含範圍中創建的,這也可能是問題的常見來源)。
  • 拋出無效的使用錯誤的刪除符。 刪除操作符(用於從對象中刪除屬性)不能用於對象的不可配置屬性。當試圖刪除一個不可配置的屬性時,非嚴格代碼將自動失敗,而在這種情況下,嚴格模式會引發錯誤。

6、考慮下面的兩個函數。他們都會返回同樣的值嗎?爲什麼或者爲什麼不?

function foo1()
{
  return {
      bar: "hello"
  };
}

function foo2()
{
  return
  {
      bar: "hello"
  };
}

令人驚訝的是,這兩個函數不會返回相同的結果。而是:

console.log("foo1 returns:");
console.log(foo1());
console.log("foo2 returns:");
console.log(foo2());

會產生:

foo1 returns:
Object {bar: "hello"}
foo2 returns:
undefined 

這不僅令人驚訝,而且特別令人煩惱的是,foo2()返回未定義而沒有引發任何錯誤。

原因與JavaScript中分號在技術上是可選的事實有關(儘管忽略它們通常是非常糟糕的形式)。因此,在foo2()中遇到包含return語句的行(沒有其他內容)時,會在return語句之後立即自動插入分號。

由於代碼的其餘部分是完全有效的,即使它沒有被調用或做任何事情(它只是一個未使用的代碼塊,它定義了一個屬性欄,它等於字符串“hello”),所以不會拋出任何錯誤。

這種行爲也被認爲是遵循了在JavaScript中將一行開頭大括號放在行尾的約定,而不是在新行的開頭。如此處所示,這不僅僅是JavaScript中的一種風格偏好。

7、什麼是NaN?它的類型是什麼?如何可靠地測試一個值是否等於NaN?

NaN屬性表示“不是數字”的值。這個特殊值是由於一個操作數是非數字的(例如“abc”/ 4)或者因爲操作的結果是非數字而無法執行的。

雖然這看起來很簡單,但NaN有一些令人驚訝的特徵,如果人們沒有意識到這些特徵,就會導致bug。

一方面,雖然NaN的意思是“不是數字”,但它的類型是,數字:

console.log(typeof NaN === "number");  // logs "true"

此外,NaN相比任何事情 - 甚至本身! - 是false:

console.log(NaN === NaN);  // logs "false"

測試數字是否等於NaN的半可靠方法是使用內置函數isNaN(),但即使使用isNaN()也不是一個好的解決方案。.

一個更好的解決方案要麼是使用value!==值,如果該值等於NaN,那麼只會生成true。另外,ES6提供了一個新的Number.isNaN()函數 ,它與舊的全局isNaN()函數不同,也更加可靠。

8、下面的代碼輸出什麼?解釋你的答案。

console.log(0.1 + 0.2);
console.log(0.1 + 0.2 == 0.3);

對這個問題的一個有教養的回答是:“你不能確定。它可能打印出0.3和true,或者可能不打印。 JavaScript中的數字全部用浮點精度處理,因此可能不會總是產生預期的結果。“

上面提供的示例是演示此問題的經典案例。令人驚訝的是,它會打印出來:

0.30000000000000004
false

一個典型的解決方案是比較兩個數字與特殊常數Number.EPSILON之間的絕對差值:

function areTheNumbersAlmostEqual(num1, num2) {
    return Math.abs( num1 - num2 ) < Number.EPSILON;
}
console.log(areTheNumbersAlmostEqual(0.1 + 0.2, 0.3));

討論寫函數的可能方法isInteger(x),它確定x是否是一個整數。

這聽起來很平凡,事實上,ECMAscript 6爲此正好引入了一個新的Number.isInteger()函數,這是微不足道的。但是,在ECMAScript 6之前,這有點複雜,因爲沒有提供與Number.isInteger()方法等價的方法。

問題在於,在ECMAScript規範中,整數只在概念上存在;即數值始終作爲浮點值存儲。

考慮到這一點,最簡單,最清潔的ECMAScript-6之前的解決方案(即使將非數字值(例如字符串或空值)傳遞給該函數,該解決方案也具有足夠的可靠性以返回false)將成爲以下用法按位異或運算符:

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); }

一個相當常見的不正確的解決方案如下:

function isInteger(x) { return parseInt(x, 10) === x; }

雖然這個基於parseInt的方法對許多x值很有效,但一旦x變得相當大,它將無法正常工作。問題是parseInt()在解析數字之前將其第一個參數強制轉換爲字符串。因此,一旦數字變得足夠大,其字符串表示將以指數形式呈現(例如1e + 21)。因此,parseInt()將嘗試解析1e + 21,但是當它到達e字符時將停止解析,因此將返回值1.觀察:

> String(1000000000000000000000)
'1e+21'


> parseInt(1000000000000000000000, 10)
1


> parseInt(1000000000000000000000, 10) === 1000000000000000000000
false

9、執行下面的代碼時,按什麼順序將數字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,因爲它們是通過簡單調用console.log()而沒有任何延遲記錄的
  • 在3之後顯示,因爲在延遲1000毫秒(即1秒)之後記錄2,而在0毫秒的延遲之後記錄3。

好的。但是,如果在延遲0毫秒後記錄3,這是否意味着它正在被立即記錄?而且,如果是這樣,不應該在4之前記錄它,因爲4是由後面的代碼行記錄的嗎?

答案與正確理解JavaScript事件和時間有關。 .

瀏覽器有一個事件循環,它檢查事件隊列並處理未決事件。例如,如果在瀏覽器繁忙時(例如,處理onclick)在後臺發生事件(例如腳本onload事件),則該事件被附加到隊列中。當onclick處理程序完成時,將檢查隊列並處理該事件(例如,執行onload腳本)。

同樣,如果瀏覽器繁忙,setTimeout()也會將其引用函數的執行放入事件隊列中。

當值爲零作爲setTimeout()的第二個參數傳遞時,它將嘗試“儘快”執行指定的函數。具體來說,函數的執行放置在事件隊列中,以在下一個計時器滴答時發生。但請注意,這不是直接的;該功能不會執行,直到下一個滴答聲。這就是爲什麼在上面的例子中,調用console.log(4)發生在調用console.log(3)之前(因爲調用console.log(3)是通過setTimeout調用的,所以稍微延遲了一點)。

10、編寫一個簡單的函數(少於160個字符),返回一個布爾值,指示字符串是否是palindrome

如果str是迴文,以下一行函數將返回true;否則,它返回false。

function isPalindrome(str) {
  str = str.replace(/\W/g, '').toLowerCase();
  return (str == str.split('').reverse().join(''));
}

例如:

console.log(isPalindrome("level"));                   // logs 'true'
console.log(isPalindrome("levels"));                  // logs 'false'
console.log(isPalindrome("A car, a man, a maraca"));  // logs 'true'

11、寫一個sum方法,當使用下面的語法調用時它將正常工作。

console.log(sum(2,3));   // Outputs 5
console.log(sum(2)(3));  // Outputs 5

有(至少)兩種方法可以做到這一點:

METHOD 1

function sum(x) {
  if (arguments.length == 2) {
    return arguments[0] + arguments[1];
  } else {
    return function(y) { return x + y; };
  }
}

在JavaScript中,函數提供對參數對象的訪問,該對象提供對傳遞給函數的實際參數的訪問。這使我們能夠使用length屬性在運行時確定傳遞給函數的參數的數量

如果傳遞兩個參數,我們只需將它們相加並返回。

否則,我們假設它是以sum(2)(3)的形式被調用的,所以我們返回一個匿名函數,它將傳遞給sum()(在本例中爲2)的參數和傳遞給匿名函數的參數這種情況3)。

METHOD 2

function sum(x, y) {
  if (y !== undefined) {
    return x + y;
  } else {
    return function(y) { return x + y; };
  }
}

當函數被調用時,JavaScript不需要參數的數量來匹配函數定義中參數的數量。如果傳遞的參數數量超過了函數定義中參數的數量,則超出的參數將被忽略。另一方面,如果傳遞的參數數量少於函數定義中的參數數量,則在函數內引用時,缺少的參數將具有未定義的值。因此,在上面的例子中,通過簡單地檢查第二個參數是否未定義,我們可以確定函數被調用的方式並相應地繼續。

12、考慮下面的代碼片段

for (var i = 0; i < 5; i++) {
  var btn = document.createElement('button');
  btn.appendChild(document.createTextNode('Button ' + i));
  btn.addEventListener('click', function(){ console.log(i); });
  document.body.appendChild(btn);
}

(a) 當用戶點擊“按鈕4”時,什麼被記錄到控制檯?爲什麼?

(b) 提供一個或多個可按預期工作的替代實現。

答:

(a) 無論用戶點擊哪個按鈕,數字5將始終記錄到控制檯。這是因爲,在調用onclick方法(對於任何按鈕)時,for循環已經完成,並且變量i已經具有值5.(如果受訪者知道足夠的話就可以獲得獎勵點數關於執行上下文,變量對象,激活對象和內部“範圍”屬性如何影響閉包行爲。)

(b) 使這項工作的關鍵是通過將它傳遞給新創建的函數對象來捕獲每次通過for循環的i的值。以下是四種可能的方法來實現這一點:

for (var i = 0; i < 5; 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);
}

或者,您可以將新的匿名函數中的整個調用包裝爲btn.addEventListener:

for (var i = 0; i < 5; 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);
}

或者,我們可以通過調用數組對象的原生forEach方法來替換for循環:

['a', 'b', 'c', 'd', 'e'].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);
});

最後,最簡單的解決方案,如果你在ES6 / ES2015上下文中,就是使用let i而不是var i:

for (let i = 0; i < 5; i++) {
  var btn = document.createElement('button');
  btn.appendChild(document.createTextNode('Button ' + i));
  btn.addEventListener('click', function(){ console.log(i); });
  document.body.appendChild(btn);
}

13、假設d是範圍內的“空”對象:

var d = {};

...使用下面的代碼完成了什麼?

[ 'zebra', 'horse' ].forEach(function(k) {
    d[k] = undefined;
});

上面顯示的代碼片段在對象d上設置了兩個屬性。理想情況下,對具有未設置鍵的JavaScript對象執行的查找評估爲未定義。但是運行這段代碼會將這些屬性標記爲對象的“自己的屬性”。

這是確保對象具有一組給定屬性的有用策略。將該對象傳遞給Object.keys將返回一個包含這些設置鍵的數組(即使它們的值未定義)。

14、下面的代碼將輸出到控制檯,爲什麼?

var arr1 = "john".split('');
var arr2 = arr1.reverse();
var arr3 = "jones".split('');
arr2.push(arr3);
console.log("array 1: length=" + arr1.length + " last=" + arr1.slice(-1));
console.log("array 2: length=" + arr2.length + " last=" + arr2.slice(-1));

記錄的輸出將是:

"array 1: length=5 last=j,o,n,e,s"
"array 2: length=5 last=j,o,n,e,s"

arr1和arr2是相同的(即['n','h','o','j',['j','o','n','e','s']])上述代碼由於以下原因而被執行:

  • 調用數組對象的reverse()方法不僅以相反的順序返回數組,它還顛倒了數組本身的順序(即在這種情況下,arr1)。
  • reverse()方法返回對數組本身的引用(即,在這種情況下爲arr1)。因此,arr2僅僅是對arr1的引用(而不是副本)。因此,當對arr2做任何事情時(即,當我們調用arr2.push(arr3);)時,arr1也會受到影響,因爲arr1和arr2只是對同一個對象的引用。

這裏有幾個觀點可以讓人們回答這個問題:

  • 將數組傳遞給另一個數組的push()方法會將整個數組作爲單個元素推入數組的末尾。結果,聲明arr2.push(arr3);將arr3作爲一個整體添加到arr2的末尾(即,它不連接兩個數組,這就是concat()方法的用途)。
  • 像Python一樣,JavaScript在調用像slice()這樣的數組方法時,會承認負面下標,以此作爲在數組末尾引用元素的方式;例如,下標-1表示數組中的最後一個元素,依此類推。

15、下面的代碼將輸出到控制檯,爲什麼?

console.log(1 +  "2" + "2");
console.log(1 +  +"2" + "2");
console.log(1 +  -"1" + "2");
console.log(+"1" +  "1" + "2");
console.log( "A" - "B" + "2");
console.log( "A" - "B" + 2);

以上代碼將輸出到控制檯:

"122"
"32"
"02"
"112"
"NaN2"
NaN

這是爲什麼...

這裏的基本問題是JavaScript(ECMAScript)是一種鬆散類型的語言,它對值執行自動類型轉換以適應正在執行的操作。讓我們來看看這是如何與上面的每個例子進行比較。

示例1:1 +“2”+“2”輸出:“122”說明:第一個操作在1 +“2”中執行。由於其中一個操作數(“2”)是一個字符串,所以JavaScript假定需要執行字符串連接,因此將1的類型轉換爲“1”,1 +“2”轉換爲“12”。然後,“12”+“2”產生“122”。

示例2:1 + +“2”+“2”輸出:“32”說明:根據操作順序,要執行的第一個操作是+“2”(第一個“2”之前的額外+被視爲一個一元運算符)。因此,JavaScript將“2”的類型轉換爲數字,然後將一元+符號應用於它(即將其視爲正數)。結果,下一個操作現在是1 + 2,當然這會產生3.但是,我們有一個數字和一個字符串之間的操作(即3和“2”),所以JavaScript再次轉換數值賦給一個字符串並執行字符串連接,產生“32”。

示例3:1 + - “1”+“2”輸出:“02”說明:這裏的解釋與前面的示例相同,只是一元運算符是 - 而不是+。因此,“1”變爲1,然後在應用 - 時將其變爲-1,然後將其加1到產生0,然後轉換爲字符串並與最終的“2”操作數連接,產生“02”。

示例4:+“1”+“1”+“2”輸出:“112”說明:儘管第一個“1”操作數是基於其前面的一元+運算符的數值類型轉換的,當它與第二個“1”操作數連接在一起時返回一個字符串,然後與最終的“2”操作數連接,產生字符串“112”。

示例5:“A” - “B”+“2”輸出:“NaN2”說明:由於 - 運算符不能應用於字符串,並且既不能將“A”也不能將“B”轉換爲數值, “ - ”B“產生NaN,然後​​與字符串”2“串聯產生”NaN2“。

例6:“A” - “B”+2輸出:NaN說明:在前面的例子中,“A” - “B”產生NaN。但是任何運算符應用於NaN和其他數字操作數仍然會產生NaN。

16、如果數組列表太大,以下遞歸代碼將導致堆棧溢出。你如何解決這個問題,仍然保留遞歸模式?

var list = readHugeList();

var nextListItem = function() {
    var item = list.pop();

    if (item) {
        // process the list item...
        nextListItem();
    }
};

通過修改nextListItem函數可以避免潛在的堆棧溢出,如下所示:

var list = readHugeList();

var nextListItem = function() {
    var item = list.pop();

    if (item) {
        // process the list item...
        setTimeout( nextListItem, 0);
    }
};

堆棧溢出被消除,因爲事件循環處理遞歸,而不是調用堆棧。當nextListItem運行時,如果item不爲null,則將超時函數(nextListItem)推送到事件隊列,並且函數退出,從而使調用堆棧清零。當事件隊列運行超時事件時,將處理下一個項目,並設置一個計時器以再次調用nextListItem。因此,該方法從頭到尾不經過直接遞歸調用即可處理,因此調用堆棧保持清晰,無論迭代次數如何。

17、什麼是JavaScript中的“閉包”?舉一個例子。

閉包是一個內部函數,它可以訪問外部(封閉)函數的作用域鏈中的變量。閉包可以訪問三個範圍內的變量;具體來說:(1)變量在其自己的範圍內,(2)封閉函數範圍內的變量,以及(3)全局變量。

這裏是一個例子:

var globalVar = "xyz";

(function outerFunc(outerArg) {
    var outerVar = 'a';

    (function innerFunc(innerArg) {
    var innerVar = 'b';

    console.log(
        "outerArg = " + outerArg + "\n" +
        "innerArg = " + innerArg + "\n" +
        "outerVar = " + outerVar + "\n" +
        "innerVar = " + innerVar + "\n" +
        "globalVar = " + globalVar);

    })(456);
})(123);

在上面的例子中,innerFunc,outerFunc和全局名稱空間的變量都在innerFunc的範圍內。上面的代碼將產生以下輸出:

outerArg = 123
innerArg = 456
outerVar = a
innerVar = b
globalVar = xyz

18、以下代碼的輸出是什麼:

for (var i = 0; i < 5; i++) {
    setTimeout(function() { console.log(i); }, i * 1000 );
}

解釋你的答案。如何在這裏使用閉包?

顯示的代碼示例不會顯示值0,1,2,3和4,這可能是預期的;而是顯示5,5,5,5。

這是因爲循環內執行的每個函數將在整個循環完成後執行,因此所有函數都會引用存儲在i中的最後一個值,即5。

通過爲每次迭代創建一個唯一的作用域 ,可以使用閉包來防止這個問題,並將該變量的每個唯一值存儲在其作用域中,如下所示:

for (var i = 0; i < 5; i++) {
    (function(x) {
        setTimeout(function() { console.log(x); }, x * 1000 );
    })(i);
}

這會產生將0,1,2,3和4記錄到控制檯的可能結果。

ES2015上下文中,您可以在原始代碼中簡單地使用let而不是var:

for (let i = 0; i < 5; i++) {
    setTimeout(function() { console.log(i); }, i * 1000 );
}

19、以下幾行代碼輸出到控制檯?

console.log("0 || 1 = "+(0 || 1));
console.log("1 || 2 = "+(1 || 2));
console.log("0 && 1 = "+(0 && 1));
console.log("1 && 2 = "+(1 && 2));

解釋你的答案。

該代碼將輸出以下四行:

0 || 1 = 1
1 || 2 = 1
0 && 1 = 0
1 && 2 = 2

在JavaScript中,都是||和&&是邏輯運算符,當從左向右計算時返回第一個完全確定的“邏輯值”。

或(||)運算符。在形式爲X || Y的表達式中,首先計算X並將其解釋爲布爾值。如果此布爾值爲真,則返回true(1),並且不計算Y,因爲“或”條件已經滿足。但是,如果此布爾值爲“假”,我們仍然不知道X || Y是真還是假,直到我們評估Y,並將其解釋爲布爾值。

因此,0 || 1評估爲真(1),正如1 || 2。

和(&&)運算符。在X && Y形式的表達式中,首先評估X並將其解釋爲布爾值。如果此布爾值爲false,則返回false(0)並且不評估Y,因爲“and”條件已失敗。但是,如果這個布爾值爲“真”,我們仍然不知道X && Y是真還是假,直到我們評估Y,並將其解釋爲布爾值。

然而,&&運算符的有趣之處在於,當表達式評估爲“真”時,則返回表達式本身。這很好,因爲它在邏輯表達式中被視爲“真”,但也可以用於在您關心時返回該值。這解釋了爲什麼,有點令人驚訝的是,1 && 2返回2(而你可能會期望它返回true或1)。

20 、下面的代碼執行時輸出是什麼?說明。

console.log(false == '0')
console.log(false === '0')

該代碼將輸出:

true
false

在JavaScript中,有兩套相等運算符。三重相等運算符===的行爲與任何傳統的相等運算符相同:如果兩側的兩個表達式具有相同的類型和相同的值,則計算結果爲true。然而,雙等號運算符在比較它們之前試圖強制這些值。因此,通常使用===而不是==。對於!== vs!=也是如此。

21、以下代碼的輸出是什麼?解釋你的答案。

var a={},
    b={key:'b'},
    c={key:'c'};

a[b]=123;
a[c]=456;

console.log(a[b]);

此代碼的輸出將是456(不是123)。

原因如下:設置對象屬性時,JavaScript會隱式地將參數值串聯起來。在這種情況下,由於b和c都是對象,它們都將被轉換爲“[object Object]”。因此,a [b]和a [c]都等價於[“[object Object]”],並且可以互換使用。因此,設置或引用[c]與設置或引用[b]完全相同。

22、以下代碼將輸出到控制檯中.

console.log((function f(n){return ((n > 1) ? n * f(n-1) : n)})(10));

該代碼將輸出10階乘的值(即10!或3,628,800)。

原因如下:

命名函數f()以遞歸方式調用自身,直到它調用f(1),它簡單地返回1.因此,這就是它的作用:

f(1): returns n, which is 1
f(2): returns 2 * f(1), which is 2
f(3): returns 3 * f(2), which is 6
f(4): returns 4 * f(3), which is 24
f(5): returns 5 * f(4), which is 120
f(6): returns 6 * f(5), which is 720
f(7): returns 7 * f(6), which is 5040
f(8): returns 8 * f(7), which is 40320
f(9): returns 9 * f(8), which is 362880
f(10): returns 10 * f(9), which is 3628800

23 、考慮下面的代碼片段。控制檯的輸出是什麼,爲什麼?

(function(x) {
    return (function(y) {
        console.log(x);
    })(2)
})(1);

輸出將爲1,即使x的值從未在內部函數中設置。原因如下:

正如我們的JavaScript招聘指南中所解釋的,閉包是一個函數,以及創建閉包時在範圍內的所有變量或函數。在JavaScript中,閉包被實現爲“內部函數”;即在另一功能的主體內定義的功能。閉包的一個重要特徵是內部函數仍然可以訪問外部函數的變量。

因此,在這個例子中,因爲x沒有在內部函數中定義,所以在外部函數的作用域中搜索一個定義的變量x,該變量的值爲1。

24、以下代碼將輸出到控制檯以及爲什麼

var hero = {
    _name: 'John Doe',
    getSecretIdentity: function (){
        return this._name;
    }
};

var stoleSecretIdentity = hero.getSecretIdentity;

console.log(stoleSecretIdentity());
console.log(hero.getSecretIdentity());

這段代碼有什麼問題,以及如何解決這個問題。

該代碼將輸出:

undefined
John Doe

第一個console.log打印未定義,因爲我們從hero對象中提取方法,所以stoleSecretIdentity()在_name屬性不存在的全局上下文(即窗口對象)中被調用。

修復stoleSecretIdentity()函數的一種方法如下:

var stoleSecretIdentity = hero.getSecretIdentity.bind(hero);

25、創建一個函數,給定頁面上的DOM元素,將訪問元素本身及其所有後代(不僅僅是它的直接子元素)。對於每個訪問的元素,函數應該將該元素傳遞給提供的回調函數。

該函數的參數應該是:

  • 一個 DOM 元素
  • 一個回調函數(以DOM元素作爲參數)

訪問樹中的所有元素(DOM)是[經典的深度優先搜索算法]Depth-First-Search algorithm應用程序。以下是一個示例解決方案:

function Traverse(p_element,p_callback) {
   p_callback(p_element);
   var list = p_element.children;
   for (var i = 0; i < list.length; i++) {
       Traverse(list[i],p_callback);  // recursive call
   }
}

27、在JavaScript中測試您的這些知識:以下代碼的輸出是什麼?

var length = 10;
function fn() {
    console.log(this.length);
}

var obj = {
  length: 5,
  method: function(fn) {
    fn();
    arguments[0]();
  }
};

obj.method(fn, 1);

輸出:

10
2

爲什麼不是10和5?

首先,由於fn作爲函數方法的參數傳遞,函數fn的作用域(this)是窗口。 var length = 10;在窗口級別聲明。它也可以作爲window.length或length或this.length來訪問(當這個===窗口時)。

方法綁定到Object obj,obj.method用參數fn和1調用。雖然方法只接受一個參數,但調用它時已經傳遞了兩個參數;第一個是函數回調,其他只是一個數字。

當在內部方法中調用fn()時,該函數在全局級別作爲參數傳遞,this.length將有權訪問在Object obj中定義的var length = 10(全局聲明)而不是length = 5。

現在,我們知道我們可以使用arguments []數組訪問JavaScript函數中的任意數量的參數。

因此arguments0只不過是調用fn()。在fn裏面,這個函數的作用域成爲參數數組,並且記錄參數[]的長度將返回2。

因此輸出將如上所述。

28、考慮下面的代碼。輸出是什麼,爲什麼?

(function () {
    try {
        throw new Error();
    } catch (x) {
        var x = 1, y = 2;
        console.log(x);
    }
    console.log(x);
    console.log(y);
})();


1
undefined
2

var語句被掛起(沒有它們的值初始化)到它所屬的全局或函數作用域的頂部,即使它位於with或catch塊內。但是,錯誤的標識符只在catch塊內部可見。它相當於:

(function () {
    var x, y; // outer and hoisted
    try {
        throw new Error();
    } catch (x /* inner */) {
        x = 1; // inner x, not the outer one
        y = 2; // there is only one y, which is in the outer scope
        console.log(x /* inner */);
    }
    console.log(x);
    console.log(y);
})();

29、這段代碼的輸出是什麼?

var x = 21;
var girl = function () {
    console.log(x);
    var x = 20;
};
girl ();

21,也不是20,結果是‘undefined’的

這是因爲JavaScript初始化沒有被掛起。

(爲什麼它不顯示21的全局值?原因是當函數執行時,它檢查是否存在本地x變量但尚未聲明它,因此它不會查找全局變量。 )

30、你如何克隆一個對象?

var obj = {a: 1 ,b: 2}
var objclone = Object.assign({},obj);

現在objclone的值是{a:1,b:2},但指向與obj不同的對象。

但請注意潛在的缺陷:Object.clone()只會執行淺拷貝,而不是深拷貝。這意味着嵌套的對象不會被複制。他們仍然引用與原始相同的嵌套對象:

let obj = {
    a: 1,
    b: 2,
    c: {
        age: 30
    }
};

var objclone = Object.assign({},obj);
console.log('objclone: ', objclone);

obj.c.age = 45;
console.log('After Change - obj: ', obj);           // 45 - This also changes
console.log('After Change - objclone: ', objclone); // 45


for (let i = 0; i < 5; i++) {
  setTimeout(function() { console.log(i); }, i * 1000 );
}

31、此代碼將打印什麼?

for (let i = 0; i < 5; i++) {
    setTimeout(function() { console.log(i); }, i * 1000 );
}

它會打印0 1 2 3 4,因爲我們在這裏使用let而不是var。變量i只能在for循環的塊範圍中看到。

32、以下幾行輸出什麼,爲什麼?

console.log(1 < 2 < 3);
console.log(3 > 2 > 1);

第一條語句返回true,如預期的那樣。

第二個返回false是因爲引擎如何針對<和>的操作符關聯性工作。它比較從左到右,所以3> 2> 1 JavaScript翻譯爲true> 1. true具有值1,因此它比較1> 1,這是錯誤的。

33、如何在數組的開頭添加元素?最後如何添加一個?

var myArray = ['a', 'b', 'c', 'd'];
myArray.push('end');
myArray.unshift('start');
console.log(myArray); // ["start", "a", "b", "c", "d", "end"]

使用ES6,可以使用擴展運算符:

myArray = ['start', ...myArray];
myArray = [...myArray, 'end'];

或者,簡而言之:

myArray = ['start', ...myArray, 'end'];
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章