[面試題]關於JavaScript,你瞭解多少?

導讀

都說金九銀十是一個面試的好季節,最近在GitHub上看到了一些關於前端的面試題,也比較基礎,在這裏整理了一下,由於內容較多,在這裏分爲HTMLCSSJavaScript三篇,但願能夠對你們有所幫助,趁着「好季節」,找到本身心儀的工做,固然也包括我本身在內,你們一塊兒加油哈!javascript

由於掘金的某條沸點,在這裏先說明一下,本篇文章適合目前正在找工做或者以爲本身的基礎不太紮實的小夥伴,大神就跳過吧O(∩_∩)O。前端

轉發自 github.com/yangshun/fr…java

1.請解釋事件委託。


事件委託是將事件監聽器添加到父元素,而不是每一個子元素單獨設置事件監聽器。當觸發子元素時,事件會冒泡到父元素,監聽器就會觸發。這種技術的好處是:git

1.內存佔用減小,由於只須要一個父元素的事件處理程序,而沒必要爲每一個後代都添加事件處理程序。
2.無需從已刪除的元素中解綁處理程序,也無需將處理程序綁定到新元素上。github

2.請簡述JavaScript中的this


JS 中的 this 是一個相對複雜的概念,不是簡單幾句能解釋清楚的。粗略地講,函數的調用方式決定了 this 的值。我閱讀了網上不少關於 this 的文章,Arnav Aggrawal 寫的比較清楚。 this 取值符合如下規則:面試

1.在調用函數時使用new關鍵字,函數內的this是一個全新的對象。
2.若是applycallbind方法用於調用、建立一個函數,函數內的 this 就是做爲參數傳入這些方法的對象。
3.當函數做爲對象裏的方法被調用時,函數內的this是調用該函數的對象。好比當obj.method()被調用時,函數內的 this 將綁定到obj對象。
4.若是調用函數不符合上述規則,那麼this的值指向全局對象(global object)。瀏覽器環境下this的值指向window對象,可是在嚴格模式下('use strict')this的值爲undefined
5.若是符合上述多個規則,則較高的規則(1 號最高,4 號最低)將決定this的值。
6.若是該函數是 ES2015 中的箭頭函數,將忽略上面的全部規則,this被設置爲它被建立時的上下文。數據庫

3.請解釋原型繼承的工做原理。


這是一個很是常見的 JavaScript 問題。全部 JS 對象都有一個prototype屬性,指向它的原型對象。當試圖訪問一個對象的屬性時,若是沒有在該對象上找到,它還會搜尋該對象的原型,以及該對象的原型的原型,依次層層向上搜索,直到找到一個名字匹配的屬性或到達原型鏈的末尾。這種行爲是在模擬經典的繼承,可是與其說是繼承,不如說是委託。express

4.說說你對AMDCommonJS的瞭解。


它們都是實現模塊體系的方式,直到ES2015出現以前,JavaScript 一直沒有模塊體系。CommonJS 是同步的,而 AMD(Asynchronous Module Definition)從全稱中能夠明顯看出是異步的。CommonJS 的設計是爲服務器端開發考慮的,而AMD支持異步加載模塊,更適合瀏覽器。編程

我發現AMD的語法很是冗長,CommonJS更接近其餘語言import 聲明語句的用法習慣。大多數狀況下,我認爲AMD沒有使用的必要,由於若是把全部 JavaScript 都捆綁進一個文件中,將沒法獲得異步加載的好處。此外,CommonJS 語法上更接近 Node 編寫模塊的風格,在先後端都使用 JavaScript開發之間進行切換時,語境的切換開銷較小。後端

我很高興看到ES2015 的模塊加載方案同時支持同步和異步,咱們終於能夠只使用一種方案了。雖然它還沒有在瀏覽器和Node中徹底推出,可是咱們可使用代碼轉換工具進行轉換。

5.請解釋下面代碼爲何不能用做IIFE:function foo(){ }();,須要做出哪些修改才能使其成爲IIFE


IIFE(Immediately Invoked Function Expressions)表明當即執行函數。 JavaScript 解析器將 function foo(){ }();解析成 function foo(){ }和(); 。其中,前者是函數聲明;後者(一對括號)是試圖調用一個函數,卻沒有指定名稱,所以它會拋出Uncaught SyntaxError: Unexpected token )的錯誤。

修改方法是:再添加一對括號,形式上有兩種:(function foo(){ })()(function foo(){ }())。以上函數不會暴露到全局做用域,若是不須要在函數內部引用自身,能夠省略函數的名稱。

你可能會用到void操做符:void function foo(){ }();。可是,這種作法是有問題的。表達式的值是undefined,因此若是你的IIFE 有返回值,不要用這種作法。例如:

const foo = void function bar() { return 'foo'; }();

console.log(foo); // undefined

複製代碼

6.nullundefined和未聲明變量之間有什麼區別?如何檢查判斷這些狀態值?


