百家雲面試總結
- 筆試的兩道演算法題
//輸入形如aaaabbbbcc,輸出4a4b2c
function char_count(str) {
let count = 0 ,i = 0,j = 0, str1 = '' ,a = '';
while(i<str.length){
a = str.charAt[i];
if(str.charAt(i)=== str.charAt(j)){
j++;
count++;
continue;
}
else {
str1+=count+str.charAt[i];
i=j;
}
count = 0;
}
return str1
}
console.log(char_count("aaaabbbccccjjjj"))
//輸入一個陣列,第一個是陣列長度,對後面的數進行排序,輸出排序後的
function arraySort(arr) {
for(let i = 1; i<arr.length; i++){
let minIndex = i;
for(let j = i + 1; j<arr.length; j++){
if(arr[minIndex]>arr[j]){
minIndex = j;
}
}
swap(arr, i, minIndex);
}
arr.splice(0,1);
return arr;
}
function swap(arr,i,j){
let temp;
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
var arr = [5,1,3,4,7,2,9,10]
console.log(arraySort(arr));
- es6中新增了哪些語法
let //變數宣告
1.變數不能重複宣告
2.塊級作用域
3.不存在變數提升
4.不影響作用域鏈
const //定義常量
1.一定要賦初始值
2.一般常量用大寫
3.常量的值不能修改
4.塊級作用域
5.對於陣列和物件的元素修改,不算做對常量的修改
` ` //模板字串
1.在裡面可以換行
2.拼接的時候用 ${}
=>() //箭頭函式
1.this是靜態的,this始終只想行數生命時所在作用域下的this的值
2.不能作為構造例項化物件
3.不能使用arguments變數
4.箭頭函式的簡寫
1).省略小括號,當形參只有一個的時候
2).省略花括號,當程式碼只有一條語句的時候,此時return必須省略,而且語句的執行結果就是函式的返回值
5.箭頭函式適合於this無關的回撥,定時器,陣列的方法回撥
6.箭頭函式不適合與this,事件回撥,物件的方法
rest引數
1.ES6引入rest引數,用於獲取函式的實參,用來代替arguments
2.用法: function date(...args){
console.log(args);
}
date("xiaohong","xiaobai","小明")
3.rest引數必須要放到引數最後
... //擴充套件運算子能將陣列轉換為逗號分割的引數序列
1.用法: const tfboys = ["易烊千璽","王源","王俊凱"];
function chunwan() {
console.log(arguments);
}
chunwan(...tfboys);
Symbol //資料型別。類似於字串
1.symbol的值時唯一的,用來解決命名衝突的問題
2.symbol的值不能與其他資料進行運算
3.symbol定義的物件不能使用for ...in 迴圈遍歷,但是可以使用Reflect.ownKeys來獲取物件的所有鍵名
4.用法:let a = Symbol("小橋流水人家")
七種資料型別:USONB
U:undefined
S: string , symbol
O: object
N:null , number
B: boolean
for(in) 儲存的是鍵值, for(of)儲存的是value
生成器 //函式,用來進行非同步程式設計
1.用法:function *gen(){
console.log("hello generator")
}
let iterator = gen()
iterator.next();
Promise //建構函式,用來封裝非同步操作並獲取其成功或失敗的結果
1.用法 https://www.cnblogs.com/whybxy/p/7645578.html
set //集合
1.用法: let s = new Set();
s.size
s.add()
s.delete()
s.has() //是否有某個元素 返回true
s.clear()
Map
1.用法:size
set //增加一個新元素,返回當前Map
get //返回鍵名物件的鍵值
has //檢測Map中是否包含某個元素,返回boolean值
clear //清空集合,返回undefined
class //類
1.靜態成員不在例項化物件裡,例項化的物件只能訪問它的prototype中的屬性
- let,var,const的區別
let宣告是塊級作用域,只在那個程式碼塊裡起作用,沒有變數提升,不影響作用域鏈
var在宣告的全域性起作用,有變數提升
const要賦初始值,常量的值不能修改
- symbol的作用
symbol的值時唯一的,用來解決命名衝突的問題
- promise是為了解決什麼問題,回撥地獄是怎麼產生的
用來解決回撥地獄的問題,避免程式碼巢狀臃腫,維護性差,錯誤處理也不能統一的問題
- html5中有哪些語法,低版本的瀏覽器怎麼相容這些語法
<canvas>
<video>和<audio>
header,nav,article,section,aside,footer
(適當回答一些就好)
移除的元素:純表現的元素:<basefont> <big> <center> <font> <s> <strike> <tt> <u>
- css3新增了哪些語法
border-radius
box-shadow
border-image
background-size
text-shadow
- cookie,localstorage,sessionstorage的區別
sessionStorage,localStorage和Cookies都用於在客戶端儲存資料。每個都有自己的儲存和到期限制。
localStorage:
可以簡單地將localStorage視為對cookie的改進,從而提供更大的儲存容量。可用大小為5MB,與典型的4KB cookie相比,可以使用的空間大得多。
對於每個HTTP請求(HTML,影像,JavaScript,CSS等),資料不會傳送回伺服器 - 減少了客戶端和伺服器之間的流量。
儲存在localStorage中的資料將一直存在,直到被明確刪除。所做的更改將儲存並可用於該網站的所有當前和未來訪問。
它適用於同源策略。因此,儲存的資料只能在同一個來源上使用。
儲存沒有過期日期的資料,只能通過JavaScript清除,或清除瀏覽器快取/本地儲存的資料。
cookie:
我們可以設定每個cookie的到期時間;
4K限制適用於整個cookie,包括名稱,值,到期日期等。要支援大多數瀏覽器,請將名稱保留在4000位元組以下,並將整體cookie大小保持在4093位元組以下。
每次HTTP請求(HTML,影像,JavaScript,CSS等)都會將資料傳送回伺服器 - 增加客戶端和伺服器之間的流量。
儲存必須通過後續請求傳送回伺服器的資料。其到期時間根據型別而有所不同,到期時間可以從伺服器端或客戶端(通常從伺服器端)設定。
Cookie主要用於伺服器端讀取(也可以在客戶端讀取),localStorage和sessionStorage只能在客戶端讀取。
sessionStorage:
它類似於localStorage。
更改僅適用於每個視窗(或Chrome和Firefox等瀏覽器中的標籤)。所做的更改將儲存並可用於當前頁面,以及將來在同一視窗中訪問該站點。視窗關閉後,將刪除儲存
資料僅在設定它的視窗/選項卡中可用。
資料不是持久的,即視窗/標籤關閉後它將丟失。與localStorage一樣,它適用於同源策略。因此,儲存的資料只能在同一個來源上使用。
類似於localStorage,但在瀏覽器關閉時(不是選項卡)到期。
- 閉包是什麼
閉包就是能夠讀取其他函式內部變數的函式,閉包是由函式以及宣告該函式的詞法環境組合而成,該環境包含了這個閉包建立時作用域內的任何變數
閉包的主要應用場景:
1.匿名自執行函式
2.快取
3.封裝
4.實現類和繼承
-
怎麼實現非同步載入
-
深拷貝和淺拷貝的區別
淺拷貝只拷貝引用,引用的值變,拷貝後的值也變
深拷貝碰見物件會重新建立一個物件,引用的值變,拷貝後的值不變
// 淺拷貝的實現;
function shallowCopy(object) {
// 只拷貝物件
if (!object || typeof object !== "object") return;
// 根據 object 的型別判斷是新建一個陣列還是物件
let newObject = Array.isArray(object) ? [] : {};
// 遍歷 object,並且判斷是 object 的屬性才拷貝
for (let key in object) {
if (object.hasOwnProperty(key)) {
newObject[key] = object[key];
}
}
return newObject;
}
// 深拷貝的實現;
function deepCopy(object) {
if (!object || typeof object !== "object") return;
let newObject = Array.isArray(object) ? [] : {};
for (let key in object) {
if (object.hasOwnProperty(key)) {
newObject[key] =
typeof object[key] === "object" ? deepCopy(object[key]) : object[key];
}
}
return newObject;
}
- 你瞭解瀏覽器的相容性嗎,怎麼解決這些相容性
常見的相容性問題及解決方法 - css3的動畫用到了哪幾個元素,區別是什麼
animation:配合@keyframes使用,有兩幀。起始和結束
transition:需要事件觸發,可以是一幀一幀的
transform :變形,可以改變影像的角度啥的
- post和get的區別
Post 和 Get 是 HTTP 請求的兩種方法。
(1)從應用場景上來說,GET 請求是一個冪等的請求,一般 Get 請求用於對伺服器資源不會產生影響的場景,比如說請求一個網
頁。而 Post 不是一個冪等的請求,一般用於對伺服器資源會產生影響的情景。比如註冊使用者這一類的操作。
(2)因為不同的應用場景,所以瀏覽器一般會對 Get 請求快取,但很少對 Post 請求快取。
(3)從傳送的報文格式來說,Get 請求的報文中實體部分為空,Post 請求的報文中實體部分一般為向伺服器傳送的資料。
(4)但是 Get 請求也可以將請求的引數放入 url 中向伺服器傳送,這樣的做法相對於 Post 請求來說,一個方面是不太安全,
因為請求的 url 會被保留在歷史記錄中。並且瀏覽器由於對 url 有一個長度上的限制,所以會影響 get 請求傳送資料時
的長度。這個限制是瀏覽器規定的,並不是 RFC 規定的。還有就是 post 的引數傳遞支援更多的資料型別。
- http請求的頭部的一些狀態引數知道嗎
1** 資訊,伺服器收到請求,需要請求者繼續執行操作.
2** 成功,操作被成功接收並處理。
3** 重定向,需要進一步的操作以完成請求。
4** 客戶端錯誤,請求包含語法錯誤或無法完成請求。
5** 伺服器錯誤,伺服器在處理請求的過程中發生了錯誤。
http請求頭、請求狀態碼、http響應頭詳解
15. 知道flex佈局嗎,你用過哪些它的屬性
阮一峰:flex佈局例項教程
- tcp和udp的區別
1) TCP提供面向連線的傳輸,通訊前要先建立連線(三次握手機制); UDP提供無連線的傳輸,通訊前不需要建立連線。
2) TCP提供可靠的傳輸(有序,無差錯,不丟失,不重複); UDP提供不可靠的傳輸。
3) TCP面向位元組流的傳輸,因此它能將資訊分割成組,並在接收端將其重組; UDP是面向資料包的傳輸,沒有分組開銷。
4) TCP提供擁塞控制和流量控制機制; UDP不提供擁塞控制和流量控制機制。
- tcp為什麼是三次握手四次揮手
三次握手:握手過程中使用了 TCP 的標誌(flag) —— SYN(synchronize) 和ACK(acknowledgement) 。
第一次握手:建立連線時,客戶端A傳送SYN包(SYN=j)到伺服器B,並進入SYN_SEND狀態,等待伺服器B確認。
第二次握手:伺服器B收到SYN包,必須確認客戶A的SYN(ACK=j+1),同時自己也傳送一個SYN包(SYN=k),即SYN+ACK包,此時伺服器B進入SYN_RECV狀態。
第三次握手:客戶端A收到伺服器B的SYN+ACK包,向伺服器B傳送確認包ACK(ACK=k+1),此包傳送完畢,完成三次握手
若在握手過程中某個階段莫名中斷, TCP 協議會再次以相同的順序傳送相同的資料包。
(2)四次揮手:由於TCP連線是全雙工的,因此每個方向都必須單獨進行關閉。這個原則是當一方完成它的資料傳送任務後就能傳送一個FIN來終止這個方向的連線。收到一個 FIN只意味著這一方向上沒有資料流動,一個TCP連線在收到一個FIN後仍能傳送資料。先進行關閉的一方將執行主動關閉,而另一方被動關閉。
客戶端A傳送一個FIN,用來關閉客戶A到伺服器B的資料傳送。
伺服器B收到這個FIN,它發回一個ACK,確認序號為收到的序號加1。
伺服器B關閉與客戶端A的連線,傳送一個FIN給客戶端A。
客戶端A發回ACK報文確認,並將確認序號設定為收到序號加1。
- url輸入後經歷了什麼
1,在應用層輸入url
使用者輸入https://mp.csdn.net時,其中http表示採用http協議進行傳輸,mp.csdn.net為網路地址,表示請求的資源在那個位置(主機)。一般網路地址為IP地址,此處為域名,是IP地址的包裝。為了讓使用者方便使用。
2.應用層DNS解析域名,獲得對端IP地址
計算機在通訊時是通過IP地址辨識,而不是域名。
域名查詢順序:本地快取->系統快取->瀏覽器快取->ISP快取->根域名伺服器->主域名伺服器
如果本地快取有就直接使用,並不是每一個過程都需要走。沒有才繼續往下走。直到獲得IP地址。
3.應用層客戶端傳送HTTP請求
HTTP請求包括請求報頭和請求主體。其中請求報頭中包含了請求方法,請求資源,請求所使用放入協議(http,smtp等),以及返回的資源是否需要快取,客戶端是否需要傳送cookie等。
4.傳輸層TCP傳輸報文
位於傳輸層的TCP協議提供可靠的位元組流服務,他為了方便傳輸,將大塊的資料分割成以報文段為基本單位的資料包進行管理。並未他們編號。方便接收端收到報文後進行組裝,還原報頭資訊。
為了保證可靠性傳輸,TCP採用三次握手來保證可靠性傳輸。
5.網路層IP協議查詢MAC地址
IP協議把TCP分割的資料包傳送給接收方。而要保證卻是能夠傳送給對方主機還需要MAC地址,也就是實體地址。IP地址和MAC地址是一一對應關係。一個裝置有且只有一個MAC地址。IP地址可以更換,MAC不會變。ARP協議就是講IP協議轉換成MAC地址的協議,利用ARP協議,找到MAC地址,當通訊的雙方不在同一個區域網時,還需要多次中轉,才能到達目標,在中轉時,通過下一個MAC地址來搜尋下一個中轉目標。
6.資料到達鏈路層
找到對方的MAC地址後,就將資料包放到鏈路層進行傳輸,封裝上鍊路層特有的報頭,然後交付給物理層,物理層通過實際的電路如雙絞線進行傳輸。
走到資料鏈路層,客戶端的請求傳送階段完畢。
7.伺服器接受請求
服務端主機的網路卡接收到資料後,驅動作業系統拿到資料,自下而上進行解包,資料包到鏈路層,就解析客戶端在鏈路層封裝的報頭資訊,提取報頭資訊內容,如目標MAC地址,IP地址等。
到達網路層,提取IP協議報頭資訊,到達傳輸層,解析傳輸層協議報頭,
到達應用層,解析HTTP報頭資訊,獲得客戶端請求的資源和方法。查詢到資源後。將資源返回給客戶端,並返回響應報文。
響應報文中包括協議名稱/協議版本,狀態碼,狀態碼描述等資訊。其中常見狀態碼:200 表示請求資源成功。301:永久重定向,表示資源已經永久性重定向到指定位置。
8.請求成功返回相應資源
請求成功後,伺服器會返回相應的HTMML檔案,該檔案的傳輸方式又會從應用層出發,自上問下傳送,到達對端時,自下而上進行解析。
二:頁面渲染
現代瀏覽器渲染頁面的過程是這樣的:jiexiHTML以構建DOM樹 –> 構建渲染樹 –> 佈局渲染樹 –> 繪製渲染樹。
DOM樹是由HTML檔案中的標籤排列組成,渲染樹是在DOM樹中加入CSS或HTML中的style樣式而形成。渲染樹只包含需要顯示在頁面中的DOM元素,像元素或display屬性值為none的元素都不在渲染樹中。
在瀏覽器還沒接收到完整的HTML檔案時,它就開始渲染頁面了,在遇到外部鏈入的指令碼標籤或樣式標籤或圖片時,會再次傳送HTTP請求重複上述的步驟。在收到CSS檔案後會對已經渲染的頁面重新渲染,加入它們應有的樣式,圖片檔案載入完立刻顯示在相應位置。在這一過程中可能會觸發頁面的重繪
- js的垃圾回收機制是什麼
v8 的垃圾回收機制基於分代回收機制,這個機制又基於世代假說,這個假說有兩個特點,一是新生的物件容易早死,另一個是不死的物件會活得更久。基於這個假說,v8 引擎將記憶體分為了新生代和老生代。
新建立的物件或者只經歷過一次的垃圾回收的物件被稱為新生代。經歷過多次垃圾回收的物件被稱為老生代。
新生代被分為 From 和 To 兩個空間,To 一般是閒置的。當 From 空間滿了的時候會執行 Scavenge 演算法進行垃圾回收。當我們執行垃圾回收演算法的時候應用邏輯將會停止,等垃圾回收結束後再繼續執行。這個演算法分為三步:
(1)首先檢查 From 空間的存活物件,如果物件存活則判斷物件是否滿足晉升到老生代的條件,如果滿足條件則晉升到老生代。如果不滿足條件則移動 To 空間。
(2)如果物件不存活,則釋放物件的空間。
(3)最後將 From 空間和 To 空間角色進行交換。
新生代物件晉升到老生代有兩個條件:
(1)第一個是判斷是物件否已經經過一次 Scavenge 回收。若經歷過,則將物件從 From 空間複製到老生代中;若沒有經歷,則複製到 To 空間。
(2)第二個是 To 空間的記憶體使用佔比是否超過限制。當物件從 From 空間複製到 To 空間時,若 To 空間使用超過 25%,則物件直接晉升到老生代中。設定 25% 的原因主要是因為演算法結束後,兩個空間結束後會交換位置,如果 To 空間的記憶體太小,會影響後續的記憶體分配。
老生代採用了標記清除法和標記壓縮法。標記清除法首先會對記憶體中存活的物件進行標記,標記結束後清除掉那些沒有標記的物件。由於標記清除後會造成很多的記憶體碎片,不便於後面的記憶體分配。所以瞭解決記憶體碎片的問題引入了標記壓縮法。
由於在進行垃圾回收的時候會暫停應用的邏輯,對於新生代方法由於記憶體小,每次停頓的時間不會太長,但對於老生代來說每次垃圾回收的時間長,停頓會造成很大的影響。 為了解決這個問題 V8 引入了增量標記的方法,將一次停頓進行的過程分為了多步,每次執行完一小步就讓執行邏輯執行一會,就這樣交替執行。
- 用過哪些ui元件
1.elementui
2.iview
- 怎麼封裝元件
- 元件怎麼引用的
- vue有什麼好處
【1】只專注於檢視層的輕量級的框架
【2】資料的雙向繫結 優點是減少了dom操作
【3】元件化 和 響應式設計
【4】實現資料與結構的分離 高效輕便 易於瀏覽器的載入速度
請採納
- vue-cli和vue的關係
俗稱 vue-cli 為腳手架,是一套大眾化的前端自動化解決方案,他的核心是
webpack ,框架是vue,還有相關輔助外掛組成。不怕浪費時間自己也可以按
照自己的習慣搭一套,如果你有豐富的前端經驗,可能構建一條合理的解決方
案,不然會疏忽很多細節。grunt、gulp 時代,前端自動化規則,輸入輸出檔案格
式,檔案的監聽,檔案的路徑都是自己配置的。配置一套理想的方案也是相當耗
費時間,所以 vue 就提供了一套面相大眾的解決方案——vue-cli,他的輸入輸出
規則,檔案的打包路徑,模組的命名基本上符合大眾前端開發者的習慣,不過實
際開發中需要一定的調整。
- vue元件間的通訊方式是什麼(重要,必問)
1.父元件向子元件傳值 props
2.子元件向父元件傳值的方法 事件方式
3.兄弟元件傳值的方法 $emit, $on
- 雙向資料繫結是怎麼實現的(重要,幾乎必問)
```
vue.js 則是採用資料劫持結合釋出者-訂閱者模式的方式,通過Object.defineProperty()來劫持各個屬性的setter,getter,
在資料變動時釋出訊息給訂閱者,觸發相應的監聽回撥。我們先來看Object.defineProperty()這個方法:
var obj = {};
Object.defineProperty(obj, 'name', {
get: function() {
console.log('我被獲取了')
return val;
},
set: function (newVal) {
console.log('我被設定了')
}
})
obj.name = 'fei';//在給obj設定name屬性的時候,觸發了set這個方法
var val = obj.name;//在得到obj的name屬性,會觸發get方法
```
- 函式節流和函式防抖的區別
//函式防抖---將若干函式呼叫合成為一次,並在給定時間過去之後,或者連續事件完全觸發完成之後,呼叫一次(僅僅只會呼叫一次!!!!!!!!!!)。
function debounce(fn, await){
let timer; //let timer只能在setTimeout的父級作用域中,這樣才是同一個timer
return function() {
if(timer){
clearTimeout(timer);
}
/*
setTimeout會在n秒後執行,如果過了n秒,timer被點選了就清除timer重新計時,
否則過了n秒它還是沒被點選就執行傳過來的函式
*/
timer = setTimeout(() => {
fn
},await)
}
}
//函式節流-----當達到了一定的時間間隔就會執行一次;可以理解為是縮減執行頻率
function throttle(fn, await){
let timer;
return function() {
let args = arguments;
if(timer){//如果timer為真,return出去
return;
}
timer = setTimeout(() => {
fn.apply(this, args);//在n秒後fn回撥函式執行,timer置空
timer = null;
},await)
}
}
- 物件導向是什麼,和結構化有什麼區別
物件導向就是把問題抽象成一個一個物件
物件導向的三大特性:繼承封裝多型
結構化就是把一個問題一步一步的解決
- git的基本語法
- vue的常見一些指令
v-model
v-for
v-if
v-text
v-on
v-bind
...(還有挺多,不過回答了常用的就可以·了·)
- vue的生命週期(超級重要,每次面試恨不得都問·)
8個鉤子函式(很重有,每一步做什麼的·要知道)
32. vuex是什麼,有什麼用
vuex是一個專為vue.js應用程式開發的狀態管理模式,它採用集中式儲存管理應
用所有元件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化,應
用遇到多個元件共享狀態時,使用vuex
vuex的五個特性:state,getter,mutation,action,module
vuex總結
33. 單例模式是什麼,策略模式又是什麼
單例模式保證了全域性只有一個例項來被訪問。比如說常用的如彈框元件的實現和全域性狀態的實現。
策略模式主要是用來將方法的實現和方法的呼叫分離開,外部通過不同的引數可以呼叫不同的策略。我主要在 MVP 模式解耦的時候
用來將檢視層的方法定義和方法呼叫分離。
- 時間複雜度和空間複雜度是什麼
1、時間複雜度是指執行演算法所需要的計算工作量。
時間複雜度是一個函式,它定性描述了該演算法的bai執行時間。這是一個關於代表演算法輸入值的字串的長度的函式。時間複雜度常用大O符號表述,不包括這個函式的低階項和首項係數。
2、空間複雜度是指執行這個演算法所需要的記憶體空間。
空間複雜度需要考慮在執行過程中為區域性變數分配的儲存空間的大小,它包括為參數列中形參變數分配的儲存空間和為在函式體中定義的區域性變數分配的儲存空間兩個部分。
- 常見排序演算法的時間複雜度度和空間複雜度是什麼
選擇排序 時間複雜度為O(n^2),空間複雜度為
快排排序
堆排序
歸併排序
- 陣列的一些用法
push()
pop()
shift()
unshift()
splice()
slice()
37.怎麼判斷一個物件是array(typeof返回的是物件)
Array.prototype.isPrototypeOf(obj)
obj instanceof Array
相關文章
- 面試總結面試
- java面試總結Java面試
- 面試技巧總結面試
- 面試題總結面試題
- VUE 面試總結Vue面試
- iOS 面試總結iOS面試
- 面試總結(一)面試
- UC面試總結面試
- Servlet面試總結Servlet面試
- kafka面試總結Kafka面試
- golang 面試總結Golang面試
- sql面試總結SQL面試
- css面試題總結CSS面試題
- RunLoop總結與面試OOP面試
- nodejs面試總結NodeJS面試
- iOS 面試題總結iOS面試題
- 今日面試總結面試
- PHP面試題總結PHP面試題
- Android面試總結Android面試
- 面試問題總結面試
- Kafka面試題總結Kafka面試題
- Ajax面試題總結面試題
- 面試刷題總結面試
- 電話面試總結面試
- 面試失敗總結面試
- 前端面試總結前端面試
- 面試官的總結面試
- 面試題總結-最新面試題
- 漢得面試總結面試
- 記錄近期面試題,面試總結面試題
- 【面試總結】記一次失敗的 bilibili 面試總結(3)面試
- 【面試總結】記一次失敗的 bilibili 面試總結(2)面試
- 【面試總結】記一次失敗的 bilibili 面試總結(1)面試
- 前端面試題總結前端面試題
- 面試題總結-Java部分面試題Java
- 前端面試題(總結)前端面試題
- 前端秋招面試總結前端面試
- PHP面試問題總結PHP面試