JS 面試題總結

請解釋事件代理 (event delegation)

事件委託技術能讓你避免對特定的每一個節點添加事件監聽器;相反,事件監聽器是被添加到它們的父元素上。事件監聽器會分析從子元素冒泡上來的事件,找到是哪一個子元素的事件。javascript

優勢:html

  • 性能獲得了優化(須要建立的以及駐留在內存中的事件處理器少了)
  • 動態添加的元素也能綁定事件了

請解釋 JavaScript 中 this 是如何工做的

this 永遠指向函數運行時所在的對象,而不是函數被建立時所在的對象。前端

  • 函數調用es3和非嚴格es5爲全局對象,嚴格es5爲undefined
  • 方法調用this指向調用該方法的對象(調用上下文)
  • 構造函數時,this指向新建立的對象
  • call() apply() 調用方法時,this指向調用方法的對象,而不是該方法擁有者對象

請解釋原型繼承 (prototypal inheritance) 的原理?

原型繼承的基礎是原型鏈查找。java

原型鏈查找基本概念:
每個函數 F 都有一個原型對象(prototype)F.prototype
每個函數均可以經過 new 關鍵字化身成爲一個類構造函數,new F 會產生一個對象 O
在調用對象的某個屬性或者方法,好比 http://O.xxx 的時候,會首先查找對象自身是否有這個方法或者屬性,若是沒找到就會去對象的構造函數的原型對象中查找(注意有兩個定語),也就是查找 O 的構造函數 F 的原型對象 http://F.prototype.xxx
F.prototype 也是一個對象,查找 http://F.prototype.xxx 的時候會重複第 3 步的過程web

請解釋爲何接下來這段代碼不是 IIFE (當即調用的函數表達式):function foo(){ }();要作哪些改動使它變成 IIFE?

 這裏只是聲明一個叫foo的function,直接用()執行這樣是不成功的,想要變成IIFE就要把聲明變成表達式,就能夠當即執行了,能夠這樣(function foo(){})()或者(function foo(){}()),這就是用括號把定義強轉成表達式,固然還有其餘方法,關鍵就是聲明不能夠執行,表達式才能夠執行。面試

描述如下變量的區別:null,undefined 或 undeclared? 該如何檢測它們?

undefined:未定義,在變量沒有賦值的時候的值即爲undefined。「缺乏值」,就是此處應該有一個值,可是尚未定義。
underclared:即爲被污染的命名,訪問沒有被聲明的變量,會拋出異常,終止執行。嘗試訪問一個undeclared的變量,瀏覽器會報錯,JS執行會中斷。
null:是一個空的對象引用。「沒有對象」,即該處不該該有值ajax

區別:
undefined和null在if語句中,都會被自動轉爲false,相等運算符甚至直接報告二者相等。typeof undefined會返回undefined ,而typeof null 總返回 object(typeof有六種可能:「number」、「string」、「boolean」、「object」、「function」、「undefined」)算法

false == undefined;//false
false == null;//false
null == undefined;//true

該如何檢測它們?typescript

var obj;
obj ===undefined; //檢測undfined 方法一
typeof obj === ‘undefined’;//檢測undefined方法2
obj = null;
obj === null;//來檢測null
typeof null;//‘object’

什麼是閉包 (closure),如何使用它,爲何要使用它?

定義:閉包就是能夠讀取到其餘函數內部變量的函數。
閉包的用途:express

  • 能夠讀取函數內部的變量。(外界沒法訪問函數的內部的私有方法和變量,只能經過提供的接口訪問)
  • 讓變量的值始終保持在內存中。
  • 能夠避免污染全局變量,實現私有方法或者變量等

注意:

  • 因爲閉包會使得函數中的變量都被保存在內存中,內存消耗很大,因此不能濫用閉包,不然會形成網頁的性能問題,在IE中可能致使內存泄露。解決方法是,在退出函數以前,將不使用的局部變量所有刪除。
  • 閉包會在父函數外部,改變父函數內部變量的值。因此,若是你把父函數看成對象(object)使用,把閉包看成它的公用方法(Public Method),把內部變量看成它的私有屬性(private value),這時必定要當心,不要隨便改變父函數內部變量的值。

請舉出一個匿名函數的典型用例?

匿名函數能夠用做回調函數執行,能夠防止全局變量污染。
在 JS 框架中常使用匿名函數來避免全局變量的污染。
$.(「input」).each(function(e){this.val(‘OK’)});
(function(){})();
$(document).ready(function(){ });
$(function() {})

你是如何組織本身的代碼?是使用模塊模式,仍是使用經典繼承的方法?

