1. 程式人生 > >前端常見問題-面試

前端常見問題-面試

 

出套 前端 筆試題 坑自己

作為一個前端老司機,近兩年更多的是出筆試題 考別人,突然想給 自己出一套 筆試題 坑自己,同時 也以此 分享給 需要的人。

HTML 部分

1 使用一個div模擬textarea的實現? div元素上加個contenteditable="true"屬性。這是H5新增的內容可編輯屬性,允許使用者編輯元素內容包含的任意文字,包括子元素。應用了此屬性後,普通的div標籤也會像文字域一樣可以獲得焦點,同時有一個游標在那裡。

2 請談談 vue 中使用的 template 元素?

  • template 元素 是一種用於儲存客戶端內容的機制,該內容在頁面載入時不-被渲染,但可以在執行時使用JavaScript進行例項化。

  • 在html中如果有很多重複的結構,就可以把重複部分寫在這個標籤內部供整個文件呼叫。2013年定稿的template標籤為我們提供一種更統一、功能更強大的模板文字存放方式。

CSS 部分

3 如何做好一個元素的居中?

  • 方法一 div.box{width:300px;height:600px;position:absolute;// 把元素變成定位元素 // 設定元素的定位位置,距離上、左都為50% left:50%; top:50%; // 設定元素的左外邊距、上外邊距為寬高的負1/2 margin-left:-150px; margin-top:-300px; }</li> <li>相容性好; 但必須知道元素的寬高

  • 方法二 div.box{position:absolute;// 把元素變成定位元素 // 設定元素的定位位置,距離上、下、左、右都為0 left:0; right:0; top:0; bottom:0; // 設定元素的margin樣式值為 auto margin:auto; }</li> <li>相容性較好,但不支援IE7以下的瀏覽器

  • 方法三 div.box{position:absolute;// 把元素變成定位元素 // 設定元素的定位位置,距離上、左都為50% left:50%; top:50%; // 設定元素的相對於自身的偏移度為負50% transform:translate(-50%,-50%); }</li> <li>transform是css3裡的樣式;相容性不好,只支援IE9+的瀏覽器

  • 方法四 div.box{display:flex;justify-content:center;align-items:center;}</li><li>flex佈局是css3裡才有的;相容性不好,只支援IE9+的瀏覽器

4 請列舉幾種隱藏元素的方法?

  • visibility: hidden; 這個屬性只是簡單的隱藏某個元素,但是元素佔用的空間任然存在

  • opacity: 0; CSS3屬性,設定0可以使一個元素完全透明

  • position: absolute; 設定一個很大的 left 負值定位,使元素定位在可見區域之外

  • display: none; 元素會變得不可見,並且不會再佔用文件的空間。

  • transform: scale(0); 將一個元素設定為縮放無限小,元素將不可見,元素原來所在的位置將被保留;

  • div 中 hidden="hidden" HTML5屬性,效果和display:none;相同,但這個屬性用於記錄一個元素的狀態;

  • height: 0; 將元素高度設為 0 ,並消除邊框;

  • filter: blur(0); CSS3屬性,將一個元素的模糊度設定為0,從而使這個元素“消失”在頁面中。

JS 部分

5 2 + 2 + '2' 和 '2' + 2 + 2的結果? '42'和'222'

6 var const let 的區別?

  • 初始值:const 宣告的變數必須設定初始值,且不能重複賦值。

  • 重複定義:const 和 let 不支援重複定義

  • const,let 支援塊級作用域,有效避免變數覆蓋

  • 變數提升:const 和 let 必須先宣告再使用,不支援變數提升

  • 在ES6之前,js的作用域只有兩種:函式作用域和全域性作用域。使用var宣告的變數,都存在變數提升的過程。

7 函式宣告與函式表示式的區別?

  • 在Javscript中,解析器在向執行環境中載入資料時,對函式宣告和函式表示式並非是一視同仁的,解析器會率先讀取函式宣告,並使其在執行任何程式碼之前可用(可以訪問);

  • 至於函式表示式,則必須等到解析器執行到它所在的程式碼行,才會真正被解析執行。

    // 函式宣告 function fun(){}

    // 函式表示式 var fun = function(){}

8 如何深拷貝物件?

 // 序列化反序列化法
 function  deepClone(obj){
   returnJSON.parse(JSON.stringify(obj))
 }

// 深拷貝物件與陣列
function  deepClone(obj){
 if(!isObject(obj)){
   throw new Error('obj 不是一個物件!')
 }

 let isArray=Array.isArray(obj)
 let cloneObj=isArray?[]:{}
 for(let key in obj){
   cloneObj[key]=isObject(obj[key])?deepClone(obj[key]):obj[key]
 }
 return  cloneObj
}

 

  • 迭代遞迴法

  • 序列化反序列化法

9 程式碼輸出結果及this幾種不同的使用場景?

var x=1;
var o={
 x:2,
 getX:function(){
   return this.x;
 }
}

console.log(o.getX());// 2
console.log(o.getX.call());// 1
console.log(o.getX.call({x:10}));// 10

 

  • 在建構函式中使用(建構函式本身)

  • 作為物件屬性時使用(呼叫屬性的物件)

  • 作為普通函式時使用(window)

  • call,apply,bind(執行的第一個引數)

10 模擬實現bind?

Function.prototype.bind2 = function (context) {
   if (typeof this !== "function") {
     throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
   }
   var self = this;
   var args = Array.prototype.slice.call(arguments, 1);
   var fNOP = function () {};
   var fbound = function () {
       self.apply(this instanceof self ? this : context, args.concat(Array.prototype.slice.call(arguments)));
   }
   fNOP.prototype = this.prototype;
   fbound.prototype = new fNOP();
   return fbound;
}

 

11 程式碼輸出結果及原因?

var x = 1;
function test() {
  var x = 2;
  return () => {
    x += 1;
  }
}

var t = test();
t();
console.log(x); // 1 閉包形成區域性變數 此時X為全域性變數

//修改test()內部x輸出
var x = 1;
function test() {
  var x = 2;
  return () => {
     return x += 1;
  }
}

var t = test();
console.log(t())//3

...................................................

var x = 1;
if(!(y in window)) {
 var y = 2;
 x += 1;
} else {
 x += 2;
}
console.log(x); // 3
console.log(y); // undefined

12 程式碼輸出結果及原因?


function Foo(){
    getName=function (){console.log(1)}
    return this;
  }

  Foo.getName=function (){
    console.log(2)
  }
  Foo.prototype.getName=function () {
    console.log(3)
  }
  var getName =function () {
      console.log(4)
    }
  function getName() {
    console.log(5)
  }
  Foo.getName(); // 2  當前函式>原型鏈
  getName(); // 4  函式表示式>函式宣告
  Foo().getName(); // 1  return this
  getName(); // 1  命名覆蓋
  new Foo.getName(); // 2  建構函式
  new Foo().getName(); // 3 原型鏈 
  new new Foo().getName(); // 3  原型鏈

13 程式碼輸出結果及原因?

  • 只要你對 JS 中同步和非同步程式碼的區別、變數作用域、閉包等概念有正確的理解,就知道正確答案是5,5,5,5,5,5;

  • 若用->表示其前後的兩次輸出之間有 1 秒,而逗號表示間隔可以忽略,結果是? 迴圈執行過程中,幾乎同時設定了 5 個定時器,一般情況下,這些定時器都會在 1 秒之後觸發,而迴圈完的輸出是立即執行的, 所以結果是 5 -> 5,5,5,5,5;

  • 如果期望程式碼的輸出變成:5 -> 0,1,2,3,4,該怎麼改造程式碼? 巧妙的利用 IIFE(宣告即執行的函式表示式)來解決閉包造成的問題, 相當於把i賦值給了j這個區域性變數: for(vari=0;i<5;i++){(function(j){setTimeout(function(){console.log(j);},1000);})(i);}console.log(i);

  • 有沒有更符合直覺的做法? 利用 JS 中基本型別的引數傳遞是按值傳遞的特徵, 對迴圈體稍做手腳,讓負責輸出的那段程式碼能拿到每次迴圈的 i 值即可: varprint=function(i){setTimeout(function(){console.log(i);},1000);};for(vari=0;i<5;i++){print(i);// 傳遞到 print 的 i 值被複制了}console.log(i);

  • 如果期望程式碼的輸出變成 0 -> 1 -> 2 -> 3 -> 4 -> 5,並且要求原有的程式碼塊中的迴圈和兩處 console.log 不變,該怎麼改造程式碼?

  • 有沒有基於ES6 Promise 的解決方案 ?

  • 把上面 i 的宣告改成 let,怎麼做?

  • 有沒有程式碼層次更好的做法?

  • 如何使用 ES7 中的 async await 特性來讓這段程式碼變的更簡潔?

  • 還能優化嗎?

const sleep=(t)=>newPromise((resolve)=>{
 setTimeout(resolve,t);
});

const print=async()=>{
 for(leti=0;i<=5;i++){
   await sleep(1000);
   console.log(i);
 }
};


print();
const sleep=(t)=>newPromise((resolve)=>{
 setTimeout(resolve,t);
});

(async()=>{
 for(vari=0;i<5;i++){
   await sleep(1000);
   console.log(i);
 }
 await sleep(1000);
 console.log(i);
})();

const tasks=[];// 存放非同步操作的 Promise
const print=(i)=>newPromise((resolve)=>{
setTimeout(()=>{
 console.log(i);
 resolve();
},1000*i);
});


// 獲得全部的非同步操作
for(vari=0;i<5;i++){
tasks.push(print(i));
}

// 輸出最後的 i
Promise.all(tasks).then(()=>{
setTimeout(()=>{
 console.log(i);
},1000);
});
const tasks=[];
for(leti=0;i<5;i++){
tasks.push(newPromise((resolve)=>{
 setTimeout(()=>{
   console.log(j);
   resolve();// 一定要 resolve
 },1000*j);
}));
}

Promise.all(tasks).then(()=>{
setTimeout(()=>{
 console.log(i);
},1000);// 只需把超時設定為 1 秒
});
const tasks=[];
for(vari=0;i<5;i++){
((j)=>{
 tasks.push(newPromise((resolve)=>{
 setTimeout(()=>{
   console.log(j);
   resolve();// 一定要 resolve
 },1000*j);
 }));
})(i);
}

Promise.all(tasks).then(()=>{
setTimeout(()=>{
 console.log(i);
},1000);// 只需把超時設定為 1 秒
});
for(vari=0;i<5;i++){
(function(j){
 setTimeout(function(){
 console.log(j);
 },1000*j));// 這裡修改 0~4 的定時器時間
})(i);
}

setTimeout(function(){// 這裡增加定時器,超時設定為 5 秒
console.log(i);
},1000*i);
for(vari=0;i<5;i++){
  setTimeout(function(){
    console.log(i);
  },1000);
}

console.log(i);

瀏覽器 部分

14 移動端 點選事件 300ms延遲如何去掉,原因是什麼? 雙擊縮放是 300 毫秒延遲的主要原因:在移動端觸發時間會按照 touchstart,touchmove,touchend,click 順序觸發;觸發touchend,click之間會有200-400不等的時間延時(因為移動端需要判斷使用者是不是想要進行雙擊);

  • fastclick 和 zepto 的tap 事件 都可以解決 300 ms延時;原理:是在檢測到touchend事件的時候,會通過DOM自定義事件立即出發模擬一個click事件,並把瀏覽器在300ms之後的click事件阻止掉。

  • tap 原理:在touchstart 時會記錄一個值x1,y1,在touchend時會記錄x2,y2,通過對比著幾個值,判斷使用者是否是點選事件,而不是滑動事件,然後直接觸發事件;

15 瀏覽器渲染主流程 瀏覽器獲取到html程式碼後,核心會做以下工作:

  • 構建DOM樹(Parse html)

  • 構建CSSOM樹(Recaculate Style)

  • 合併DOM樹與CSSOM樹為Render樹

  • 佈局(Layout)

  • 繪製(Paint)

  • 複合圖層化(Composite)

16 cookies,sessionStorage和localStorage的區別?

  • 共同點:都是儲存在瀏覽器端,且是同源的。

  • 區別:

  • cookies是為了標識使用者身份而儲存在使用者本地終端上的資料,始終在同源http請求中攜帶,即cookies在瀏覽器和伺服器間來回傳遞,而sessionstorage和localstorage不會自動把資料發給伺服器,僅在本地儲存。

  • 儲存大小的限制不同。cookie儲存的資料很小,不能超過4k,而sessionstorage和localstorage儲存的資料大,可達到5M。

  • 資料的有效期不同。cookie在設定的cookie過期時間之前一直有效,即使視窗或者瀏覽器關閉。sessionstorage僅在瀏覽器視窗關閉之前有效。localstorage始終有效,視窗和瀏覽器關閉也一直儲存,用作長久資料儲存。

  • 作用域不同。cookie在所有的同源視窗都是共享;sessionstorage不在不同的瀏覽器共享,即使同一頁面;localstorage在所有同源視窗都是共享。

主流框架使用

17 react 元件間是如何通訊的?vue呢?

  • 父元件向子元件通訊:使用 props

  • 子元件向父元件通訊:使用 props 回撥

  • 跨級元件間通訊:使用 context 物件

  • 非巢狀元件間通訊:使用事件訂閱

vue父子元件間傳值: 父元件通過標籤上面定義傳值, 子元件通過props方法接受資料;子元件通過$emit方法傳遞引數向父元件傳遞資料。

18 react 生命週期? vue呢

  • 首次例項化時:

  • 客戶端 1、getDefaultProps2、getInitialState3、componentWillMount4、render5、componentDidMount

  • 服務端渲染: 1、getDefaultProps2、getInitialState3、componentWillMount4、render//componentDidMount 不會在服務端被渲染的過程中呼叫。

  • 互動時: 1、componentWillReceiveProps2、shouldComponentUpdate3、componentWillUpdate4、render5、componentDidUpdate Vue生命週期鉤子函式:

  • vue 例項從建立到銷燬的過程,就是生命週期;

  • 建立前/後: 在beforeCreate階段,vue例項的掛載元素el和資料物件data都為undefined,還未初始化。在created階段,vue例項的資料物件data有了,el還沒有;

  • 載入前/後:在beforeMount階段,vue例項的$el和data都初始化了,但還是掛載之前為虛擬的dom節點,data.message還未替換。在mounted階段,vue例項掛載完成,data.message成功渲染;

  • 更新前/後:當data變化時,會觸發beforeUpdate和updated方法;

  • 銷燬前/後:在執行destroy方法後,對data的改變不會再觸發周期函式,說明此時vue例項已經解除了事件監聽以及和dom的繫結,但是dom結構依然存在;

  • vue生命週期中的事件鉤子,讓我們在控制整個Vue例項的過程時更容易形成好的邏輯。

演算法

19 去掉一組整型陣列中重複的值

let unique =  function(arr){
 let hash={};
 let data=[];
 for (let i=0;i < arr.length; i++){
   if (!hash[arr[i]])  {
     hash[arr[i]] = true;
     data.push(arr[i]);
   }      
 }
 return data
}

20 合併兩個有序連結串列

function ListNode(val) {
  this.val = val;
  this.next = null;
}
// 方法1
var mergeTwoLists = function(l1, l2) {
   var head=new ListNode(0);
   var curr=head;
   var p=l1,q=l2;
   while(p!=null&&q!=null){
       if(p.val<q.val){
           curr.next=new ListNode(p.val)
           curr=curr.next;
           p=p.next;
       } else if (p.val==q.val){
           curr.next=new ListNode(p.val);
           curr=curr.next;
           curr.next=new ListNode(p.val);
           curr=curr.next
           p=p.next;
           q=q.next;
       } else {
           curr.next=new ListNode(q.val);
           curr=curr.next;
           q=q.next;
       }
   }
   if(p!==null) {
       curr.next=p;
   }
   if(q!==null) {
       curr.next=q;
   }
   return head.next;
};



// 方法2 將l2合併到l1

// 外層迴圈控制遍歷第二條連結串列,內層迴圈負責插入新節點,所以是O(m*n)的演算法。
var mergeTwoLists = function(l1, l2) {
   while(l2){
       var prev = null;
       var cur = l1;
       while(cur && l2.val > cur.val){
           prev = cur;
           cur = cur.next;
       }
       var newNode = new ListNode(l2.val);
       newNode.next = cur;
       if(prev){
           prev.next = newNode;
       }else{
           l1 = newNode;
       }
       l2 = l2.next;
   }
   return l1;
};

原文出處