當你沒有提早使用varletconst聲明變量,就爲一個變量賦值時,該變量是未聲明變量(undeclared variables)。未聲明變量會脫離當前做用域,成爲全局做用域下定義的變量。在嚴格模式下,給未聲明的變量賦值,會拋出ReferenceError錯誤。和使用全局變量同樣,使用未聲明變量也是很是很差的作法,應當儘量避免。要檢查判斷它們,須要將用到它們的代碼放在try/catch語句中。

function foo() {
  x = 1; // 在嚴格模式下,拋出 ReferenceError 錯誤
}

foo();
console.log(x); // 1

複製代碼

當一個變量已經聲明,但沒有賦值時,該變量的值是undefined。若是一個函數的執行結果被賦值給一個變量,可是這個函數卻沒有返回任何值,那麼該變量的值是undefined。要檢查它,須要使用嚴格相等(===);或者使用typeof,它會返回 'undefined'字符串。請注意,不能使用非嚴格相等(==)來檢查,由於若是變量值爲null,使用非嚴格相等也會返回true

var foo;
console.log(foo); // undefined
console.log(foo === undefined); // true
console.log(typeof foo === 'undefined'); // true

console.log(foo == null); // true. 錯誤,不要使用非嚴格相等!

function bar() {}
var baz = bar();
console.log(baz); // undefined

複製代碼

null只能被顯式賦值給變量。它表示空值,與被顯式賦值 undefined 的意義不一樣。要檢查判斷null值,須要使用嚴格相等運算符。請注意,和前面同樣,不能使用非嚴格相等(==)來檢查,由於若是變量值爲undefined,使用非嚴格相等也會返回true

var foo = null;

console.log(foo === null); // true

console.log(foo == undefined); // true. 錯誤,不要使用非嚴格相等!

複製代碼

做爲一種我的習慣,我從不使用未聲明變量。若是定義了暫時沒有用到的變量,我會在聲明後明確地給它們賦值爲null

7.什麼是閉包,爲何使用閉包?


閉包是函數和聲明該函數的詞法環境的組合。詞法做用域中使用的域,是變量在代碼中聲明的位置所決定的。閉包是即便被外部函數返回,依然能夠訪問到外部(封閉)函數做用域的函數。

爲何使用閉包?
1.利用閉包實現數據私有化或模擬私有方法,這個方式也稱爲模塊模式。
2.部分參數函數柯里化。

8.請說明.forEach循環和.map()循環的主要區別,它們分別在什麼狀況下使用?


爲了理解二者的區別,咱們看看它們分別是作什麼的。

1.forEach

遍歷數組中的元素。
爲每一個元素執行回調。
無返回值。

const a = [1, 2, 3];
const doubled = a.forEach((num, index) => {
  // 執行與 num、index 相關的代碼
});

// doubled = undefined

2.map

遍歷數組中的元素
經過對每一個元素調用函數,將每一個元素「映射(map)」到一個新元素,從而建立一個新數組。

const a = [1, 2, 3];
const doubled = a.map(num => {
  return num * 2;
});

// doubled = [2, 4, 6]

複製代碼

.forEach.map()的主要區別在於.map()返回一個新的數組。若是你想獲得一個結果,但不想改變原始數組,用.map()。若是你只須要在數組上作迭代修改,用forEach

9.匿名函數的典型應用場景是什麼?


匿名函數能夠在IIFE中使用,來封裝局部做用域內的代碼,以便其聲明的變量不會暴露到全局做用域。

(function() {
  // 一些代碼。
})();

匿名函數能夠做爲只用一次,不須要在其餘地方使用的回調函數。當處理函數在調用它們的程序內部被定義時,代碼具備更好地自閉性和可讀性,能夠省去尋找該處理函數的函數體位置的麻煩。

setTimeout(function() {
  console.log('Hello world!');
}, 1000);

匿名函數能夠用於函數式編程或 Lodash(相似於回調函數)。

const arr = [1, 2, 3];
const double = arr.map(function(el) {
  return el * 2;
});
console.log(double); // [2, 4, 6]

複製代碼

10.你如何組織本身的代碼?(使用模塊模式(module pattern)仍是經典繼承(classical inheritance)?)


我之前使用 Backbone 組織個人模型 (model)Backbone 鼓勵採用面向對象的方法——建立 Backbone 模型,併爲其添加方法。

模塊模式仍然是很好的方式,可是如今我使用基於 React/ReduxFlux體系結構,它鼓勵使用單向函數編程的方法。我用普通對象 (plain object) 表示個人 app 模型,編寫實用純函數去操做這些對象。使用動做 (actions) 和化簡器 (reducers) 來處理狀態,就像其餘 Redux 應用同樣。

我儘量避免使用經典繼承。若是非要這麼作,我會堅持這些原則。

11.宿主對象 (host objects) 和原生對象 (native objects) 的區別是什麼?