請指出 JavaScript 宿主對象 (host objects) 和原生對象 (native objects) 的區別?

原生對象:獨立於宿主環境的 ECMAScript 實現提供的對象。爲array obj regexp date function等能夠new實例化的對象。

內置對象:爲gload Math 等,開發者沒必要明確實例化內置對象,它已被實例化了。相似於isNaN()、parseInt()和parseFloat()方法等,看起來都是函數,而實際上,它們都是Global對象的方法。具體能夠參考 JavaScript 全局對象

宿主對象:即由 ECMAScript 實現的宿主環境(操做系統和瀏覽器)提供的對象。全部的BOM和DOM對象都是宿主對象。由於其對於不一樣的「宿主」環境所展現的內容不一樣(這就是兼容性和特性檢測的原因)。ECMAScript官方未定義的對象都屬於宿主對象。

請指出如下代碼的區別:function Person(){}、var person = Person()、var person = new Person()?

第一個爲函數聲明,第二個將函數person()返回值賦值給person,第三個經過Person()的構造器建立了一個對象讓person變量引用該對象;

.call 和 .apply 的區別是什麼?

call和apply都是調用一個對象的一個方法,以另外一個對象替換當前對象。它們都屬於Function.prototype的一個方法,因此每一個function實例都有call和apply屬性。這兩個方法能夠用來代替另外一個對象調用一個方法,可將一個函數的對象上下文從初始的上下文改變爲由 thisObj 指定的新對象。

區別:
二者傳遞的參數不一樣,雖然函數第一個參數都是要傳入給當前對象的對象,可是,apply的第二個參數是一個參數數組,將多個參數組合成爲一個數組傳入;而call第二個參數則是直接的參數列表。

請解釋 Function.prototype.bind?

Function.prototype.bind()其實就是函數綁定。函數的接收者取決於他是如何被調用,能夠經過調用.bind()給函數綁定做用域上下文(this的值),即函數的接收者。

var foo = { x: 3} 
var bar = function(){console.log(    this.x);}

bar(); // undefinedvar
boundFunc = bar.bind(foo);//隱式看做是在foo做用域裏調用bar方法
boundFunc(); // 3

.bind()建立了一個函數,當這個函數在被調用的時候,它的 this 關鍵詞會被設置成被傳入的值(這裏指調用bind()時傳入的參數)也就是咱們傳入想要的上下文。 簡單的用法: 關於 Function.prototype.bind() 內部,這裏有個很是簡單的例子:

Function.prototype.bind = function (scope) {
    var fn = this;
    return function () {
        return fn.apply(scope);//使用call效果同樣
    };
}

在何時你會使用 document.write()?

document.write()方法能夠用在兩個方面:

  • 頁面載入過程當中用實時腳本建立頁面內容,該方法須要一個字符串參數,它是寫到窗口或框架中的HTML內容。
  • 以及用延時腳本建立本窗口或新窗口的內容。該方法須要一個字符串參數,它是寫到窗口或框架中的HTML內容。

記住,在載入頁面後,瀏覽器輸出流自動關閉。在此以後,任何一個對當前頁面進行操做的document.write()方法將打開—個新的輸出流,它將清除當前頁面內容(包括源文檔的任何變量或值)。所以,假如但願用腳本生成的HTML替換當前頁面,就必須把HTML內容鏈接起來賦給一個變量,使用一個document.write()方法完成寫操做。沒必要清除文檔並打開一個新數據流,一個document.write()調用就可完成全部的操做。

關於document.write()方法還有一點要說明的是它的相關方法document.close()。腳本向窗口(無論是本窗口或其餘窗口)寫完內容後,必須關閉輸出流。在延時腳本的最後一個document.write()方法後面,必須確保含有document.close()方法,不這樣作就不能顯示圖像和表單。而且,任何後面調用的document.write()方法只會把內容追加到頁面後,而不會清除現有內容來寫入新值。爲了演示document.write()方法,咱們提供了同一個應用程序的兩個版本。

大多數生成的廣告代碼依舊使用 document.write(),雖然這種用法會讓人很不爽。

請指出瀏覽器特性檢測,特性推斷和瀏覽器 UA 字符串嗅探的區別?

檢測瀏覽器的特殊名稱和版本(用戶代理檢測)即瀏覽器UA字符串嗅探。瀏覽器嗅探技術能夠快捷的將代碼進行分支,以便針對不一樣的瀏覽器應用不一樣的指令;針對特定瀏覽器的特定版本,超出範圍以外都是不可靠的

