前端面試-進階javascript篇
1.自己實現一個bind函式
原理:通過apply或者call方法來實現。
(1)初始版本
Function.prototype.bind=function(obj,arg){
var arg=Array.prototype.slice.call(arguments,1);
var context=this;
return function(newArg){
arg=arg.concat(Array.prototype.slice.call(newArg));
return context.apply(obj,arg);
}
}
複製程式碼
(2) 考慮到原型鏈
為什麼要考慮?因為在new 一個bind過生成的新函式的時候,必須的條件是要繼承原函式的原型
Function.prototype.bind=function(obj,arg){ var arg=Array.prototype.slice.call(arguments,1); var context=this; var bound=function(newArg){ arg=arg.concat(Array.prototype.slice.call(newArg)); return context.apply(obj,arg); } var F=function(){} //這裡需要一個寄生組合繼承 F.prototype=context.prototype; bound.prototype=new F(); return bound; } 複製程式碼
2.用setTimeout來實現setInterval
(1)用setTimeout()方法來模擬setInterval()與setInterval()之間的什麼區別?
首先來看setInterval的缺陷,使用setInterval()建立的定時器確保了定時器程式碼規則地插入佇列中。這個問題在於:如果定時器程式碼在程式碼再次新增到佇列之前還沒完成執行,結果就會導致定時器程式碼連續執行好幾次。而之間沒有間隔。不過幸運的是:javascript引擎足夠聰明,能夠避免這個問題。當且僅當沒有該定時器的如何程式碼例項時,才會將定時器程式碼新增到佇列中。這確保了定時器程式碼加入佇列中最小的時間間隔為指定時間。
這種重複定時器的規則有兩個問題:1.某些間隔會被跳過 2.多個定時器的程式碼執行時間可能會比預期小。
下面舉例子說明:
假設,某個onclick事件處理程式使用啦setInterval()來設定了一個200ms的重複定時器。如果事件處理程式花了300ms多一點的時間完成。
這個例子中的第一個定時器是在205ms處新增到佇列中,但是要過300ms才能執行。在405ms又添加了一個副本。在一個間隔,605ms處,第一個定時器程式碼還在執行中,而且佇列中已經有了一個定時器例項,結果是605ms的定時器程式碼不會新增到佇列中。結果是在5ms處新增的定時器程式碼執行結束後,405處的程式碼立即執行。
function say(){
//something
setTimeout(say,200);
}
setTimeout(say,200)
複製程式碼
或者
setTimeout(function(){
//do something
setTimeout(arguments.callee,200);
},200);
複製程式碼
3.js怎麼控制一次載入一張圖片,載入完後再載入下一張
(1)方法1
<script type="text/javascript">
var obj=new Image();
obj.src="http://www.phpernote.com/uploadfiles/editor/201107240502201179.jpg";
obj.onload=function(){
alert('圖片的寬度為:'+obj.width+';圖片的高度為:'+obj.height);
document.getElementById("mypic").innnerHTML="<img src='"+this.src+"' />";
}
</script>
<div id="mypic">onloading……</div>
複製程式碼
(2)方法2
<script type="text/javascript">
var obj=new Image();
obj.src="http://www.phpernote.com/uploadfiles/editor/201107240502201179.jpg";
obj.onreadystatechange=function(){
if(this.readyState=="complete"){
alert('圖片的寬度為:'+obj.width+';圖片的高度為:'+obj.height);
document.getElementById("mypic").innnerHTML="<img src='"+this.src+"' />";
}
}
</script>
<div id="mypic">onloading……</div>
複製程式碼
3.程式碼的執行順序
setTimeout(function(){console.log(1)},0);
new Promise(function(resolve,reject){
console.log(2);
resolve();
}).then(function(){console.log(3)
}).then(function(){console.log(4)});
process.nextTick(function(){console.log(5)});
console.log(6);
//輸出2,6,5,3,4,1
複製程式碼
為什麼呢?具體請參考我的文章: 從promise、process.nextTick、setTimeout出發,談談Event Loop中的Job queue
4.如何實現sleep的效果(es5或者es6)
(1)while迴圈的方式
function sleep(ms){
var start=Date.now(),expire=start+ms;
while(Date.now()<expire);
console.log('1111');
return;
}
複製程式碼
執行sleep(1000)之後,休眠了1000ms之後輸出了1111。上述迴圈的方式缺點很明顯,容易造成死迴圈。
(2)通過promise來實現
function sleep(ms){
var temple=new Promise(
(resolve)=>{
console.log(111);setTimeout(resolve,ms)
});
return temple
}
sleep(500).then(function(){
//console.log(222)
})
//先輸出了111,延遲500ms後輸出222
複製程式碼
(3)通過async封裝
function sleep(ms){
return new Promise((resolve)=>setTimeout(resolve,ms));
}
async function test(){
var temple=await sleep(1000);
console.log(1111)
return temple
}
test();
//延遲1000ms輸出了1111
複製程式碼
####(4).通過generate來實現
function* sleep(ms){
yield new Promise(function(resolve,reject){
console.log(111);
setTimeout(resolve,ms);
})
}
sleep(500).next().value.then(function(){console.log(2222)})
複製程式碼
5.簡單的實現一個promise
首先明確什麼是promiseA+規範,參考規範的地址:
如何實現一個promise,參考我的文章:
一般不會問的很詳細,只要能寫出上述文章中的v1.0版本的簡單promise即可。
6.Function._proto_(getPrototypeOf)是什麼?
獲取一個物件的原型,在chrome中可以通過__proto__的形式,或者在ES6中可以通過Object.getPrototypeOf的形式。
那麼Function.proto是什麼麼?也就是說Function由什麼物件繼承而來,我們來做如下判別。
Function.__proto__==Object.prototype //false
Function.__proto__==Function.prototype//true
複製程式碼
我們發現Function的原型也是Function。
我們用圖可以來明確這個關係:
7.實現js中所有物件的深度克隆(包裝物件,Date物件,正則物件)
通過遞迴可以簡單實現物件的深度克隆,但是這種方法不管是ES6還是ES5實現,都有同樣的缺陷,就是隻能實現特定的object的深度複製(比如陣列和函式),不能實現包裝物件Number,String , Boolean,以及Date物件,RegExp物件的複製。
(1)前文的方法
function deepClone(obj){
var newObj= obj instanceof Array?[]:{};
for(var i in obj){
newObj[i]=typeof obj[i]=='object'?
deepClone(obj[i]):obj[i];
}
return newObj;
}
複製程式碼
這種方法可以實現一般物件和陣列物件的克隆,比如:
var arr=[1,2,3];
var newArr=deepClone(arr);
// newArr->[1,2,3]
var obj={
x:1,
y:2
}
var newObj=deepClone(obj);
// newObj={x:1,y:2}
複製程式碼
但是不能實現例如包裝物件Number,String,Boolean,以及正則物件RegExp和Date物件的克隆,比如:
//Number包裝物件
var num=new Number(1);
typeof num // "object"
var newNum=deepClone(num);
//newNum -> {} 空物件
//String包裝物件
var str=new String("hello");
typeof str //"object"
var newStr=deepClone(str);
//newStr-> {0:'h',1:'e',2:'l',3:'l',4:'o'};
//Boolean包裝物件
var bol=new Boolean(true);
typeof bol //"object"
var newBol=deepClone(bol);
// newBol ->{} 空物件
....
複製程式碼
(2)valueof()函式
所有物件都有valueOf方法,valueOf方法對於:如果存在任意原始值,它就預設將物件轉換為表示它的原始值。物件是複合值,而且大多數物件無法真正表示為一個原始值,因此預設的valueOf()方法簡單地返回物件本身,而不是返回一個原始值。陣列、函式和正則表示式簡單地繼承了這個預設方法,呼叫這些型別的例項的valueOf()方法只是簡單返回這個物件本身。
對於原始值或者包裝類:
function baseClone(base){
return base.valueOf();
}
//Number
var num=new Number(1);
var newNum=baseClone(num);
//newNum->1
//String
var str=new String('hello');
var newStr=baseClone(str);
// newStr->"hello"
//Boolean
var bol=new Boolean(true);
var newBol=baseClone(bol);
//newBol-> true
複製程式碼
其實對於包裝類,完全可以用=號來進行克隆,其實沒有深度克隆一說,
這裡用valueOf實現,語法上比較符合規範。
對於Date型別:
因為valueOf方法,日期類定義的valueOf()方法會返回它的一個內部表示:1970年1月1日以來的毫秒數.因此我們可以在Date的原型上定義克隆的方法:
Date.prototype.clone=function(){
return new Date(this.valueOf());
}
var date=new Date('2010');
var newDate=date.clone();
// newDate-> Fri Jan 01 2010 08:00:00 GMT+0800
複製程式碼
對於正則物件RegExp:
RegExp.prototype.clone = function() {
var pattern = this.valueOf();
var flags = '';
flags += pattern.global ? 'g' : '';
flags += pattern.ignoreCase ? 'i' : '';
flags += pattern.multiline ? 'm' : '';
return new RegExp(pattern.source, flags);
};
var reg=new RegExp('/111/');
var newReg=reg.clone();
//newReg-> /\/111\//
複製程式碼
8.簡單實現Node的Events模組
簡介:觀察者模式或者說訂閱模式,它定義了物件間的一種一對多的關係,讓多個觀察者物件同時監聽某一個主題物件,當一個物件發生改變時,所有依賴於它的物件都將得到通知。
node中的Events模組就是通過觀察者模式來實現的:
var events=require('events');
var eventEmitter=new events.EventEmitter();
eventEmitter.on('say',function(name){
console.log('Hello',name);
})
eventEmitter.emit('say','Jony yu');
複製程式碼
這樣,eventEmitter發出say事件,通過On接收,並且輸出結果,這就是一個訂閱模式的實現,下面我們來簡單的實現一個Events模組的EventEmitter。
(1)實現簡單的Event模組的emit和on方法
function Events(){
this.on=function(eventName,callBack){
if(!this.handles){
this.handles={};
}
if(!this.handles[eventName]){
this.handles[eventName]=[];
}
this.handles[eventName].push(callBack);
}
this.emit=function(eventName,obj){
if(this.handles[eventName]){
for(var i=0;o<this.handles[eventName].length;i++){
this.handles[eventName][i](obj);
}
}
}
return this;
}
複製程式碼
這樣我們就定義了Events,現在我們可以開始來呼叫:
var events=new Events();
events.on('say',function(name){
console.log('Hello',nama)
});
events.emit('say','Jony yu');
//結果就是通過emit呼叫之後,輸出了Jony yu
複製程式碼
(2)每個物件是獨立的
因為是通過new的方式,每次生成的物件都是不相同的,因此:
var event1=new Events();
var event2=new Events();
event1.on('say',function(){
console.log('Jony event1');
});
event2.on('say',function(){
console.log('Jony event2');
})
event1.emit('say');
event2.emit('say');
//event1、event2之間的事件監聽互相不影響
//輸出結果為'Jony event1' 'Jony event2'
複製程式碼
9.箭頭函式中this指向舉例
var a=11;
function test2(){
this.a=22;
let b=()=>{console.log(this.a)}
b();
}
var x=new test2();
//輸出22
複製程式碼
定義時繫結。
連結:https://juejin.im/post/5b44a485e51d4519945fb6b7