js前端面試題及答案整理(一)
Part1 手寫(xiě)代碼
現(xiàn)場(chǎng)手寫(xiě)代碼是現(xiàn)在面試中很常見(jiàn)的一類(lèi)面試題,考察基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu)與算法能力。
1 數(shù)組去重的實(shí)現(xiàn)
基本數(shù)組去重
Array.prototype.unique = function(){ var result = []; this.forEach(function(v){ if(result.indexOf(v) < 0){ result.push(v); } }); return result; }
•利用hash表去重,這是一種空間換時(shí)間的方法
Array.prototype.unique = function(){ var result = [],hash = {}; this.forEach(function(v){ if(!hash[v]){ hash[v] = true; result.push(v); } }); return result; }
上面的方法存在一個(gè)bug,對(duì)于數(shù)組[1,2,'1','2',3],去重結(jié)果為[1,2,3],原因在于對(duì)象對(duì)屬性索引時(shí)會(huì)進(jìn)行強(qiáng)制類(lèi)型轉(zhuǎn)換,arr[‘1']和arr[1]得到的都是arr[1]的值,因此需做一些改變:
Array.prototype.unique = function(){ var result = [],hash = {}; this.forEach(function(v){ var type = typeof(v); //獲取元素類(lèi)型 hash[v] || (hash[v] = new Array()); if(hash[v].indexOf(type) < 0){ hash[v].push(type); //存儲(chǔ)類(lèi)型 result.push(v); } }); return result; }
•先排序后去重
Array.prototype.unique = function(){ var result = [this[0]]; this.sort(); this.forEach(function(v){ v != result[result.length - 1] && result.push(v); //僅與result最后一個(gè)元素比較 }); }
2 快速排序的實(shí)現(xiàn)
方法一(盡可能不用js數(shù)組方法):
function quickSort(arr){ qSort(arr,0,arr.length - 1); } function qSort(arr,low,high){ if(low < high){ var partKey = partition(arr,low,high); qSort(arr,low, partKey - 1); qSort(arr,partKey + 1,high); } } function partition(arr,low,high){ var key = arr[low]; //使用第一個(gè)元素作為分類(lèi)依據(jù) while(low < high){ while(low < high && arr[high] >= arr[key]) high--; arr[low] = arr[high]; while(low < high && arr[low] <= arr[key]) low++; arr[high] = arr[low]; } arr[low] = key; return low; }
方法二(使用js數(shù)組方法):
function quickSort(arr){ if(arr.length <= 1) return arr; var index = Math.floor(arr.length/2); var key = arr.splice(index,1)[0]; var left = [],right = []; arr.forEach(function(v){ v <= key ? left.push(v) : right.push(v); }); return quickSort(left).concat([key],quickSort(right)); }
另外要知道,快速排序的平均時(shí)間復(fù)雜度O(nlogn),最壞情況是有序的情況,時(shí)間復(fù)雜度為n的平方,另外快速排序是不穩(wěn)定的。
Part2 JavaScript相關(guān)
1 JavaScript基礎(chǔ)數(shù)據(jù)類(lèi)型
JavaScript數(shù)據(jù)類(lèi)型包括原始類(lèi)型和引用類(lèi)型,原始類(lèi)型有五個(gè):
Number(數(shù)值) String(字符串) Boolean(布爾) Null(空) Undefined(未定義)
引用類(lèi)型有一個(gè):
Object(對(duì)象)
通過(guò)typeof(x)可以返回一個(gè)變量x的數(shù)據(jù)類(lèi)型“number”、“string”、“boolean”、“undefined”、"object",這里要注意一點(diǎn):typeof運(yùn)算符對(duì)于null類(lèi)型返回的是object。
《JavaScript高級(jí)程序設(shè)計(jì)》:
這實(shí)際上是JavaScript最初實(shí)現(xiàn)中的一個(gè)錯(cuò)誤,后來(lái)被ECMAScript沿用了?,F(xiàn)在null被認(rèn)為是對(duì)象的占位符,從而解釋了這一矛盾。但是從技術(shù)上來(lái)說(shuō),它仍然是原始值。
2 談一談JavaScript作用域鏈
當(dāng)執(zhí)行一段JavaScript代碼(全局代碼或函數(shù))時(shí),JavaScript引擎會(huì)創(chuàng)建為其創(chuàng)建一個(gè)作用域又稱(chēng)為執(zhí)行上下文(Execution Context),在頁(yè)面加載后會(huì)首先創(chuàng)建一個(gè)全局的作用域,然后每執(zhí)行一個(gè)函數(shù),會(huì)建立一個(gè)對(duì)應(yīng)的作用域,從而形成了一條作用域鏈。每個(gè)作用域都有一條對(duì)應(yīng)的作用域鏈,鏈頭是全局作用域,鏈尾是當(dāng)前函數(shù)作用域。
作用域鏈的作用是用于解析標(biāo)識(shí)符,當(dāng)函數(shù)被創(chuàng)建時(shí)(不是執(zhí)行),會(huì)將this、arguments、命名參數(shù)和該函數(shù)中的所有局部變量添加到該當(dāng)前作用域中,當(dāng)JavaScript需要查找變量X的時(shí)候(這個(gè)過(guò)程稱(chēng)為變量解析),它首先會(huì)從作用域鏈中的鏈尾也就是當(dāng)前作用域進(jìn)行查找是否有X屬性,如果沒(méi)有找到就順著作用域鏈繼續(xù)查找,直到查找到鏈頭,也就是全局作用域鏈,仍未找到該變量的話(huà),就認(rèn)為這段代碼的作用域鏈上不存在x變量,并拋出一個(gè)引用錯(cuò)誤(ReferenceError)的異常。
3 如何理解JavaScript原型鏈
JavaScript中的每個(gè)對(duì)象都有一個(gè)prototype屬性,我們稱(chēng)之為原型,而原型的值也是一個(gè)對(duì)象,因此它也有自己的原型,這樣就串聯(lián)起來(lái)了一條原型鏈,原型鏈的鏈頭是object,它的prototype比較特殊,值為null。
原型鏈的作用是用于對(duì)象繼承,函數(shù)A的原型屬性(prototype property)是一個(gè)對(duì)象,當(dāng)這個(gè)函數(shù)被用作構(gòu)造函數(shù)來(lái)創(chuàng)建實(shí)例時(shí),該函數(shù)的原型屬性將被作為原型賦值給所有對(duì)象實(shí)例,比如我們新建一個(gè)數(shù)組,數(shù)組的方法便從數(shù)組的原型上繼承而來(lái)。
當(dāng)訪(fǎng)問(wèn)對(duì)象的一個(gè)屬性時(shí), 首先查找對(duì)象本身, 找到則返回; 若未找到, 則繼續(xù)查找其原型對(duì)象的屬性(如果還找不到實(shí)際上還會(huì)沿著原型鏈向上查找, 直至到根). 只要沒(méi)有被覆蓋的話(huà), 對(duì)象原型的屬性就能在所有的實(shí)例中找到,若整個(gè)原型鏈未找到則返回undefined;
4 JavaScript變量聲明提前
《JavaScript權(quán)威指南》中是這樣解釋的:JavaScript變量在聲明之前已經(jīng)可用,JavaScript的這個(gè)特性被非正式的稱(chēng)為聲明提前(hoisting),即JavaScript函數(shù)中聲明的所有變量(但不涉及賦值)都被“提前”至函數(shù)的頂部。
從一個(gè)例子來(lái)看:
var scope = "global"; function myFunc(){ console.log(scope); var scope = "local"; }
控制臺(tái)打印出來(lái)的不是“global”而是“undefined”,這是因?yàn)樵趍yFunc這個(gè)函數(shù)的作用域中,局部變量scope聲明被提前至函數(shù)頂部,而此時(shí),scope僅聲明,未賦值,因此輸出undefined。實(shí)際上,上面的代碼和下面的效果是一樣的:
var scope = "global"; function myFunc(){ var scope; console.log(scope); scope = "local"; }
5 如何理解和應(yīng)用JavaScript閉包
關(guān)于閉包具體的定義文獻(xiàn)中給的概念很抽象,我認(rèn)為閉包是一種使函數(shù)能夠都去其它函數(shù)的局部變量的語(yǔ)法機(jī)制。
舉個(gè)例子:
function outFunc(){ var name = "Vicfeel"; function inFunc(){ console.log(name); } return inFunc; } inFunc(); //控制臺(tái)顯示"Vicfeel"
這這個(gè)例子我們可以看出,在函數(shù)inFunc中依然可以訪(fǎng)問(wèn)outFunc的局部變量name。
閉包應(yīng)用舉例,模擬類(lèi)的私有屬性,利用閉包的性質(zhì),局部變量只有在sayAge方法中才可以訪(fǎng)問(wèn),而name在外部也訪(fǎng)問(wèn),從而實(shí)現(xiàn)了類(lèi)的私有屬性。
function User(){ this.name = "Vicfeel"; //共有屬性 var age = 23; //私有屬性 this.sayAge:function(){ console.log("my age is " + age); } } var user = new User(); console.log(user.name); //"Vicfeel" console.log(user.age); //"undefined" user.sayAge(); //"my age is 23"
要了解詳細(xì)的閉包,推薦一下 阮一峰的網(wǎng)絡(luò)日志-學(xué)習(xí)Javascript閉包(Closure)。
6 new構(gòu)建對(duì)象的本質(zhì)
function User(){ this.name = "Vicfeel"; this.age = 23; } var user = new User();
通過(guò)new操作符,實(shí)際上在構(gòu)造函數(shù)User中完成了如下操作:
•創(chuàng)建一個(gè)新的對(duì)象,這個(gè)對(duì)象的類(lèi)型是object;
•設(shè)置這個(gè)新的對(duì)象的內(nèi)部、可訪(fǎng)問(wèn)性和prototype屬性為構(gòu)造函數(shù)(指prototype.construtor所指向的構(gòu)造函數(shù))中設(shè)置的;
•執(zhí)行構(gòu)造函數(shù);
•返回新創(chuàng)建的對(duì)象。
function User(){ //this = {}; //this.constructor = User; this.name = "Vicfeel"; this.age = 23; //return this; } var user = new User();
如果構(gòu)造函數(shù)默認(rèn)返回的新創(chuàng)建的this對(duì)象,如果手動(dòng)return 一個(gè)變量的話(huà),如果該變量是原始類(lèi)型則無(wú)效,如果是對(duì)象,則返回該對(duì)象。
7 JavaScript代理
當(dāng)我們需要對(duì)很多元素添加事件的時(shí)候,可以通過(guò)將事件添加到它們的父節(jié)點(diǎn)而將事件委托給父節(jié)點(diǎn)來(lái)觸發(fā)處理函數(shù)。
比如我們需要向一個(gè)ul中動(dòng)態(tài)添加很多個(gè)li,需要遍歷li逐個(gè)添加點(diǎn)擊事件
<ul id='list'></ul> var count = 100; var ulList = document.getElementById("list"); //動(dòng)態(tài)構(gòu)建節(jié)點(diǎn) for(var i = count;i--;){ var liDom = document.createElement('li'); ulList.appendChild(liDom); } //綁定點(diǎn)擊事件 var liNode = ulList.getElementByTagName("li"); for(var i=0, l = liNodes.length; i < l; i++){ liNode[i].onClick = function(){ //li點(diǎn)擊事件 } }
眾所周知,DOM操作是十分消耗性能的。所以重復(fù)的事件綁定簡(jiǎn)直是性能殺手。而事件代理的核心思想,就是通過(guò)盡量少的綁定,去監(jiān)聽(tīng)盡量多的事件。如何做呢?答案是利用事件冒泡機(jī)制,對(duì)其父節(jié)點(diǎn)ul進(jìn)行事件綁定(Event Bubble),然后通過(guò)event.target來(lái)判斷是哪個(gè)節(jié)點(diǎn)觸發(fā)的事件,從而減少很多EventHandler的綁定。
var count = 100; var ulList = document.getElementById("list"); //動(dòng)態(tài)構(gòu)建節(jié)點(diǎn) for(var i = count;i--;){ var liDom = document.createElement('li'); ulList.appendChild(liDom); } //綁定點(diǎn)擊事件 var liNode = ulList.getElementByTagName("li"); liNode.onClick = function(e){ if(e.target && e.target.nodeName.toUpperCase == "LI") { // li點(diǎn)擊事件 } }
發(fā)現(xiàn)新內(nèi)容會(huì)持續(xù)更新...
相關(guān)文章
JavaScrip關(guān)于創(chuàng)建常量的知識(shí)點(diǎn)
這篇文章主要介紹了JavaScrip創(chuàng)建常量的相關(guān)知識(shí)點(diǎn),幫助大家對(duì)JS更加深入的學(xué)習(xí),參考下吧。2017-12-12javascript setinterval 的正確語(yǔ)法如何書(shū)寫(xiě)
setinterval是用來(lái)干什么,想必大家都知道了,下面為大家介紹下javascript setinterval 正確的語(yǔ)法,高手勿噴2014-06-06Javascript的時(shí)間戳和php的時(shí)間戳轉(zhuǎn)換注意事項(xiàng)
需要注意的是js的時(shí)間戳是13位,php的時(shí)間戳是10位,轉(zhuǎn)換函數(shù)如下,感興趣的朋友可以參考下哈2013-04-04JavaScript入門(mén)學(xué)習(xí)書(shū)籍推薦
對(duì)于許多想學(xué)習(xí) JavaScript 的朋友來(lái)說(shuō),無(wú)疑如何選擇入門(mén)的書(shū)籍是他們最頭疼的問(wèn)題,或許也是他們一直畏懼,甚至放棄學(xué)習(xí) JavaScript 的理由。2008-06-06Javascript中 關(guān)于prototype屬性實(shí)現(xiàn)繼承的原理圖
Javascript中關(guān)于prototype屬性實(shí)現(xiàn)繼承的原理圖2013-04-04JavaScript中g(shù)etUTCMinutes()方法的使用詳解
這篇文章主要介紹了JavaScript中g(shù)etUTCMinutes()方法的使用詳解,是JS入門(mén)學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-06-06