請儘量詳盡的解釋 Ajax 的工做原理?

使用 Ajax 都有哪些優劣?

優點:能夠刷新局部頁面,而不用總體頁面都刷新
缺點:用戶禁用javascript的狀況

請解釋 JSONP 的工做原理,以及它爲何不是真正的 Ajax。

工做原理:JSONP動態建立script標籤,回調函數。Ajax是頁面無刷新請求數據操做,動態添加一個&ltscript>標籤,而script標籤的src屬性是沒有跨域的限制的。這樣說來,這種跨域方式其實與ajax XmlHttpRequest協議無關了。
當GET請求從被調用頁面返回時,能夠返回一段JavaScript代碼,這段代碼會自動調用主頁面中的一個callback函數。

優勢:不受同源策略的影響,它的兼容性更好,在更加古老的瀏覽器中均可以運行,不須要XMLHttpRequest或ActiveX的支持;而且在請求完畢後能夠經過調用callback的方式回傳結果   
缺點:只支持GET請求而不支持POST等其它類型的HTTP請求;它只支持跨域HTTP請求這種狀況,不能解決不一樣域的兩個頁面之間如何進行JavaScript調用的問題。

請解釋變量聲明提高 (hoisting)

在JavaScript代碼運行以前實際上是有一個編譯階段的。編譯以後纔是從上到下,一行一行解釋執行。變量提高就發生在編譯階段,它把變量和函數的聲明提高至做用域的頂端。(編譯階段的工做之一就是將變量與其做用域進行關聯)。

變量提高須要注意兩點:

  • 提高的部分只是變量聲明,賦值語句和可執行的代碼邏輯還保持在原地不動
  • 提高只是將變量聲明提高到變量所在的變量範圍的頂端,並非提高到全局範圍

函數聲明:

  • 變量聲明和函數聲明都會獲得變量提高,但函數聲明會最早獲得提高,而後是變量聲明(函數是一等公民)
  • 對於函數聲明來講,若是定義了相同的函數變量聲明,後定義的聲明會覆蓋掉先前的聲明

請描述事件冒泡機制 (event bubbling)

從目標元素開始,往頂層元素傳播。途中若是有節點綁定了相應的事件處理函數,這些函數都會被依次觸發。若是想阻止事件起泡,可使用e.stopPropagation()(Firefox)或者e.cancelBubble=true(IE)來組織事件的冒泡傳播

「attribute」 和 「property」 的區別是什麼?

DOM元素的attribute和property二者是不一樣的東西。attribute翻譯爲「特性」,property翻譯爲「屬性」。

attribute是一個特性節點,每一個DOM元素都有一個對應的attributes屬性來存放全部的attribute節點,attributes是一個類數組的容器,說得準確點就是NameNodeMap,不繼承於Array.prototype,不能直接調用Array的方法。attributes的每一個數字索引以名值對(name=」value」)的形式存放了一個attribute節點。

property就是一個屬性,若是把DOM元素當作是一個普通的Object對象,那麼property就是一個以名值對(name=」value」)的形式存放在Object中的屬性。要添加和刪除property和普通的對象相似。

不少attribute節點還有一個相對應的property屬性,好比上面的div元素的id和class既是attribute,也有對應的property,無論使用哪一種方法均可以訪問和修改。

總之,attribute節點都是在HTML代碼中可見的,而property只是一個普通的名值對屬性

爲何擴展 JavaScript 內置對象不是好的作法?

由於你不知道哪一天瀏覽器或javascript自己就會實現這個方法,並且和你擴展的實現有不一致的表現。到時候你的javascript代碼可能已經在無數個頁面中執行了數年,而瀏覽器的實現致使全部使用擴展原型的代碼都崩潰了。

須要給Array原型添加一個distinct的方法,最好檢查是否存在同名的方法,避免自定義方法覆蓋原生方法:

Arrray.prototype.distinct = Arrray.prototype.distinct || function(){/…../}

請指出 document load 和 document DOMContentLoaded 兩個事件的區別。

ready 表示文檔的 DOM 已經加載完成(不包含圖片、視頻等資源);load 表示整個網頁加載完成。能夠看出,ready 事件發生在 load 事件以前。

== 和 === 有什麼不一樣?

若是兩邊的操做數具備一致的類型且擁有相同的值時,=== 返回 true,!== 返回 false。

請解釋 JavaScript 的同源策略 (same-origin policy)。

同源策略限制了一個源(origin)中加載文本或腳本與來自其它源(origin)中資源的交互方式。

