37道WEB前端開發面試題之JavaScript篇章!
參考: 《JavaScript》高級程序設計第21章:Ajax和Comet jQuery中Ajax操作
- apply和call的用法和區別:
用法:
都能繼承另一個對象的方法和屬性,區別在於參數列表不一樣
區別:
Function.apply(obj, args) args是一個數組,作為參數傳給Function
Function.call(obj, arg1, arg2,...) arg*是參數列表
apply一個妙用: 可以將一個數組默認的轉化為一個參數列表
舉個栗子: 有一個數組arr要push進一個新的數組中去, 如果用call的話需要把數組中的元素一個個取出來再push, 而用apply只有Array.prototype.push.apply(this, arr)
- bind函數的兼容性
用法:
bind()函數會創建一個新函數, 為綁定函數。當調用這個綁定函數時,綁定函數會以創建它時傳入bind方法的第一個參數作為this,傳入bind方法的第二個以及以後的參數加上綁定函數運行時本身的參數按照順序作為原函數的參數來調用原函數.
一個綁定函數也能使用new操作符創建對象:這種行為就像把原函數當成構造器。提供的 this 值被忽略,同時調用時的參數被提供給模擬函數。
- 解釋下事件代理
事件委托利用了事件冒泡, 只指定一個事件處理程序, 就可以管理某一類型的所有事件.
例: html部分: 要點擊li彈出其id
html部分
<ul id="list">
<li id="li-1">Li 2</li>
<li id="li-2">Li 3</li>
<li id="li-3">Li 4</li>
<li id="li-4">Li 5</li>
<li id="li-5">Li 6</li>
<li id="li-6">Li 7</li>
</ul>
//js部分
document.getElementById("list").addHandler("click", function(e){
var e = e || window.event;
var target = e.target || e.srcElement;
if(target.nodeName.toUpperCase == "LI"){
console.log("List item", e,target.id, "was clicked!");
}
});
- 解釋下js中this是怎麽工作的?
this 在 JavaScript 中主要由以下五種使用場景。
作為函數調用,this 綁定全局對象,瀏覽器環境全局對象為 window 。
內部函數內部函數的 this 也綁定全局對象,應該綁定到其外層函數對應的對象上,這是 JavaScript的缺陷,用that替換。
作為構造函數使用,this 綁定到新創建的對象。
作為對象方法使用,this 綁定到該對象。
使用apply或call調用 this 將會被顯式設置為函數調用的第一個參數。
- 繼承
參考:js怎麽實現繼承?
- AMD vs. CommonJS?
AMD是依賴提前加載
CMD是依賴延時加載
- 什麽是哈希表?
哈希表(Hash table,也叫散列表),是根據關鍵碼值(Key value)而直接進行訪問的數據結構。也就是說,它通過把關鍵碼值映射到表中一個位置來訪問記錄,以加快查找的速度。這個映射函數叫做散列函數,存放記錄的數組叫做散列表。
使用哈希查找有兩個步驟:
使用哈希函數將被查找的鍵轉換為數組的索引。在理想的情況下,不同的鍵會被轉換為不同的索引值,但是在有些情況下我們需要處理多個鍵被哈希到同一個索引值的情況。
所以哈希查找的第二個步驟就是處理沖突。處理哈希碰撞沖突。有很多處理哈希碰撞沖突的方法,比如拉鏈法和線性探測法。
元素特征轉變為數組下標的方法就是散列法。散列法當然不止一種,下面列出三種比較常用的:
1,除法散列法
最直觀的一種,上圖使用的就是這種散列法,公式: index = value % 16
學過匯編的都知道,求模數其實是通過一個除法運算得到的,所以叫“除法散列法”。
2,平方散列法
求index是非常頻繁的操作,而乘法的運算要比除法來得省時(對現在的CPU來說,估計我們感覺不出來),所以我們考慮把除法換成乘法和一個位移操作。公式: index = (value * value) >> 28 (右移,除以2^28。記法:左移變大,是乘。右移變小,是除。)
如果數值分配比較均勻的話這種方法能得到不錯的結果,但我上面畫的那個圖的各個元素的值算出來的index都是0——非常失敗。也許你還有個問題,value如果很大,value * value不會溢出嗎?答案是會的,但我們這個乘法不關心溢出,因為我們根本不是為了獲取相乘結果,而是為了獲取index。
3,斐波那契(Fibonacci)散列法
解決沖突的方法:
- 拉鏈法
將大小為M 的數組的每一個元素指向一個條鏈表,鏈表中的每一個節點都存儲散列值為該索引的鍵值對,這就是拉鏈法.
對采用拉鏈法的哈希實現的查找分為兩步,首先是根據散列值找到等一應的鏈表,然後沿著鏈表順序找到相應的鍵。
- 線性探測法:
使用數組中的空位解決碰撞沖突
參考:淺談算法和數據結構: 十一 哈希表 哈希表的工作原理
- 什麽是閉包? 閉包有什麽作用?
閉包是指有權訪問另一個函數作用域中的變量的函數. 創建閉包常見方式,就是在一個函數內部創建另一個函數.
作用:
匿名自執行函數 (function (){ ... })(); 創建了一個匿名的函數,並立即執行它,由於外部無法引用它內部的變量,因此在執行完後很快就會被釋放,關鍵是這種機制不會汙染全局對象。
緩存, 可保留函數內部的值
實現封裝
實現模板
參考: js閉包的用途
- 偽數組:
什麽是偽數組:
偽數組是能通過Array.prototype.slice 轉換為真正的數組的帶有length屬性的對象
比如arguments對象,還有像調用getElementsByTagName,document.childNodes之類的,它們都返回NodeList對象都屬於偽數組
我們可以通過Array.prototype.slice.call(fakeArray)將偽數組轉變為真正的Array對象: 返回新數組而不會修改原數組
參考:偽數組
- undefined和null的區別, 還有undeclared:
null表示沒有對象, 即此處不該有此值. 典型用法:
(1) 作為函數的參數,表示該函數的參數不是對象。
(2) 作為對象原型鏈的終點。
( 3 ) null可以作為空指針. 只要意在保存對象的值還沒有真正保存對象,就應該明確地讓該對象保存null值.
undefined表示缺少值, 即此處應該有值, 但還未定義.
(1)變量被聲明了,但沒有賦值時,就等於undefined。
(2) 調用函數時,應該提供的參數沒有提供,該參數等於undefined。
(3)對象沒有賦值的屬性,該屬性的值為undefined。
(4)函數沒有返回值時,默認返回undefined。
undeclared即為被汙染的命名, 訪問沒有被聲明的變量, 則會拋出異常, 終止執行. 即undeclared是一種語法錯誤
參考: undefined與null的區別
- 事件冒泡機制:
從目標元素開始,往頂層元素傳播。途中如果有節點綁定了相應的事件處理函數,這些函數都會被一次觸發。如果想阻止事件起泡,可以使用e.stopPropagation()(Firefox)或者e.cancelBubble=true(IE)來組織事件的冒泡傳播。
- 解釋下為什麽接下來這段代碼不是 IIFE(立即調用的函數表達式):function foo(){ }();?
而函數定義(語句以function關鍵字開始)是不能被立即執行的,這無疑會導致語法的錯誤(SyntaxError)。當函數定義代碼段包裹在括號內,使解析器可以將之識別為函數表達式,然後調用。IIFE: (function foo(){})()
區分 (function(){})(); 和 (function(){}()); 其實兩者實現效果一樣。
函數字面量:首先聲明一個函數對象,然後執行它。(function () { alert(1); })();
優先表達式:由於Javascript執行表達式是從圓括號裏面到外面,所以可以用圓括號強制執行聲明的函數。(function () { alert(2); }());
- "attribute" 和 "property" 的區別是什麽?
DOM元素的attribute和property兩者是不同的東西。attribute翻譯為“特性”,property翻譯為“屬性”。
attribute是一個特性節點,每個DOM元素都有一個對應的attributes屬性來存放所有的attribute節點,attributes是一個類數組的容器,說得準確點就是NameNodeMap,不繼承於Array.prototype,不能直接調用Array的方法。attributes的每個數字索引以名值對(name=”value”)的形式存放了一個attribute節點。<div class="box" id="box" gameid="880">hello</div>
property就是一個屬性,如果把DOM元素看成是一個普通的Object對象,那麽property就是一個以名值對(name=”value”)的形式存放在Object中的屬性。要添加和刪除property和普通的對象類似。
很多attribute節點還有一個相對應的property屬性,比如上面的div元素的id和class既是attribute,也有對應的property,不管使用哪種方法都可以訪問和修改。
總之,attribute節點都是在HTML代碼中可見的,而property只是一個普通的名值對屬性。
- 請指出 document load 和 document ready 兩個事件的區別。
document.ready和onload的區別——JavaScript文檔加載完成事件。頁面加載完成有兩種事件:
一是ready,表示文檔結構已經加載完成(不包含圖片等非文字媒體文件)
二是onload,指示頁面包含圖片等文件在內的所有元素都加載完成。
jQuery中$(function(){});他的作用或者意義就是:在DOM加載完成後就可以可以對DOM進行操作。一般情況先一個頁面響應加載的順序是,域名解析-加載html-加載js和css-加載圖片等其他信息。
- 什麽是use strict? 其好處壞處分別是什麽?
在所有的函數 (或者所有最外層函數) 的開始處加入 "use strict"; 指令啟動嚴格模式。
"嚴格模式"有兩種調用方法
1)將"use strict"放在腳本文件的第一行,則整個腳本都將以"嚴格模式"運行。如果這行語句不在第一行,則無效,整個腳本以"正常模式"運行。如果不同模式的代碼文件合並成一個文件,這一點需要特別註意。
2)將整個腳本文件放在一個立即執行的匿名函數之中。
好處
-
消除Javascript語法的一些不合理、不嚴謹之處,減少一些怪異行為;
-
消除代碼運行的一些不安全之處,保證代碼運行的安全;
-
提高編譯器效率,增加運行速度;
- 為未來新版本的Javascript做好鋪墊。
壞處
同樣的代碼,在"嚴格模式"中,可能會有不一樣的運行結果;一些在"正常模式"下可以運行的語句,在"嚴格模式"下將不能運行
- 瀏覽器端的js包括哪幾個部分?
核心( ECMAScript) , 文檔對象模型(DOM), 瀏覽器對象模型(BOM)
- DOM包括哪些對象?
DOM是針對HTML和XML文檔的一個API(應用程序編程接口). DOM描繪了一個層次化的節點樹, 允許開發人員添加, 移除和修改頁面的某一部分.
常用的DOM方法:
getElementById(id)
getElementsByTagName()
appendChild(node)
removeChild(node)
replaceChild()
insertChild()
createElement()
createTextNode()
getAttribute()
setAttribute()
常用的DOM屬性
innerHTML 節點(元素)的文本值
parentNode 節點(元素)的父節點
childNodes
attributes 節點(元素)的屬性節點
參考: HTML DOM 方法
- js有哪些基本類型?
Undefined, Null, Boolean, Number, String
Object是復雜數據類型, 其本質是由一組無序的名值對組成的.
- 基本類型與引用類型有什麽區別?
基本類型如上題所示. 引用類型則有: Object, Array, Date, RegExp, Function
存儲
基本類型值在內存中占據固定大小的空間,因此被保存在棧內存中
引用類型的值是對象, 保存在堆內存中. 包含引用類型的變量實際上包含的並不是對象本身, 而是一個指向改對象的指針
復制
從一個變量向另一個變量復制基本類型的值, 會創建這個值的一個副本
從一個變量向另一個變量復制引用類型的值, 復制的其實是指針, 因此兩個變量最終都指向同一個對象
檢測類型
確定一個值是哪種基本類型可以用typeof操作符,
而確定一個值是哪種引用類型可以使用instanceof操作符
- 關於js的垃圾收集例程
js是一門具有自動垃圾回收機制的編程語言,開發人員不必關心內存分配和回收問題
離開作用域的值將被自動標記為可以回收, 因此將在垃圾收集期間被刪除
"標記清除"是目前主流的垃圾收集算法, 這種算法的思路是給當前不使用的值加上標記, 然後再回收其內存
另一種垃圾收集算法是"引用計數", 這種算法的思想是跟蹤記錄所有值被引用的次數. js引擎目前都不再使用這種算法, 但在IE中訪問非原生JS對象(如DOM元素)時, 這種算法仍然可能會導致問題
當代碼中存在循環引用現象時, "引用計數" 算法就會導致問題
解除變量的引用不僅有助於消除循環引用現象, 而且對垃圾收集也有好處. 為了確保有效地回收內存, 應該及時解除不再使用的全局對象, 全局對象屬性以及循環引用變量的引用
- ES5中, 除了函數,什麽能夠產生作用域?
try-catch 和with延長作用域. 因為他們都會創建一個新的變量對象.
這兩個語句都會在作用域鏈的前端添加一個變量對象. 對with語句來說, 會將指定的對象添加到作用域鏈中. 對catch語句來說, 會創建一個新的變量對象, 其中包含的是被拋出的錯誤對象的聲明.
當try代碼塊中發生錯誤時,執行過程會跳轉到catch語句,然後把異常對象推入一個可變對象並置於作用域的頭部。在catch代碼塊內部,函數的所有局部變量將會被放在第二個作用域鏈對象中。請註意,一旦catch語句執行完畢,作用域鏈機會返回到之前的狀態。try-catch語句在代碼調試和異常處理中非常有用,因此不建議完全避免。你可以通過優化代碼來減少catch語句對性能的影響。一個很好的模式是將錯誤委托給一個函數處理
with(object) {statement}。它的意思是把object添加到作用域鏈的頂端
//代碼片段
function buildUrl(){
var qs = "?debug=true";
//with接收location對象, 因此其變量對象中就包含了location對象的所有屬性和方法, 而這個變量對象被添加到了作用域鏈的前端
with(location){
//這裏的href其實是location.href. 創建了一個名為url的變量, 就成了函數執行環境的一部分
var url = href + qs;
}
return url;
}
參考: js try、catch、finally語句還有with語句 JavaScript 開發進階:理解 JavaScript 作用域和作用域鏈
- js有幾種函數調用方式?
方法調用模型 var obj = { func : function(){};} obj.func()
函數調用模式 var func = function(){} func();
構造器調用模式
apply/ call調用模式
- 描述事件模型?IE的事件模型是怎樣的?事件代理是什麽?事件代理中怎麽定位實際事件產生的目標?
捕獲->處於目標->冒泡,IE應該是只有冒泡沒有捕獲。
事件代理就是在父元素上綁定事件來處理,通過event對象的target來定位。
- js動畫有哪些實現方法?
用定時器 setTimeout和setInterval
- 還有什麽實現動畫的方法?
js動畫:
使用定時器 setTimeout和setInterval
CSS : transition , animation
transition 包含4種屬性:transition-delaytransition-durationtransition-propertytransition-timing-function,對應動畫的4種屬性: 延遲、持續時間、對應css屬性和緩動函數,
transform 包含7種屬性:animation-nameanimation-durationanimation-timing-functionanimation-delayanimation-directionanimation-iteration-countanimation-fill-modeanimation-play-state,它們可以定義動畫名稱,持續時間,緩動函數,動畫延遲,動畫方向,重復次數,填充模式。
HTML5 動畫
canvas
svg
webgl
參考:前端動畫效果實現的簡單比較
- 面向對象有哪幾個特點?
封裝, 繼承, 多態
- 如何判斷屬性來自自身對象還是原型鏈?
hasOwnPrototype
- ES6新特性
1) 箭頭操作符 inputs=>outputs: 操作符左邊是輸入的參數,而右邊則是進行的操作以及返回的值
2) 支持類, 引入了class關鍵字. ES6提供的類實際上就是JS原型模式的包裝
3) 增強的對象字面量.
-
可以在對象字面量中定義原型 proto: xxx //設置其原型為xxx,相當於繼承xxx
-
定義方法可以不用function關鍵字
- 直接調用父類方法
4) 字符串模板: ES6中允許使用反引號 ` 來創建字符串,此種方法創建的字符串裏面可以包含由美元符號加花括號包裹的變量${vraible}。
5) 自動解析數組或對象中的值。比如若一個函數要返回多個值,常規的做法是返回一個對象,將每個值做為這個對象的屬性返回。但在ES6中,利用解構這一特性,可以直接返回一個數組,然後數組中的值會自動被解析到對應接收該值的變量中。
6) 默認參數值: 現在可以在定義函數的時候指定參數的默認值了,而不用像以前那樣通過邏輯或操作符來達到目的了。
7) 不定參數是在函數中使用命名參數同時接收不定數量的未命名參數。在以前的JavaScript代碼中我們可以通過arguments變量來達到這一目的。不定參數的格式是三個句點後跟代表所有不定參數的變量名。比如下面這個例子中,…x代表了所有傳入add函數的參數。
8) 拓展參數則是另一種形式的語法糖,它允許傳遞數組或者類數組直接做為函數的參數而不用通過apply。
9) let和const關鍵字: 可以把let看成var,只是它定義的變量被限定在了特定範圍內才能使用,而離開這個範圍則無效。const則很直觀,用來定義常量,即無法被更改值的變量。
10) for of值遍歷 每次循環它提供的不是序號而是值。
11) iterator, generator
12) 模塊
13) Map, Set, WeakMap, WeakSet
14) Proxy可以監聽對象身上發生了什麽事情,並在這些事情發生後執行一些相應的操作。一下子讓我們對一個對象有了很強的追蹤能力,同時在數據綁定方面也很有用處。
15) Symbols Symbol 通過調用symbol函數產生,它接收一個可選的名字參數,該函數返回的symbol是唯一的。之後就可以用這個返回值做為對象的鍵了。Symbol還可以用來創建私有屬性,外部無法直接訪問由symbol做為鍵的屬性值。
16) Math, Number, String, Object的新API
17) Promises是處理異步操作的一種模式
參考:ES6新特性概覽
- 如何獲取某個DOM節點,節點遍歷方式
獲取節點: getElementById() getElementsByTagName()
節點遍歷:先序遍歷DOM樹的5種方法
- 用LESS如何給某些屬性加瀏覽器前綴?
可以自定義一個函數
//代碼片段
.border-radius(@values) {
-webkit-border-radius: @values;
-moz-border-radius: @values;
border-radius: @values;
}
div {
.border-radius(10px);
}
- js異步模式如何實現?
參考:JavaScript異步編程的Promise模式
- 圖片預加載的實現
使用jQuery圖片預加載插件Lazy Load
1.加載jQuery, 與jquery.lazyload.js
2.設置圖片的占位符為data-original, 給圖片一個特別的標簽,比如class=".lazy"
3.然後延遲加載: $(‘img.lazy‘).lazyload();這個函數可以選擇一些參數:
3.1.圖片預先加載距離:threshold,通過設置這個值,在圖片未出現在可視區域的頂部距離這個值時加載。
3.2.事件綁定加載的方式:event
3.3.圖片限定在某個容器內:container
使用js實現圖片加載: 就是new一個圖片對象, 綁定onload函數, 賦值url
用CSS實現圖片的預加載
寫一個CSS樣式設置一批背景圖片,然後將其隱藏
改進: 使用js來推遲預加載時間, 防止與頁面其他內容一起加載
用Ajax實現預加載
其實就是通過ajax請求請求圖片地址. 還可以用這種方式加載css,js文件等
- 如果在同一個元素上綁定了兩個click事件, 一個在捕獲階段執行, 一個在冒泡階段執行. 那麽當觸發click條件時, 會執行幾個事件? 執行順序是什麽?
我在回答這個題的時候說是兩個事件, 先執行捕獲的後執行冒泡的. 其實是不對的.
綁定在目標元素上的事件是按照綁定的順序執行的!!!!
即: 綁定在被點擊元素的事件是按照代碼順序發生,其他元素通過冒泡或者捕獲“感知”的事件,按照W3C的標準,先發生捕獲事件,後發生冒泡事件。所有事件的順序是:其他元素捕獲階段事件 -> 本元素代碼順序事件 -> 其他元素冒泡階段事件 。
參考: JavaScript-父子dom同時綁定兩個點擊事件,一個用捕獲,一個用冒泡時執行順序
- js中怎麽實現塊級作用域?
使用匿名函數, (立即執行函數)
(function(){...})()
使用es6
塊級作用域引入了兩種新的聲明形式,可以用它們定義一個只存在於某個語句塊中的變量或常量.這兩種新的聲明關鍵字為:
let: 語法上非常類似於var, 但定義的變量只存在於當前的語句塊中
const: 和let類似,但聲明的是一個只讀的常量
使用let代替var可以更容易的定義一個只在某個語句塊中存在的局部變量,而不用擔心它和函數體中其他部分的同名變量有沖突.在let語句內部用var聲明的變量和在let語句外部用var聲明的變量沒什麽差別,它們都擁有函數作用域,而不是塊級作用域.
-
構造函數裏定義function和使用prototype.func的區別?
-
直接調用function,每一個類的實例都會拷貝這個函數,弊端就是浪費內存(如上)。prototype方式定義的方式,函數不會拷貝到每一個實例中,所有的實例共享prototype中的定義,節省了內存。
-
但是如果prototype的屬性是對象的話,所有實例也會共享一個對象(這裏問的是函數應該不會出現這個情況),如果其中一個實例改變了對象的值,則所有實例的值都會被改變。同理的話,如果使用prototype調用的函數,一旦改變,所有實例的方法都會改變。——不可以對實例使用prototype屬性,只能對類和函數用。
- js實現對象的深克隆
因為js中數據類型分為基本數據類型(number, string, boolean, null, undefined)和引用類型值(對象, 數組, 函數). 這兩類對象在復制克隆的時候是有很大區別的. 原始類型存儲的是對象的實際數據, 而對象類型存儲的是對象的引用地址(對象的實際內容單獨存放, 為了減少數據開銷通常放在內存中). 此外, 對象的原型也是引用對象, 它把原型的屬性和方法放在內存中, 通過原型鏈的方式來指向這個內存地址.
於是克隆也會分為兩類:
淺度克隆:
原始類型為值傳遞, 對象類型仍為引用傳遞
深度克隆:
所有元素或屬性均完全復制, 與原對象完全脫離, 也就是說所有對於新對象的修改都不會反映到原對象中
深度克隆實現:
//代碼片段
function clone(obj){
if(typeof(obj)== ‘object‘){
var result = obj instanceof Array ? [] : {};
for(var i in obj){
var attr = obj[i];
result[i] = arguments.callee(attr);
}
return result;
} else {
return obj;
}
};
參考: JavaScript深克隆 javascript中對象的深度克隆
37道WEB前端開發面試題之JavaScript篇章!