摘要:即為被污染的命名,訪問沒有被聲明的變量,會拋出異常,終止執行。請舉出一個匿名函數的典型用例匿名函數可以用作回調函數執行,可以防止全局變量污染。在框架中常使用匿名函數來避免全局變量的污染。
請解釋事件代理 (event delegation)
事件委托技術能讓你避免對特定的每個節點添加事件監聽器;相反,事件監聽器是被添加到它們的父元素上。事件監聽器會分析從子元素冒泡上來的事件,找到是哪個子元素的事件。
優點:
性能得到了優化(需要創建的以及駐留在內存中的事件處理器少了)
動態添加的元素也能綁定事件了
請解釋 JavaScript 中 this 是如何工作的this 永遠指向函數運行時所在的對象,而不是函數被創建時所在的對象。
函數調用es3和非嚴格es5為全局對象,嚴格es5為undefined
方法調用this指向調用該方法的對象(調用上下文)
構造函數時,this指向新創建的對象
call() apply() 調用方法時,this指向調用方法的對象,而不是該方法擁有者對象
請解釋原型繼承 (prototypal inheritance) 的原理?原型繼承的基礎是原型鏈查找。
原型鏈查找基本概念:
每一個函數 F 都有一個原型對象(prototype)F.prototype
每一個函數都可以通過 new 關鍵字化身成為一個類構造函數,new F 會產生一個對象 O
在調用對象的某個屬性或者方法,比如 http://O.xxx 的時候,會首先查找對象自身是否有這個方法或者屬性,如果沒找到就會去對象的構造函數的原型對象中查找(注意有兩個定語),也就是查找 O 的構造函數 F 的原型對象 http://F.prototype.xxx
F.prototype 也是一個對象,查找 http://F.prototype.xxx 的時候會重復第 3 步的過程
這里只是聲明一個叫foo的function,直接用()執行這樣是不成功的,想要變成IIFE就要把聲明變成表達式,就可以立即執行了,可以這樣(function foo(){})()或者(function foo(){}()),這就是用括號把定義強轉成表達式,當然還有其他方法,關鍵就是聲明不可以執行,表達式才可以執行。
描述以下變量的區別:null,undefined 或 undeclared? 該如何檢測它們?undefined:未定義,在變量沒有賦值的時候的值即為undefined。“缺少值”,就是此處應該有一個值,但是還沒有定義。
underclared:即為被污染的命名,訪問沒有被聲明的變量,會拋出異常,終止執行。嘗試訪問一個undeclared的變量,瀏覽器會報錯,JS執行會中斷。
null:是一個空的對象引用。“沒有對象”,即該處不應該有值
區別:
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
該如何檢測它們?
var obj; obj ===undefined; //檢測undfined 方法一 typeof obj === ‘undefined’;//檢測undefined方法2 obj = null; obj === null;//來檢測null typeof null;//‘object’什么是閉包 (closure),如何使用它,為什么要使用它?
定義:閉包就是可以讀取到其他函數內部變量的函數。
閉包的用途:
可以讀取函數內部的變量。(外界無法訪問函數的內部的私有方法和變量,只能通過提供的接口訪問)
讓變量的值始終保持在內存中。
可以避免污染全局變量,實現私有方法或者變量等
注意:
由于閉包會使得函數中的變量都被保存在內存中,內存消耗很大,所以不能濫用閉包,否則會造成網頁的性能問題,在IE中可能導致內存泄露。解決方法是,在退出函數之前,將不使用的局部變量全部刪除。
閉包會在父函數外部,改變父函數內部變量的值。所以,如果你把父函數當作對象(object)使用,把閉包當作它的公用方法(Public Method),把內部變量當作它的私有屬性(private value),這時一定要小心,不要隨便改變父函數內部變量的值。
請舉出一個匿名函數的典型用例?匿名函數可以用作回調函數執行,可以防止全局變量污染。
在 JS 框架中常使用匿名函數來避免全局變量的污染。
$.(“input”).each(function(e){this.val(‘OK’)});
(function(){})();
$(document).ready(function(){ });
$(function() {})
原生對象:獨立于宿主環境的 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()其實就是函數綁定。函數的接收者取決于他是如何被調用,可以通過調用.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動態創建script標簽,回調函數。Ajax是頁面無刷新請求數據操作,動態添加一個<script>標簽,而script標簽的src屬性是沒有跨域的限制的。這樣說來,這種跨域方式其實與ajax XmlHttpRequest協議無關了。
當GET請求從被調用頁面返回時,可以返回一段JavaScript代碼,這段代碼會自動調用主頁面中的一個callback函數。
優點:不受同源策略的影響,它的兼容性更好,在更加古老的瀏覽器中都可以運行,不需要XMLHttpRequest或ActiveX的支持;并且在請求完畢后可以通過調用callback的方式回傳結果
缺點:只支持GET請求而不支持POST等其它類型的HTTP請求;它只支持跨域HTTP請求這種情況,不能解決不同域的兩個頁面之間如何進行JavaScript調用的問題。
在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什么是三元表達式 (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模塊和庫之間潛在的名稱沖突。
在JavaScript源文件的開頭包含 use strict 有什么意義和好處?
這種技術的另一個特點是,允許一個易于引用的(假設更短的)別名用于全局變量。這通常用于,例如,jQuery插件中。jQuery允許你使用jQuery.noConflict(),來禁用 $ 引用到jQuery命名空間。在完成這項工作之后,你的代碼仍然可以使用$ 利用這種閉包技術,如下所示:
(function($) { /* jQuery plugin code referencing $ */ } )(jQuery);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中的數字和浮點精度的處理相同,因此,可能不會總是產生預期的結果。
討論寫函數 isInteger(x) 的可能方法,用于確定x是否是整數
以上所提供的例子就是一個演示了這個問題的典型例子。但出人意料的是,它會輸出:
0.30000000000000004falseECMAScript 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比較明顯而易見的那部分:
寫一個簡單的函數(少于80個字符),要求返回一個布爾值指明字符串是否為回文結構。
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被調用的,因此會稍微延遲)。下面這個函數在 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 + " " + "innerArg = " + innerArg + " " + "outerVar = " + outerVar + " " + "innerVar = " + innerVar + " " + "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屬性不存在。
創建一個給定頁面上的一個DOM元素,就會去訪問元素本身及其所有子元素(不只是它的直接子元素)的函數。對于每個被訪問的元素,函數應該傳遞元素到提供的回調函數
其中一種修復stoleSecretIdentity() 函數的方法如下:
var stoleSecretIdentity = hero.getSecretIdentity.bind(hero);此函數的參數為:
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 } }面試題收集面試題一
面試題二
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/94399.html
摘要:獲取的對象范圍方法獲取的是最終應用在元素上的所有屬性對象即使沒有代碼,也會把默認的祖宗八代都顯示出來而只能獲取元素屬性中的樣式。因此對于一個光禿禿的元素,方法返回對象中屬性值如果有就是據我測試不同環境結果可能有差異而就是。 花了很長時間整理的前端面試資源,喜歡請大家不要吝嗇star~ 別只收藏,點個贊,點個star再走哈~ 持續更新中……,可以關注下github 項目地址 https:...
摘要:一基礎接口的意義百度規范擴展回調抽象類的意義我的前端面試經歷百度前端掘金博主就讀于電子科技大學,大三狗一枚面試是個漫長的過程,從海投到收獲電話面試,一面二面三面,一個步驟出錯那么后面就宣告終結。 一道常被人輕視的前端 JS 面試題 - 前端 - 掘金 目錄前言第一問第二問變量聲明提升函數表達式第三問第四問第五問第六問構造函數的返回值第七問最后前言 年前剛剛離職了,分享下我曾經出過的一道...
摘要:并總結經典面試題集各種算法和插件前端視頻源碼資源于一身的文檔,優化項目,在瀏覽器端的層面上提升速度,幫助初中級前端工程師快速搭建項目。 本文是關注微信小程序的開發和面試問題,由基礎到困難循序漸進,適合面試和開發小程序。并總結vue React html css js 經典面試題 集各種算法和插件、前端視頻源碼資源于一身的文檔,優化項目,在瀏覽器端的層面上提升速度,幫助初中級前端工程師快...
閱讀 2033·2023-04-25 22:50
閱讀 2845·2021-09-29 09:35
閱讀 3401·2021-07-29 10:20
閱讀 3170·2019-08-29 13:57
閱讀 3369·2019-08-29 13:50
閱讀 3045·2019-08-26 12:10
閱讀 3545·2019-08-23 18:41
閱讀 2646·2019-08-23 18:01