1. 程式人生 > >前端面試-進階javascript篇

前端面試-進階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多一點的時間完成。

2018-07-10 11 36 43

這個例子中的第一個定時器是在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+規範,參考規範的地址:

primiseA+規範

如何實現一個promise,參考我的文章:

實現一個完美符合Promise/A+規範的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。

我們用圖可以來明確這個關係:

2018-07-10 2 38 27

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