同源策略出於安全,不容許源 A 的腳本讀取(read)源 B 的資源的內容,但卻容許執行(execute)源 B 的資源。這個概念也有些拗口。簡單說,有一個頁面調用了 Google CDN 提供的 jQuery,以及其它 CDN 上的 Bootstrap JS、CSS 代碼,雖然它們與個人博客不一樣源,但我能夠用它們來操做這個頁面,並應用樣式,這是執行的概念。

如何實現下列代碼:[1,2,3,4,5].duplicator(); // [1,2,3,4,5,1,2,3,4,5]

將此方法添加至 Array.prototype 實現,代碼以下:

Array.prototype.duplicator = function(){
  var l = this.length,i;
      for(i=0;i<l;i++){
       this.push(this[i]) 
       }
}

什麼是三元表達式 (Ternary expression)?「三元 (Ternary)」 表示什麼意思?

一個運算符若是有一個操做數,爲一元運算符,兩個爲二元,三個爲三元運算符,三元表達式則爲一個三元運算表達式!

什麼是 「use strict」; ? 使用它的好處和壞處分別是什麼?

ECMAScript5中引入的嚴格模式,經過讓JavaScript運行環境對一些開發過程當中最多見和不易發現的錯誤作出和當前不一樣的處理,來讓開發者擁有一個」更好」的JavaScript語言。

好處:

  • 消除Javascript語法的一些不合理、不嚴謹之處,減小一些怪異行爲;
  • 消除代碼運行的一些不安全之處,保證代碼運行的安全;
  • 提升編譯器效率,增長運行速度;
  • 爲將來新版本的Javascript作好鋪墊。

好處具體體現:

  • 去除WITH關鍵詞
  • 防止意外爲全局變量賦值
  • 函數中的THIS再也不默認指向全局
  • 防止重名
  • 安全的 EVAL()
  • 對只讀屬性修改時拋出異常

壞處:一樣的代碼,在「嚴格模式」中,可能會有不同的運行結果;一些在「正常模式」下能夠運行的語句,在「嚴格模式」下將不能運行

總結:啓用JavaScript嚴格模式,它能幫你發現代碼中不曾注意到的錯誤。不要在全局環境中啓用,但你能儘可能多的使用IIFE(當即執行函數表達式)來把嚴格模式做用到多個函數範圍內。一開始,你會遇到以前不曾碰到過的錯誤提示,這是正常的。當啓用嚴格模式後,請確保在支持的瀏覽器中作了測試,以發現新的潛在問題。必定不要僅僅在代碼中添加一行」use strict」就假定餘下的代碼能正常工做。

請實現一個遍歷至 100 的 for loop 循環,在能被 3 整除時輸出 「fizz」,在能被 5 整除時輸出 「buzz」,在能同時被 3 和 5 整除時輸出 「fizzbuzz」。

for (var i = 1; i <= 30; i++) {

    if (i % 3 === 0) {
        if (i % 5 === 0) {
            alert('fizzbuzz' + i);
            continue;
        }
        alert('fizz' + i);
        continue;
    } else if (i % 5 === 0) {
        if (i % 3 === 0) {
            alert('fizzbuzz' + i);
            continue;
        }
        alert('buzz' + i);
        continue;
    }
}

爲什麼一般會認爲保留網站現有的全局做用域 (global scope) 不去改變它,是較好的選擇?

它的意思是: 儘可能少在全局做用域定義變量。

目的:減小名稱衝突 利於模塊化

爲什麼你會使用 load 之類的事件 (event)?此事件有缺點嗎?你是否知道其餘替代品,以及爲什麼使用它們?

要等到等頁面徹底加載後(全部圖像、javascript文件、CSS等外部文件)。替代:把script標籤放到最後面。

請解釋什麼是單頁應用 (single page app), 以及如何使其對搜索引擎友好 (SEO-friendly)。

單頁應用是一種特殊的web應用,它將全部的活動侷限於一個web頁面中,僅在該Web頁面初始化時加載相應的HTML、JavaScript 和 CSS。

優勢:

  • 用戶體驗:對於內容的改動不須要加載整個頁面
  • 高效:服務器壓力很小,消耗更少的帶寬,可以與面向服務的架構更好地結合。
  • 經典MVC開發模式,先後端各負其責。
  • 一套Server API,多端使用(web、移動APP等)
  • 重前端,業務邏輯所有在本地操做,數據都須要經過AJAX同步、提交

