最近在前端面試,面經(jīng)是自己的和周圍同學(xué)的面經(jīng)整理出來(lái)的,持續(xù)更新有用的同學(xué)加關(guān)注嗷
手寫一個(gè)js的深克?。缊F(tuán)、愛(ài)奇藝)
function deepCopy(obj){
//判斷是否是簡(jiǎn)單數(shù)據(jù)類型,
if(typeof obj == "object"){
//復(fù)雜數(shù)據(jù)類型
var result = obj.constructor == Array ? [] : {};
for(let i in obj){
result[i] = typeof obj[i] == "object" ? deepCopy(obj[i]) : obj[i];
}
}else {
//簡(jiǎn)單數(shù)據(jù)類型 直接 == 賦值
var result = obj;
}
return result;
}
手寫組合繼承(美團(tuán)、愛(ài)奇藝、搜狗)
// 定義一個(gè)動(dòng)物類
function Animal (name) {
// 屬性
this.name = name || 'Animal';
// 實(shí)例方法
this.sleep = function(){
console.log(this.name + '正在睡覺(jué)!');
}
}
// 原型方法
Animal.prototype.eat = function(food) {
console.log(this.name + '正在吃:' + food);
};
//組合繼承
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
Cat.prototype = new Animal();
手寫一個(gè)promise(愛(ài)奇藝、搜狐)
promise是一個(gè)構(gòu)造函數(shù),下面是一個(gè)簡(jiǎn)單實(shí)例
var promise = new Promise((resolve,reject) => {
if (操作成功) {
resolve(value)
} else {
reject(error)
}
})
promise.then(function (value) {
// success
},function (value) {
// failure
})
防抖和節(jié)流
scroll 事件本身會(huì)觸發(fā)頁(yè)面的重新渲染,同時(shí) scroll 事件的 handler 又會(huì)被高頻度的觸發(fā), 因此事件的 handler 內(nèi)部不應(yīng)該有復(fù)雜操作,例如 DOM 操作就不應(yīng)該放在事件處理中。
針對(duì)此類高頻度觸發(fā)事件問(wèn)題(例如頁(yè)面 scroll ,屏幕 resize,監(jiān)聽用戶輸入等),有兩種常用的解決方法,防抖和節(jié)流。
防抖(Debouncing)
防抖技術(shù)即是可以把多個(gè)順序地調(diào)用合并成一次,也就是在一定時(shí)間內(nèi),規(guī)定事件被觸發(fā)的次數(shù)。
通俗一點(diǎn)來(lái)說(shuō),先看下面這個(gè)簡(jiǎn)化的例子,這個(gè)簡(jiǎn)單的防抖的例子大概功能就是如果 500ms 內(nèi)沒(méi)有連續(xù)觸發(fā)兩次 scroll 事件,那么才會(huì)觸發(fā)我們真正想在 scroll 事件中觸發(fā)的函數(shù)。
// 簡(jiǎn)單的防抖動(dòng)函數(shù)
function debounce(func, wait, immediate) {
// 定時(shí)器變量
var timeout;
return function() {
// 每次觸發(fā) scroll handler 時(shí)先清除定時(shí)器
clearTimeout(timeout);
// 指定 xx ms 后觸發(fā)真正想進(jìn)行的操作 handler
timeout = setTimeout(func, wait);
};
};
// 實(shí)際想綁定在 scroll 事件上的 handler
function realFunc(){
console.log("Success");
}
// 采用了防抖動(dòng)
window.addEventListener('scroll',debounce(realFunc,500));
// 沒(méi)采用防抖動(dòng)
window.addEventListener('scroll',realFunc);
完整的防抖動(dòng)函數(shù):
// 防抖動(dòng)函數(shù)
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
var myEfficientFn = debounce(function() {
// 滾動(dòng)中的真正的操作
}, 250);
// 綁定監(jiān)聽
window.addEventListener('resize', myEfficientFn);
節(jié)流(Throttling)
防抖函數(shù)確實(shí)不錯(cuò),但是也存在問(wèn)題,譬如圖片的懶加載,我希望在下滑過(guò)程中圖片不斷的被加載出來(lái),而不是只有當(dāng)我停止下滑時(shí)候,圖片才被加載出來(lái)。又或者下滑時(shí)候的數(shù)據(jù)的 ajax 請(qǐng)求加載也是同理。
這個(gè)時(shí)候,我們希望即使頁(yè)面在不斷被滾動(dòng),但是滾動(dòng) handler 也可以以一定的頻率被觸發(fā)(譬如 250ms 觸發(fā)一次),這類場(chǎng)景,就要用到另一種技巧,稱為節(jié)流函數(shù)(throttling)。
節(jié)流函數(shù),只允許一個(gè)函數(shù)在 X 毫秒內(nèi)執(zhí)行一次。與防抖相比,節(jié)流函數(shù)最主要的不同在于它保證在 X 毫秒內(nèi)至少執(zhí)行一次我們希望觸發(fā)的事件 handler。
與防抖相比,節(jié)流函數(shù)多了一個(gè) mustRun 屬性,代表 mustRun 毫秒內(nèi),必然會(huì)觸發(fā)一次 handler。
同樣是利用定時(shí)器,看看下面的簡(jiǎn)單示例,大概功能就是如果在一段時(shí)間內(nèi) scroll 觸發(fā)的間隔一直短于 500ms ,那么能保證事件我們希望調(diào)用的 handler 至少在 1000ms 內(nèi)會(huì)觸發(fā)一次。
// 簡(jiǎn)單的節(jié)流函數(shù)
function throttle(func, wait, mustRun) {
var timeout,
startTime = new Date();
return function() {
var context = this,
args = arguments,
curTime = new Date();
clearTimeout(timeout);
// 如果達(dá)到了規(guī)定的觸發(fā)時(shí)間間隔,觸發(fā) handler
if(curTime - startTime >= mustRun){
func.apply(context,args);
startTime = curTime;
// 沒(méi)達(dá)到觸發(fā)間隔,重新設(shè)定定時(shí)器
}else{
timeout = setTimeout(func, wait);
}
};
};
// 實(shí)際想綁定在 scroll 事件上的 handler
function realFunc(){
console.log("Success");
}
// 采用了節(jié)流函數(shù)
window.addEventListener('scroll',throttle(realFunc,500,1000));
手寫原生js實(shí)現(xiàn)事件代理,并要求兼容瀏覽器(騰訊)
/ ============ 簡(jiǎn)單的事件委托
function delegateEvent(interfaceEle, selector, type, fn) {
if(interfaceEle.addEventListener){
interfaceEle.addEventListener(type, eventfn);
}else{
interfaceEle.attachEvent("on"+type, eventfn);
}
function eventfn(e){
var e = e || window.event;
var target = e.target || e.srcElement;
if (matchSelector(target, selector)) {
if(fn) {
fn.call(target, e);
}
}
}
}
function matchSelector(ele, selector) {
// if use id
if (selector.charAt(0) === "#") {
return ele.id === selector.slice(1);
}
// if use class
if (selector.charAt(0) === ".") {
return (" " + ele.className + " ").indexOf(" " + selector.slice(1) + " ") != -1;
}
// if use tagName
return ele.tagName.toLowerCase() === selector.toLowerCase();
}
//調(diào)用
var odiv = document.getElementById("oDiv");
delegateEvent(odiv,"a","click",function(){
alert("1");
})
手寫Function.bind函數(shù)(騰訊、愛(ài)奇藝)
if(!Function.prototype.bind){
Function.prototype.bind = function(oThis){
if(typeof this !=="function"){ //如果不函數(shù)拋出異常
throw new TyperError("")
}
var aArgs = Array.prototype.slice.call(arguments,1), //此處的aArgs是除函數(shù)外的參數(shù)
fToBind = this,//要綁定的對(duì)象
fNOP = function(){},
fBound = function(){
return fToBind.apply(
this instanceof fNOP ? this:oThis||this,aArgs.concat(Array.prototype.slice.call(arguments)));
)
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
}
}
手寫AJAX(騰訊)
創(chuàng)建XMLHttpRequest對(duì)象
指定響應(yīng)函數(shù)
打開連接(指定請(qǐng)求)
發(fā)送請(qǐng)求
創(chuàng)建響應(yīng)函數(shù)
var xmlhttp=null;//聲明一個(gè)變量,用來(lái)實(shí)例化XMLHttpRequest對(duì)象
if (window.XMLHttpRequest)
{
xmlhttp=new XMLHttpRequest();// 新版本的瀏覽器可以直接創(chuàng)建XMLHttpRequest對(duì)象
}
else if (window.ActiveXObject)
{
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");// IE5或IE6沒(méi)有XMLHttpRequest對(duì)象,而是用的ActiveXObject對(duì)象
}
if (xmlhttp!=null)
{
xmlhttp.onreadystatechange=state_Change;//指定響應(yīng)函數(shù)為state_Change
xmlhttp.open("GET","/example/xdom/note.xml",true);//指定請(qǐng)求,這里要訪問(wèn)在/example/xdom路徑下的note.xml文件,true代表的使用的是異步請(qǐng)求
xmlhttp.send(null);//發(fā)送請(qǐng)求
}
else
{
alert("Your browser does not support XMLHTTP.");
}
//創(chuàng)建具體的響應(yīng)函數(shù)state_Change
function state_Change()
{
if (xmlhttp.readyState==4)
{
if (xmlhttp.status==200)
{
// 這里應(yīng)該是函數(shù)具體的邏輯
}
else
{
alert("Problem retrieving XML data");
}
}
}
手寫XMLHttpRequest
var xhr = new XMLHttpRequest();
xhr.open("GET","/api",false);
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
if(xhr.status == 200){
alert(xhr.responseText);
}
}
}
xhr.send(null);