原生對象是由 ECMAScript 規範定義的 JavaScript內置對象,好比StringMathRegExpObjectFunction等等。

宿主對象是由運行時環境(瀏覽器或 Node)提供,好比 windowXMLHTTPRequest 等等。

12.下列語句有什麼區別:

function Person(){}

var person = Person()

var person = new Person()

複製代碼

這個問題問得很含糊。我猜這是在考察 JavaScript 中的構造函數(constructor)。從技術上講,function Person(){}只是一個普通的函數聲明。使用 PascalCase 方式命名函數做爲構造函數,是一個慣例。

var person = Person()將Person以普通函數調用,而不是構造函數。若是該函數是用做構造函數的,那麼這種調用方式是一種常見錯誤。一般狀況下,構造函數不會返回任何東西,所以,像普通函數同樣調用構造函數,只會返回undefined賦給用做實例的變量。

var person = new Person()使用new操做符,建立Person對象的實例,該實例繼承自Person.prototype。另一種方式是使用Object.create,例如:

Object.create(Person.prototype)`。

function Person(name) {
  this.name = name;
}

var person = Person('John');
console.log(person); // undefined
console.log(person.name); // Uncaught TypeError: Cannot read property 'name' of undefined

var person = new Person('John');
console.log(person); // Person { name: "John" }
console.log(person.name); // "john"

複製代碼

13..call.apply有什麼區別?


.call.apply都用於調用函數,第一個參數將用做函數內 this 的值。然而,.call接受逗號分隔的參數做爲後面的參數,而 .apply接受一個參數數組做爲後面的參數。一個簡單的記憶方法是,從call中的 C 聯想到逗號分隔(comma-separated),從apply中的 A 聯想到數組(array)。

function add(a, b) {
  return a + b;
}

console.log(add.call(null, 1, 2)); // 3
console.log(add.apply(null, [1, 2])); // 3

複製代碼

14.請說明 Function.prototype.bind 的用法。


摘自MDN

bind()方法建立一個新的函數, 當被調用時,將其 this 關鍵字設置爲提供的值,在調用新函數時,在任何提供以前提供一個給定的參數序列。

根據個人經驗,將this的值綁定到想要傳遞給其餘函數的類的方法中是很是有用的。在 React 組件中常常這樣作。

15.何時會用到document.write()


document.write()用來將一串文本寫入由document.open()打開的文檔流中。當頁面加載後執行document.write()時,它將調用document.open,會清除整個文檔(<head><body>會被移除!),並將文檔內容替換成給定的字符串參數。所以它一般被認爲是危險的而且容易被誤用。

網上有一些答案,解釋了document.write()被用於分析代碼中,或者當你想包含只有在啓用了 JavaScript 的狀況下才能工做的樣式。它甚至在 HTML5 樣板代碼中用於並行加載腳本並保持執行順序!可是,我懷疑這些使用緣由是過期的,如今能夠在不使用document.write()的狀況下實現。若是個人觀點有錯,請糾正我。

16.請儘量詳細地解釋 Ajax


Ajax(asynchronous JavaScript and XML)是使用客戶端上的許多 Web 技術,建立異步 Web 應用的一種 Web 開發技術。藉助 AjaxWeb 應用能夠異步(在後臺)向服務器發送數據和從服務器檢索數據,而不會干擾現有頁面的顯示和行爲。經過將數據交換層與表示層分離,Ajax 容許網頁和擴展 Web 應用程序動態更改內容,而無需從新加載整個頁面。實際上,如今一般將 XML 替換爲 JSON,由於 JavaScriptJSON 有原生支持優點。

XMLHttpRequest API 常常用於異步通訊。此外還有最近流行的fetch API

17.使用 Ajax 的優缺點分別是什麼?


優勢

交互性更好。來自服務器的新內容能夠動態更改,無需從新加載整個頁面。
減小與服務器的鏈接,由於腳本和樣式只須要被請求一次。
狀態能夠維護在一個頁面上。JavaScript 變量和 DOM 狀態將獲得保持,由於主容器頁面未被從新加載。
基本上包括大部分 SPA 的優勢。
複製代碼

缺點

動態網頁很難收藏。
若是 JavaScript 已在瀏覽器中被禁用,則不起做用。
有些網絡爬蟲不執行 JavaScript,也不會看到 JavaScript 加載的內容。
基本上包括大部分 SPA 的缺點。
複製代碼

18.請說明 JSONP 的工做原理,它爲何不是真正的 Ajax


JSONP(帶填充的 JSON)是一種一般用於繞過 Web 瀏覽器中的跨域限制的方法,由於 Ajax 不容許跨域請求。

JSONP 經過<script>標籤發送跨域請求,一般使用callback查詢參數,例如:example.com?callback=printData。 而後服務器將數據包裝在一個名爲printData的函數中並將其返回給客戶端。

<!-- https://mydomain.com -->
<script>
function printData(data) {
  console.log(`My name is ${data.name}!`);
}
</script>

<script src="https://example.com?callback=printData"></script>
printData({ name: 'Yang Shun' });

複製代碼

客戶端必須在其全局範圍內具備printData函數,而且在收到來自跨域的響應時,該函數將由客戶端執行。

JSONP 可能具備一些安全隱患。因爲 JSONP 是純 JavaScript 實現,它能夠完成 JavaScript 所能作的一切,所以須要信任 JSONP 數據的提供者。

現現在,跨來源資源共享(CORS) 是推薦的主流方式,JSONP 已被視爲一種比較 hack 的方式。

19.請解釋變量提高。


變量提高(hoisting)是用於解釋代碼中變量聲明行爲的術語。使用var關鍵字聲明或初始化的變量,會將聲明語句「提高」到當前做用域的頂部。 可是,只有聲明纔會觸發提高,賦值語句(若是有的話)將保持原樣。咱們用幾個例子來解釋一下。

// 用 var 聲明獲得提高
console.log(foo); // undefined

var foo = 1;

console.log(foo); // 1

// 用 let/const 聲明不會提高
console.log(bar); // ReferenceError: bar is not defined

let bar = 2;

console.log(bar); // 2

函數聲明會使函數體提高,但函數表達式(以聲明變量的形式書寫)只有變量聲明會被提高。

// 函數聲明
console.log(foo); // [Function: foo]

foo(); // 'FOOOOO'

function foo() {

  console.log('FOOOOO');
  
}
console.log(foo); // [Function: foo]

// 函數表達式
console.log(bar); // undefined

bar(); // Uncaught TypeError: bar is not a function

var bar = function() {

  console.log('BARRRR');
  
};

console.log(bar); // [Function: bar]

複製代碼

20.請描述事件冒泡。


當一個事件在 DOM 元素上觸發時,若是有事件監聽器,它將嘗試處理該事件,而後事件冒泡到其父級元素,併發生一樣的事情。最後直到事件到達祖先元素。事件冒泡是實現事件委託的原理(event delegation)。

21.「attribute」 和 「property」 之間有什麼區別?


Attribute」 是在 HTML 中定義的,而 「property」 是在 DOM 上定義的。爲了說明區別,假設咱們在 HTML 中有一個文本框:

<input type="text" value="Hello">。

const input = document.querySelector('input');
console.log(input.getAttribute('value')); // Hello
console.log(input.value); // Hello

可是在文本框中鍵入「 World!」後:

console.log(input.getAttribute('value')); // Hello
console.log(input.value); // Hello World!

複製代碼

22.爲何擴展 JavaScript 內置對象是很差的作法?


擴展 JavaScript 內置(原生)對象意味着將屬性或方法添加到其prototype中。雖然聽起來很不錯,但事實上這樣作很危險。想象一下,你的代碼使用了一些庫,它們經過添加相同的 contains 方法來擴展Array.prototype,若是這兩個方法的行爲不相同,那麼這些實現將會相互覆蓋,你的代碼將不能正常運行。

擴展內置對象的惟一使用場景是建立 polyfill,本質上爲老版本瀏覽器缺失的方法提供本身的實現,該方法是由 JavaScript 規範定義的。

23.document 中的load事件和DOMContentLoaded事件之間的區別是什麼?


當初始的 HTML 文檔被徹底加載和解析完成以後,DOMContentLoaded事件被觸發,而無需等待樣式表、圖像和子框架的完成加載。

windowload事件僅在 DOM 和全部相關資源所有完成加載後纔會觸發。

24.=====的區別是什麼?


==是抽象相等運算符,而===是嚴格相等運算符。==運算符是在進行必要的類型轉換後,再比較。===運算符不會進行類型轉換,因此若是兩個值不是相同的類型,會直接返回false。使用==時,可能發生一些特別的事情,例如:

1 == '1'; // true
1 == [1]; // true
1 == true; // true
0 == ''; // true
0 == '0'; // true
0 == false; // true
個人建議是從不使用==運算符,除了方便與null或undefined比較時,a == null若是a爲null或undefined將返回true。

var a = null;
console.log(a == null); // true
console.log(a == undefined); // true

複製代碼

25.請解釋關於 JavaScript 的同源策略。


同源策略可防止 JavaScript 發起跨域請求。源被定義爲 URI主機名端口號的組合。此策略可防止頁面上的惡意腳本經過該頁面的文檔對象模型,訪問另外一個網頁上的敏感數據。

26.請使下面的語句生效:

duplicate([1, 2, 3, 4, 5]); // [1,2,3,4,5,1,2,3,4,5]

function duplicate(arr) {
  return arr.concat(arr);
}

duplicate([1, 2, 3, 4, 5]); // [1,2,3,4,5,1,2,3,4,5]

複製代碼

27.請說明三元表達式中「三元」這個詞表明什麼?


三元」表示接受三個操做數:判斷條件then表達式else表達式。三元表達式不是 JavaScript 特有的,我不知道這個問題爲何會出如今這裏。

28.什麼是"use strict";?使用它有什麼優缺點?


'use strict' 是用於對整個腳本或單個函數啓用嚴格模式的語句。嚴格模式是可選擇的一個限制 JavaScript 的變體一種方式 。

優勢:

沒法再意外建立全局變量。
會使引發靜默失敗(silently fail,即:不報錯也沒有任何效果)的賦值操拋出異常。
試圖刪除不可刪除的屬性時會拋出異常(以前這種操做不會產生任何效果)。
要求函數的參數名惟一。
全局做用域下,this的值爲undefined。
捕獲了一些常見的編碼錯誤,並拋出異常。
禁用使人困惑或欠佳的功能。
複製代碼

缺點:

缺失許多開發人員已經習慣的功能。
沒法訪問function.caller和function.arguments。
以不一樣嚴格模式編寫的腳本合併後可能致使問題。
總的來講,我認爲利大於弊,我歷來不使用嚴格模式禁用的功能,所以我推薦使用嚴格模式。
複製代碼

29.建立一個循環,從 1 迭代到 100,3的倍數時輸出 "fizz",5的倍數時輸出 "buzz",同時爲3和5的倍數時輸出 "fizzbuzz"。


for (let i = 1; i <= 100; i++) {
  let f = i % 3 == 0,
    b = i % 5 == 0;
  console.log(f ? (b ? 'FizzBuzz' : 'Fizz') : b ? 'Buzz' : i);
}

複製代碼

我不建議你在面試時寫上面的代碼。只要寫得清晰便可。關於更多千奇百怪的 FizzBuzz 實現,請查看下面的參考連接。

30.爲何不要使用全局做用域?


每一個腳本均可以訪問全局做用域,若是人人都使用全局命名空間來定義本身的變量,確定會發生衝突。使用模塊模式(IIFE)將變量封裝在本地命名空間中。

31.爲何要使用load事件?這個事件有什麼缺點嗎?你知道一些代替方案嗎,爲何使用它們?


在文檔裝載完成後會觸發load事件。此時,在文檔中的全部對象都在 DOM 中,全部圖像、腳本、連接和子框架都完成了加載。

DOM 事件DOMContentLoaded將在頁面的 DOM 構建完成後觸發,但不要等待其餘資源完成加載。若是在初始化以前不須要裝入整個頁面,這個事件是使用首選。

32.請解釋單頁應用是什麼,如何使其對 SEO 友好。


現現在,Web 開發人員將他們構建的產品稱爲 Web 應用,而不是網站。雖然這兩個術語之間沒有嚴格的區別,但網絡應用每每具備高度的交互性和動態性,容許用戶執行操做並接收他們的操做響應。在過去,瀏覽器從服務器接收 HTML 並渲染。當用戶導航到其它 URL 時,須要整頁刷新,服務器會爲新頁面發送新的 HTML。這被稱爲服務器端渲染。

然而,在現代的 SPA 中,客戶端渲染取而代之。瀏覽器從服務器加載初始頁面、整個應用程序所需的腳本(框架、庫、應用代碼)和樣式表。當用戶導航到其餘頁面時,不會觸發頁面刷新。該頁面的 URL 經過 HTML5 History API 進行更新。瀏覽器經過 AJAX 請求向服務器檢索新頁面所需的數據(一般採用 JSON 格式)。而後,SPA 經過 JavaScript 來動態更新頁面,這些 JavaScript 在初始頁面加載時已經下載。這種模式相似於原生移動應用的工做方式。

好處:

用戶感知響應更快,用戶切換頁面時,再也不看到因頁面刷新而致使的白屏。
對服務器進行的 HTTP 請求減小,由於對於每一個頁面加載,沒必要再次下載相同的資源。
客戶端和服務器之間的關注點分離。能夠爲不一樣平臺(例如手機、聊天機器人、智能手錶)創建新的客戶端,而無需修改服務器代碼。只要 API 沒有修改,能夠單獨修改客戶端和服務器上的代碼。
複製代碼

壞處:

因爲加載了多個頁面所需的框架、應用代碼和資源,致使初始頁面加載時間較長。
服務器還須要進行額外的工做,須要將全部請求路由配置到單個入口點,而後由客戶端接管路由。
SPA 依賴於 JavaScript 來呈現內容,但並不是全部搜索引擎都在抓取過程當中執行 JavaScript,他們可能會在你的頁面上看到空的內容。這無心中損害了應用的搜索引擎優化(SEO)。然而,當你構建應用時,大多數狀況下,搜索引擎優化並非最重要的因素,由於並不是全部內容都須要經過搜索引擎進行索引。爲了解決這個問題,能夠在服務器端渲染你的應用,或者使用諸如 Prerender 的服務來「在瀏覽器中呈現你的 javascript,保存靜態 HTML,並將其返回給爬蟲」。
複製代碼

33.你對 Promises 及其 polyfill 的掌握程度如何?


掌握它的工做原理。Promise是一個可能在將來某個時間產生結果的對象:操做成功的結果或失敗的緣由(例如發生網絡錯誤)。 Promise 可能處於如下三種狀態之一:fulfilledrejectedpending。 用戶能夠對Promise添加回調函數來處理操做成功的結果或失敗的緣由。

一些常見的 polyfill$.deferredQBluebird,但不是全部的 polyfill 都符合規範。ES2015 支持Promises,如今一般不須要使用 polyfills

34.Promise代替回調函數有什麼優缺點?


優勢:
避免可讀性極差的回調地獄。
使用.then()編寫的順序異步代碼,既簡單又易讀。
使用Promise.all()編寫並行異步代碼變得很容易。

缺點:
輕微地增長了代碼的複雜度(這點存在爭議)。
在不支持 ES2015 的舊版瀏覽器中,須要引入 polyfill 才能使用。

複製代碼

35.你使用什麼工具和技巧調試 JavaScript 代碼?


React 和 Redux
React Devtools
Redux Devtools
Vue
Vue Devtools
JavaScript
Chrome Devtools
debugger聲明
使用萬金油console.log進行調試

複製代碼

36.你使用什麼語句遍歷對象的屬性和數組的元素?


對象:

1.for循環:for (var property in obj) { console.log(property); }。可是,這還會遍歷到它的繼承屬性,在使用以前,你須要加入obj.hasOwnProperty(property)檢查。

2.Object.keys():Object.keys(obj).forEach(function (property) { ... })。Object.keys()方法會返回一個由一個給定對象的自身可枚舉屬性組成的數組。

3.Object.getOwnPropertyNames():Object.getOwnPropertyNames(obj).forEach(function (property) { ... })。Object.getOwnPropertyNames()方法返回一個由指定對象的全部自身屬性的屬性名(包括不可枚舉屬性但不包括 Symbol 值做爲名稱的屬性)組成的數組。
數組:

4.for loops:for (var i = 0; i < arr.length; i++)。這裏的常見錯誤是var是函數做用域而不是塊級做用域,大多數時候你想要迭代變量在塊級做用域中。ES2015 引入了具備塊級做用域的let,建議使用它。因此就變成了:for (let i = 0; i < arr.length; i++)。

5.forEach:arr.forEach(function (el, index) { ... })。這個語句結構有時會更精簡,由於若是你所須要的只是數組元素,你沒必要使用index。還有every和some方法可讓你提早終止遍歷。
大多數狀況下,我更喜歡.forEach方法,但這取決於你想要作什麼。for循環有更強的靈活性,好比使用break提早終止循環,或者遞增步數大於一。

複製代碼

36.請解釋可變對象和不可變對象之間的區別。


什麼是 JavaScript 中的不可變對象的例子? 不變性有什麼優勢和缺點? 你如何在本身的代碼中實現不變性? 可變對象 在建立以後是能夠被改變的。

不可變對象 在建立以後是不能夠被改變的。

JavaScript 中,stringnumber 從設計之初就是不可變(Immutable)。 不可變 實際上是保持一個對象狀態不變,這樣作的好處是使得開發更加簡單,可回溯,測試友好,減小了任何可能的反作用。可是,每當你想添加點東西到一個不可變(Immutable)對象裏時,它必定是先拷貝已存在的值到新實例裏,而後再給新實例添加內容,最後返回新實例。相比可變對象,這勢必會有更多內存、計算量消耗。 好比:構造一個純函數

const student1 = {
  school: 'Baidu',
  name: 'HOU Ce',
  birthdate: '1995-12-15',
};

const changeStudent = (student, newName, newBday) => {
  return {
    ...student, // 使用解構
    name: newName, // 覆蓋name屬性
    birthdate: newBday, // 覆蓋birthdate屬性
  };
};

const student2 = changeStudent(student1, 'YAN Haijing', '1990-11-10');

// both students will have the name properties

console.log(student1, student2);

// Object {school: "Baidu", name: "HOU Ce", birthdate: "1995-12-15"}
// Object {school: "Baidu", name: "YAN Haijing", birthdate: "1990-11-10"}

複製代碼

37.請解釋同步和異步函數之間的區別。


同步函數阻塞,而異步函數不阻塞。在同步函數中,語句完成後,下一句才執行。在這種狀況下,程序能夠按照語句的順序進行精確評估,若是其中一個語句須要很長時間,程序的執行會停滯很長時間。

異步函數一般接受回調做爲參數,在調用異步函數後當即繼續執行下一行。回調函數僅在異步操做完成且調用堆棧爲空時調用。諸如從 Web 服務器加載數據或查詢數據庫等重負載操做應該異步完成,以便主線程能夠繼續執行其餘操做,而不會出現一直阻塞,直到費時操做完成的狀況(在瀏覽器中,界面會卡住)。

38.什麼是事件循環?調用堆棧和任務隊列之間有什麼區別?


事件循環是一個單線程循環,用於監視調用堆棧並檢查是否有工做即將在任務隊列中完成。若是調用堆棧爲空而且任務隊列中有回調函數,則將回調函數出隊並推送到調用堆棧中執行。

39.請解釋 function foo() {}var foo = function() {} 之間 foo 的用法上的區別。


前者是函數聲明,後者是函數表達式。關鍵的區別在於函數聲明會使函數體提高(具備與變量相同的提高行爲),但函數表達式的函數體不能。有關變量提高的更多解釋,請參閱上面關於變量提高的問題。若是你試圖在定義函數表達式以前調用它,你會獲得一個Uncaught TypeError: XXX is not a function的錯誤。

函數聲明

foo(); // 'FOOOOO'
function foo() {
  console.log('FOOOOO');
}
函數表達式

foo(); // Uncaught TypeError: foo is not a function
var foo = function() {
  console.log('FOOOOO');
};

複製代碼

40.使用letvarconst建立變量有什麼區別?


var聲明的變量的做用域是它當前的執行上下文,它能夠是嵌套的函數,也能夠是聲明在任何函數外的變量。letconst是塊級做用域,意味着它們只能在最近的一組花括號(functionif-else 代碼塊或 for 循環中)中訪問。

function foo() {
  // 全部變量在函數中均可訪問
  var bar = 'bar';
  let baz = 'baz';
  const qux = 'qux';

  console.log(bar); // bar
  console.log(baz); // baz
  console.log(qux); // qux
}

console.log(bar); // ReferenceError: bar is not defined
console.log(baz); // ReferenceError: baz is not defined
console.log(qux); // ReferenceError: qux is not defined

if (true) {
  var bar = 'bar';
  let baz = 'baz';
  const qux = 'qux';
}

// 用 var 聲明的變量在函數做用域上均可訪問
console.log(bar); // bar

// let 和 const 定義的變量在它們被定義的語句塊以外不可訪問
console.log(baz); // ReferenceError: baz is not defined
console.log(qux); // ReferenceError: qux is not defined

var會使變量提高,這意味着變量能夠在聲明以前使用。let和const不會使變量提高,提早使用會報錯。

console.log(foo); // undefined

var foo = 'foo';

console.log(baz); // ReferenceError: can't access lexical declaration 'baz' before initialization let baz = 'baz'; console.log(bar); // ReferenceError: can't access lexical declaration 'bar' before initialization

const bar = 'bar';

用var重複聲明不會報錯,但let和const會。

var foo = 'foo';
var foo = 'bar';
console.log(foo); // "bar"

let baz = 'baz';
let baz = 'qux'; // Uncaught SyntaxError: Identifier 'baz' has already been declared

let和const的區別在於:let容許屢次賦值,而const只容許一次。

// 這樣不會報錯。
let foo = 'foo';
foo = 'bar';

// 這樣會報錯。
const baz = 'baz';
baz = 'qux';

複製代碼

41.高階函數 (higher-order) 的定義是什麼?


高階函數是將一個或多個函數做爲參數的函數,它用於數據處理,也可能將函數做爲返回結果。高階函數是爲了抽象一些重複執行的操做。一個典型的例子是map,它將一個數組和一個函數做爲參數。map使用這個函數來轉換數組中的每一個元素,並返回一個包含轉換後元素的新數組。JavaScript 中的其餘常見示例是 forEachfilterreduce。高階函數不只須要操做數組的時候會用到,還有許多函數返回新函數的用例。Function.prototype.bind就是一個例子。

Map 示例:

假設咱們有一個由名字組成的數組,咱們須要將每一個字符轉換爲大寫字母。

const names = ['irish', 'daisy', 'anna'];
不使用高階函數的方法是這樣:

const transformNamesToUppercase = function(names) {
  const results = [];
  for (let i = 0; i < names.length; i++) {
    results.push(names[i].toUpperCase());
  }
  return results;
};

transformNamesToUppercase(names); // ['IRISH', 'DAISY', 'ANNA']

使用.map(transformerFn)使代碼更簡明

const transformNamesToUppercase = function(names) {
  return names.map(name => name.toUpperCase());
};
transformNamesToUppercase(names); // ['IRISH', 'DAISY', 'ANNA']

複製代碼

42.請給出一個解構 (destructuring) 對象或數組的例子。


解構是 ES6 中新功能,它提供了一種簡潔方便的方法來提取對象或數組的值,並將它們放入不一樣的變量中。

數組解構

// 變量賦值
const foo = ['one', 'two', 'three'];

const [one, two, three] = foo;
console.log(one); // "one"
console.log(two); // "two"
console.log(three); // "three"
// 變量交換
let a = 1;
let b = 3;

[a, b] = [b, a];
console.log(a); // 3
console.log(b); // 1
對象解構

// 變量賦值
const o = { p: 42, q: true };
const { p, q } = o;

console.log(p); // 42
console.log(q); // true

複製代碼

43.ES6 的模板字符串爲生成字符串提供了很大的靈活性,你能夠舉個例子嗎?


模板字面量 (Template literals) 是容許嵌入表達式的字符串字面量。你可使用多行字符串和字符串插值功能。

語法

`string text`
`string text line 1 string text line 2`
`string text ${expression} string text`;
tag`string text ${expression} string text`;

示例
console.log(`string text line 1 string text line 2`);
// "string text line 1 string text line 2"

var a = 5;
var b = 10;
console.log(`Fifteen is ${a + b} and\nnot ${2 * a + b}.`);
// "Fifteen is 15 and\nnot 20."

//show函數採用rest參數的寫法以下:

let name = '張三',
  age = 20,
  message = show`我來給你們介紹:${name}的年齡是${age}.`;

function show(stringArr, ...values) {
  let output = '';

  let index = 0;

  for (; index < values.length; index++) {
    output += stringArr[index] + values[index];
  }

  output += stringArr[index];

  return output;
}

message; //"我來給你們介紹:張三的年齡是20."

複製代碼

44.你能舉出一個柯里化函數(curry function)的例子嗎?它有哪些好處?


柯里化 (currying) 是一種模式,其中具備多個參數的函數被分解爲多個函數,當被串聯調用時,將一次一個地累積全部須要的參數。這種技術幫助編寫函數式風格的代碼,使代碼更易讀、緊湊。值得注意的是,對於須要被 curry 的函數,它須要從一個函數開始,而後分解成一系列函數,每一個函數都須要一個參數。

function curry(fn) {
  if (fn.length === 0) {
    return fn;
  }

  function _curried(depth, args) {
    return function(newArgument) {
      if (depth - 1 === 0) {
        return fn(...args, newArgument);
      }
      return _curried(depth - 1, [...args, newArgument]);
    };
  }

  return _curried(fn.length, []);
}

function add(a, b) {
  return a + b;
}

var curriedAdd = curry(add);
var addFive = curriedAdd(5);

var result = [0, 1, 2, 3, 4, 5].map(addFive); // [5, 6, 7, 8, 9, 10]

複製代碼

45.使用擴展運算符(spread)的好處是什麼,它與使用剩餘參數語句(rest)有什麼區別?


在函數泛型編碼時,ES6 的擴展運算符很是有用,由於咱們能夠輕鬆建立數組和對象的拷貝,而無需使用 Object.createslice 或其餘函數庫。這個語言特性在 Reduxrx.js 的項目中常常用到。

function putDookieInAnyArray(arr) {
  return [...arr, 'dookie'];
}

const result = putDookieInAnyArray(['I', 'really', "don't", 'like']); // ["I", "really", "don't", "like", "dookie"]

const person = {
  name: 'Todd',
  age: 29,
};

const copyOfTodd = { ...person };

複製代碼

ES6 的剩餘參數語句提供了一個簡寫,容許咱們將不定數量的參數表示爲一個數組。它就像是擴展運算符語法的反面,將數據收集到數組中,而不是解構數組。剩餘參數語句在函數參數、數組和對象的解構賦值中有很大做用。

function addFiveToABunchOfNumbers(...numbers) {
  return numbers.map(x => x + 5);
}

const result = addFiveToABunchOfNumbers(4, 5, 6, 7, 8, 9, 10); // [9, 10, 11, 12, 13, 14, 15]

const [a, b, ...rest] = [1, 2, 3, 4]; // a: 1, b: 2, rest: [3, 4]

const { e, f, ...others } = {
  e: 1,
  f: 2,
  g: 3,
  h: 4,
}; // e: 1, f: 2, others: { g: 3, h: 4 }

複製代碼

46.如何在文件之間共用代碼?


這取決於執行 JavaScript 的環境。

在客戶端(瀏覽器環境)上,只要變量或函數在全局做用域 (window) 中聲明,全部腳本均可以引用它們。或者,經過 RequireJS 採用異步模塊定義 (AMD) 以得到更多模塊化方法。

在服務器 (Node.js) 上,經常使用的方法是使用 CommonJS。每一個文件都被視爲一個模塊,能夠經過將它們附加到module.exports對象來導出變量和函數。

ES2015 定義了一個模塊語法,旨在替換 AMDCommonJS。 這最終將在瀏覽器和 Node 環境中獲得支持。

47.什麼狀況下會用到靜態類成員?


靜態類成員(屬性或方法)不綁定到某個類的特定實例,無論哪一個實例引用它,都具備相同的值。靜態屬性一般是配置變量,而靜態方法一般是純粹的實用函數,不依賴於實例的狀態。

文中若有錯誤,歡迎在評論區指正,若是這篇文章幫助到了你,歡迎點贊👍和關注,😀。

相關文章
相關標籤/搜索