缺點:

  • 不利於SEO:解決方案也有一些:H5pushState,經過瀏覽器歷史記錄讓搜索引擎抓取;url中#!
    複雜的單頁架構頁面,對Google來講抓取比較困難,因而給開發者制定一個規範:
    1)、網站提交sitemap給Google;
    2)、Google發現URL裏有#!符號,例如example.com/#!/detail/1,因而Google開始抓取example.com/?_escaped_fragment_=/detail/1;_escaped_fragment_這個參數是Google指定的命名,若是開發者但願把網站內容提交給Google,就必須經過這個參數生成靜態頁面。
  • 首屏渲染速度慢

使用 Promises 而非回調 (callbacks) 優缺點是什麼?

優勢:易讀性改善

使用一種能夠編譯成 JavaScript 的語言來寫 JavaScript 代碼有哪些優缺點?

以Typescript爲例子:
typescript是javascript的強類型版本,在編譯期去掉類型和特有語法,生成純粹的javascript代碼。TypeScript 是 JavaScript 的超集,這意味着他支持全部的 JavaScript 語法。並在此之上對 JavaScript 添加了一些擴展,如 class / interface / module 等。這樣會大大提高代碼的可閱讀性。

優勢:

  • 靜態類型檢查
  • IDE 智能提示 (編譯階段便可發現類型不匹配的錯誤)
  • 代碼重構
  • 可讀性

缺點:

  • 不指定類型就寫不了程序,類型只是輔助信息,並非程序的本以後
  • 靈活性問題

你使用哪些工具和技術來調試 JavaScript 代碼?

  • alert
  • console.log
  • 斷點調試(這三種調試方式都是打斷點)
  • js斷點調試
  • source斷點調試
  • Debugger斷點(具體的說就是經過在代碼中添加」debugger;」語句,當代碼執行到該語句的時候就會自動斷點。)
  • DOM斷點調試
  • 當節點內部子節點變化時斷點
  • 當節點屬性發生變化時斷點
  • 當節點被移除時斷點

請解釋可變 (mutable) 和不變 (immutable) 對象的區別?

javascript中的原始值(undefined、null、布爾值、數字和字符串)與對象(包括數組和函數)有着根本區別。原始值是不可更改的:任何方法都沒法更改(或「突變」)一個原始值。對數字和布爾值來講顯然如此—-改變數字的值自己就說不通,而對字符串來講就不那麼明顯了,由於字符串看起來像由字符組成的數組,咱們指望能夠經過指定索引來假改字符串中的字符。實際上,javascript是禁止這樣作的。字符串中全部的方法看上去返回了一個修改後的字符串,實際上返回的是一個新的字符串值。

區別:

  • 可變性:對象和原始值不一樣,首先,它們是可變的–它們的值是可修改的
  • 值的比較:對象的比較並不是值的比較:即便兩個對象包含一樣的屬性及相同的值,它們也是不相等的。各個索引元素相等的兩個數組也不相等。

不變性 (immutability) 有哪些優缺點?

優勢:
*由於不能修改一個不變對象的狀態,因此能夠避免由此引發的沒必要要的程序錯誤;一個不變的對象要比一個可變的對象更加容易維護。

  • 由於沒有任何一個線程可以修改不變對象的內部狀態,一個不變對象自動就是線程安全的,這樣能夠省掉處理同步化的開銷。一個不變對象能夠自由地被不一樣的客戶端共享。

缺點:

  • 一旦須要修改一個不變對象的狀態,就只好建立一個新的同類對象。在須要頻繁修改不變對象的環境裏,會有大量的不變對象做爲中間結果被建立出來,這是一種資源上的浪費。

如何用你本身的代碼來實現不變性 (immutability)?

可使用const 修飾變量不可變

你會使用怎樣的語言結構來遍歷對象屬性 (object properties) 和數組內容?

請解釋同步 (synchronous) 和異步 (asynchronous) 函數的區別。

同步式:當計算機調度線程進行I/O操做命令後,因爲文件的讀寫或者網絡通訊須要較長的操做時間,操做系統爲了充分利用cpu,此時會暫停到當前的I/O線程對CPU的控制(故又稱同步式爲阻塞式I/O),把cup資源然給其餘的線程資源,當I/O線程完成了操做時,此時操做系統會恢復此時的I/O線程,從而當前I/O線程從新得到了cup的的控制權,繼續完成其餘操做。

異步式:異步式IO又稱非阻塞式I/O,異步式與同步式不一樣的是,當線程進行IO操做時,操做系統並非暫停當前的線程操做,而是執行完I/O指令後,操做系統繼續讓當前線程執行下一條指令,當I/O操做完成後,會經過事件(event)通知I/O線程,而線程在接收到通知後,會處理響應事件。

什麼是事件循環 (event loop)?

主線程從「任務隊列」中讀取事件,這個過程是循環不斷的,因此整個的這種運行機制又稱爲Event Loop(事件循環)。

請問調用棧 (call stack) 和任務隊列 (task queue) 的區別是什麼?

所謂「回調函數」(callback),就是那些會被主線程掛起來的代碼。異步任務必須指定回調函數,當異步任務從「任務隊列」回到執行棧,回調函數就會執行。

「任務隊列」是一個先進先出的數據結構,排在前面的事件,優先返回主線程。主線程的讀取過程基本上是自動的,只要執行棧一清空,「任務隊列」上第一位的事件就自動返回主線程。可是,因爲存在後文提到的「定時器」功能,主線程要檢查一下執行時間,某些事件必需要在規定的時間返回主線程。

解釋 function foo() {} 與 var foo = function() {} 用法的區別?

第一個未函數聲明,第二個爲函數定義表達式(函數定義表達式foo,變量聲明提早單賦值並未提早)

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

(function(){ 
    var a = b = 3;
})(); 
console.log("a defined? " + (typeof a !== 'undefined'));
console.log("b defined? " + (typeof b !== 'undefined'));

輸出: a defined? false b defined? true

封裝JavaScript源文件的所有內容到一個函數塊有什麼意義及理由?

這是一個愈來愈廣泛的作法,被許多流行的JavaScript庫(jQuery,Node.js等)採用。這種技術建立了一個圍繞文件所有內容的閉包,也許是最重要的是,建立了一個私有的命名空間,從而有助於避免不一樣JavaScript模塊和庫之間潛在的名稱衝突。
這種技術的另外一個特色是,容許一個易於引用的(假設更短的)別名用於全局變量。這一般用於,例如,jQuery插件中。jQuery容許你使用jQuery.noConflict(),來禁用 $ 引用到jQuery命名空間。在完成這項工做以後,你的代碼仍然可使用$ 利用這種閉包技術,以下所示:
(function($) { /* jQuery plugin code referencing $ */ } )(jQuery);

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

use strict 是一種在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操做符(用於從對象中刪除屬性)不能用在對象不可配置的屬性上。當試圖刪除一個不可配置的屬性時,非嚴格代碼將默默地失敗,而嚴格模式將在這樣的狀況下拋出異常。

考慮如下兩個函數。它們會返回相同的東西嗎? 爲何相同或爲何不相同?

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

返回不相同: foo1 returns:Object {bar: "hello"}foo2 returns:undefined

分號在JavaScript中是一個可選項(儘管省略它們一般是很是糟糕的形式)。其結果就是,當碰到 foo2()中包含 return語句的代碼行(代碼行上沒有其餘任何代碼),分號會當即自動插入到返回語句以後。也不會拋出錯誤,由於代碼的其他部分是徹底有效的,即便它沒有獲得調用或作任何事情(至關於它就是是一個未使用的代碼塊,定義了等同於字符串 "hello"的屬性 bar)。

這種行爲也支持放置左括號於JavaScript代碼行的末尾,而不是新代碼行開頭的約定。正如這裏所示,這不只僅只是JavaScript中的一個風格偏好。

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

NaN 屬性表明一個「不是數字」的值。這個特殊的值是由於運算不能執行而致使的,不能執行的緣由要麼是由於其中的運算對象之一非數字(例如, "abc" / 4),要麼是由於運算的結果非數字(例如,除數爲零)。

NaN的特色:

  • NaN的類型爲Number: console.log(typeof NaN === "Number"); // logs "true"
  • NaN 和任何東西比較——甚至是它本身自己!——結果是false:console.log(NaN === NaN); // logs "false"

測試數字爲NaN的方法:

  • 使用內置函數iSNaN(半可靠)
  • value !== value,若是值等於NaN,只會產生true
  • ES6提供: Number.isNaN() 比老的isNaN更可靠

下列代碼將輸出什麼?並解釋緣由

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

JavaScript中的數字和浮點精度的處理相同,所以,可能不會老是產生預期的結果。
以上所提供的例子就是一個演示了這個問題的典型例子。但出人意料的是,它會輸出:
0.30000000000000004false

討論寫函數 isInteger(x) 的可能方法,用於肯定x是不是整數

ECMAScript 6 以前沒有提供相似 Number.isInteger 的方法。在ECMAScript規格說明中,整數只概念上存在:即,數字值老是存儲爲浮點值。
方法:

  • 最簡單又最乾淨的ECMAScript6以前的解決方法(同時也很是穩健地返回 false ,即便一個非數字的值,如字符串或 null ,被傳遞給函數)如:function isInteger(x) { return (x^0) === x; }
  • function isInteger(x) { return Math.round(x) === x; } (不如第一個優雅)
  • 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) === 1000000000000000000000false

下列代碼行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() 而沒有任何延遲輸出的
2 之因此放在 3的後面,是由於 2 是延遲了1000毫秒(即,1秒)以後輸出的,而 3 是延遲了0毫秒以後輸出的。
好的。可是,既然 3 是0毫秒延遲以後輸出的,那麼是否意味着它是當即輸出的呢?若是是的話,那麼它是否是應該在 4 以前輸出,既然 4 是在第二行輸出的?
要回答這個問題,你須要正確理解JavaScript的事件和時間設置。
瀏覽器有一個事件循環,會檢查事件隊列和處理未完成的事件。例如,若是時間發生在後臺(例如,腳本的 onload 事件)時,瀏覽器正忙(例如,處理一個 onclick),那麼事件會添加到隊列中。當onclick處理程序完成後,檢查隊列,而後處理該事件(例如,執行 onload 腳本)。
一樣的, setTimeout() 也會把其引用的函數的執行放到事件隊列中,若是瀏覽器正忙的話。
當setTimeout()的第二個參數爲0的時候,它的意思是「儘快」執行指定的函數。具體而言,函數的執行會放置在事件隊列的下一個計時器開始。可是請注意,這不是當即執行:函數不會被執行除非下一個計時器開始。這就是爲何在上述的例子中,調用 console.log(4) 發生在調用 console.log(3) 以前(由於調用 console.log(3) 是經過setTimeout被調用的,所以會稍微延遲)。

寫一個簡單的函數(少於80個字符),要求返回一個布爾值指明字符串是否爲迴文結構。

下面這個函數在 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'

寫一個 sum方法,在使用下面任一語法調用時,均可以正常工做

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

(至少)有兩種方法能夠作到:

方法1:

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

方法2:

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

請看下面的代碼片斷

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)當用戶點擊「Button 4」的時候會輸出什麼到控制檯,爲何?
不管用戶點擊什麼按鈕,數字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);

    });

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

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"

調用數組對象的 reverse() 方法並不僅返回反順序的陣列,它也反轉了數組自己的順序(即,在這種狀況下,指的是 arr1)。
reverse() 方法返回一個到數組自己的引用(在這種狀況下即,arr1)。其結果爲,arr2 僅僅是一個到 arr1的引用(而不是副本)。所以,當對 arr2作了任何事情(即當咱們調用 arr2.push(arr3);)時,arr1 也會受到影響,由於 arr1 和 arr2 引用的是同一個對象。

注意:
傳遞數組到另外一個數組的 push() 方法會讓整個數組做爲單個元素映射到數組的末端。其結果是,語句 arr2.push(arr3); 在其總體中添加 arr3 做爲一個單一的元素到 arr2 的末端(也就是說,它並無鏈接兩個數組,鏈接數組是 concat() 方法的目的)。
和Python同樣,JavaScript標榜數組方法調用中的負數下標,例如 slice() 可做爲引用數組末尾元素的方法:例如,-1下標表示數組中的最後一個元素,等等。

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

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" 都不能轉換成數值,所以,"A" - "B"的結果是 NaN,而後再和字符串 "2" 鏈接,獲得 "NaN2" 。
例6: "A" - "B" + 2 輸出: NaN 說明:參見前一個例子, "A" - "B" 結果爲 NaN。可是,應用任何運算符到NaN與其餘任何的數字運算對象,結果仍然是 NaN。

下面的遞歸代碼在數組列表偏大的狀況下會致使堆棧溢出。在保留遞歸模式的基礎上,你怎麼解決這個問題?

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不爲空,timeout函數(nextListItem)就會被推到事件隊列,該函數退出,所以就清空調用堆棧。當事件隊列運行其timeout事件,且進行到下一個 item 時,定時器被設置爲再次調用 extListItem。所以,該方法從頭至尾都沒有直接的遞歸調用,因此不管迭代次數的多少,調用堆棧保持清空的狀態。

JavaScript中的「閉包」是什麼?請舉一個例子

閉包是一個能夠訪問外部(封閉)函數做用域鏈中的變量的內部函數。
閉包能夠訪問三種範圍中的變量:這三個範圍具體爲:

  • 本身範圍內的變量
  • 封閉函數範圍內的變量
  • 全局變量。

下面是一個簡單的例子:

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 = 123innerArg = 456outerVar = ainnerVar = bglobalVar = xyz

下面的代碼將輸出什麼

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

解釋你的答案。閉包在這裏能起什麼做用?
上面的代碼不會按預期顯示值0,1,2,3,和4,而是會顯示5,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到控制檯。

如下代碼行將輸出什麼到控制檯?

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 = 11 || 2 = 10 && 1 = 01 && 2 = 2

在JavaScript中, || 和 &&都是邏輯運算符,用於在從左至右計算時,返回第一個可徹底肯定的「邏輯值」。
或( || )運算符。在形如 X||Y的表達式中,首先計算X 並將其解釋執行爲一個布爾值。若是這個布爾值true,那麼返回true(1),再也不計算 Y,由於「或」的條件已經知足。若是這個布爾值爲false,那麼咱們仍然不能知道 X||Y是真是假,直到咱們計算 Y,而且也把它解釋執行爲一個布爾值。

所以, 0 || 1 的計算結果爲true(1),同理計算1 || 2。
與( &&)運算符。在形如 X&&Y的表達式中,首先計算 X並將其解釋執行爲一個布爾值。若是這個布爾值爲 false,那麼返回 false(0),再也不計算 Y,由於「與」的條件已經失敗。若是這個布爾值爲true,可是,咱們仍然不知道 X&&Y 是真是假,直到咱們去計算 Y,而且也把它解釋執行爲一個布爾值。

不過,關於 &&運算符有趣的地方在於,當一個表達式計算爲「true」的時候,那麼就返回表達式自己。這很好,雖然它在邏輯表達式方面計算爲「真」,但若是你但願的話也可用於返回該值。這就解釋了爲何,有些使人奇怪的是, 1 && 2返回 2(而不是你覺得的可能返回 true 或 1)。

執行下面的代碼時將輸出什麼?請解釋。

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

代碼將輸出:

true false

在JavaScript中,有兩種等式運算符。三個等於運算符 === 的做用相似傳統的等於運算符:若是兩側的表達式有着相同的類型和相同的值,那麼計算結果爲true。而雙等於運算符,會只強制比較它們的值。所以,整體上而言,使用 ===而不是 ==的作法更好。 !==vs !=亦是同理。

如下代碼將輸出什麼?並解釋你的答案。

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]均至關於a["[object Object]"] ,並能夠互換使用。所以,設置或引用 a[c]和設置或引用 a[b]徹底相同。

如下代碼行將輸出什麼到控制檯?

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

代碼將輸出10!的值(即10!或3628800)。
緣由是:
命名函數 f()遞歸地調用自己,當調用 f(1)的時候,只簡單地返回1。下面就是它的調用過程:
f(1): returns n, which is 1f(2): returns 2 f(1), which is 2f(3): returns 3 f(2), which is 6f(4): returns 4 f(3), which is 24f(5): returns 5 f(4), which is 120f(6): returns 6 f(5), which is 720f(7): returns 7 f(6), which is 5040f(8): returns 8 f(7), which is 40320f(9): returns 9 f(8), which is 362880f(10): returns 10 * f(9), which is 3628800

請看下面的代碼段。控制檯將輸出什麼,爲何?

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

控制檯將輸出 1,即便歷來沒有在函數內部設置過x的值。緣由是:
閉包是一個函數,連同在閉包建立的時候,其範圍內的全部變量或函數一塊兒。在JavaScript中,閉包是做爲一個「內部函數」實施的:即,另外一個函數主體內定義的函數。閉包的一個重要特徵是,內部函數仍然有權訪問外部函數的變量。
所以,在本例中,因爲 x未在函數內部中定義,所以在外部函數範圍中搜索定義的變量 x,且被發現具備1的值。

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

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

代碼將輸出:

undefinedJohn Doe

第一個 console.log之因此輸出 undefined,是由於咱們正在從 hero對象提取方法,因此調用了全局上下文中(即窗口對象)的 stoleSecretIdentity(),而在此全局上下文中, _name屬性不存在。
其中一種修復stoleSecretIdentity() 函數的方法以下:
var stoleSecretIdentity = hero.getSecretIdentity.bind(hero);

建立一個給定頁面上的一個DOM元素,就會去訪問元素自己及其全部子元素(不僅是它的直接子元素)的函數。對於每一個被訪問的元素,函數應該傳遞元素到提供的回調函數

此函數的參數爲:
DOM元素
回調函數(將DOM元素做爲其參數)
訪問樹(DOM)的全部元素是經典的深度優先搜索算法應用。下面是一個示範的解決方案:

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

面試題收集

面試題一
面試題二

相關文章
相關標籤/搜索