1. 程式人生 > >記阿里前端第一次電面(螞蟻金服)

記阿里前端第一次電面(螞蟻金服)

記阿里前端第一次電面

前端妹子一枚,研究生二年級,學習前端半年多,以前從未找過工作,今年春天想先投投實習練練手。實驗室投前端的同學有六個,前幾天,大家陸陸續續也都接到了阿里的面試電話,所以心裡一直惴惴不安。終於,3月4號星期六早上十點多便接到了浙江杭州的電話,本來覺得沒準備好還想推一推的,最後一咬牙覺得就這樣吧,於是便開始電面。 面試官是個聲音很溫柔的男生,簡單自我介紹後,便開始問一些基礎問題了。

Q&A

1..
Q:談談閉包
A:閉包指的是有權訪問另一個函式作用域中變數的函式,建立閉包的常見方式,就是在一個函式內部建立另一個函式。要理解閉包,首先必須理解Javascript特殊的變數作用域。變數的作用域無非就是兩種:全域性變數和區域性變數。函式內部可以直接讀取全域性變數,但在函式外部無法讀取函式內的區域性變數。如果我們有時候需要得到函式內的區域性變數。正常情況下,這是辦不到的,只有通過變通方法才能實現。那就是在函式的內部,再定義一個函式。

 function f1(){
    var n=999;
    function f2(){
      alert(n); // 999
    }
  }

在上面的程式碼中,函式f2就被包括在函式f1內部,這時f1內部的所有區域性變數,對f2都是可見的。但是反過來就不行,f2內部的區域性變數,對f1就是不可見的。這就是Javascript語言特有的”鏈式作用域”結構(chain scope),子物件會一級一級地向上尋找所有父物件的變數。所以,父物件的所有變數,對子物件都是可見的,反之則不成立。

既然f2可以讀取f1中的區域性變數,那麼只要把f2作為返回值,就可以在f1外部讀取它的內部變量了!

function f1(){
    var n=999;
    function f2(){
      alert(n); 
    }
    return f2;
  }
  var result=f1();
  result(); // 999

2..
Q:那閉包的用處有什麼?
A:閉包可以用在許多地方。它的最大用處有兩個,一個是前面提到的可以讀取函式內部的變數,另一個就是讓這些變數的值始終保持在記憶體中。

function f1(){
    var n=999;
    nAdd=function(){n+=1}
    function f2(){
      alert(n);
    }
    return
f2;   }   var result=f1();   result(); // 999   nAdd();   result(); // 1000

在這段程式碼中,result實際上就是閉包f2函式。它一共運行了兩次,第一次的值是999,第二次的值是1000。這證明了,函式f1中的區域性變數n一直儲存在記憶體中,並沒有在f1呼叫後被自動清除。

為什麼會這樣呢?原因就在於f1是f2的父函式,而f2被賦給了一個全域性變數,這導致f2始終在記憶體中,而f2的存在依賴於f1,因此f1也始終在記憶體中,不會在呼叫結束後,被垃圾回收機制(garbage collection)回收。

這段程式碼中另一個值得注意的地方,就是”nAdd=function(){n+=1}”這一行,首先在nAdd前面沒有使用var關鍵字,因此nAdd是一個全域性變數,而不是區域性變數。其次,nAdd的值是一個匿名函式(anonymous function),而這個匿名函式本身也是一個閉包,所以nAdd相當於是一個setter,可以在函式外部對函式內部的區域性變數進行操作。

3..
Q:使用閉包的問題?
A:由於閉包會使得函式中的變數都被儲存在記憶體中,記憶體消耗很大,所以不能濫用閉包,否則會造成網頁的效能問題,在IE中可能導致記憶體洩露。解決方法是,在退出函式之前,將不使用的區域性變數全部刪除。
由於IE9 之前的版本對JScript 物件和COM 物件使用不同的垃圾收集。因此閉包在IE 的這些版本中會導致一些特殊的問題。具體來說,如果閉包的作用域鏈中儲存著一個HTML 元素,那麼就意味著該元素將無法被銷燬。

function assignHandler(){
    var element = document.getElementById("someElement");
    element.onclick = function(){
        alert(element.id);
    };
}

以上程式碼建立了一個作為element 元素事件處理程式的閉包,而這個閉包則又建立了一個迴圈引用。由於匿名函式儲存了一個對assignHandler()的活動物件的引用,因此就會導致無法減少element 的引用數。只要匿名函式存在,element 的引用數至少也是1,因此它所佔用的記憶體就永遠不會被回收,這是IE的問題,所以閉包和記憶體洩漏沒半毛錢關係。

解決辦法,把element.id 的一個副本儲存在一個變數中,從而消除閉包中該變數的迴圈引用同時將element變數設為null。

function assignHandler(){
    var element = document.getElementById("someElement");
    var id = element.id;
    element.onclick = function(){
        alert(id);
    };
    element = null;
}

4..
Q:談談對Ajax的瞭解吧?
A:Ajax是無需重新整理頁面就能夠從伺服器取得資料的一種方法。

  • Ajax技術的核心是XMLHttpRequest物件(XHR物件)
  • XHR物件由微軟最早在IE5中引用,用於通過JavaScript從伺服器取得XML資料.在IE中可能會遇到三種不同版本的XHR物件。
  • 在此之後,IE7+、Firefox、Safari、Chrome、Opera都實現了相同的特性,使XHR成為了Web的一個事實標準,它們都支援原生的XHR物件。
  • 雖然實現之間有差異,但XHR物件的基本用法在不同瀏覽器間還是相對規範的,因此可以放心地用在Web開發之中。

5
Q:Ajax為什麼不能跨域?
A:由於瀏覽器實現的同源策略的限制,XmlHttpRequest只允許請求當前源(域名、協議、埠)的資源,所以AJAX是不允許跨域的。不過 像<script>,<link>,<img>,<iframe>這些標籤是允許跨域的,但你並不能修 改這些資源,比如iframe裡的內容。

為什麼瀏覽器要實現同源限制?我們舉例說明:比如一個黑客,他利用iframe把真正的銀行登入頁面嵌到他的頁面上,當你使用真實的使用者名稱和密碼登入時,如果沒有同源限制,他的頁面就可以通過 JavaScript讀取到你的表單中輸入的內容,這樣使用者名稱和密碼就輕鬆到手了.又比如你登入了OSC,同時瀏覽了惡意網站,如果沒有同源限制,該惡意 網站就可以構造AJAX請求頻繁在OSC發廣告帖.

6..
Q:跨域的幾種方式?
A:1)CORS(跨域源資源共享)
是W3C的一個工作草案,定義了在必須訪問跨源資源時,瀏覽器與伺服器應該如何溝通。CORS背後的基本思想,就是使用自定義的HTTP頭部讓瀏覽器與伺服器進行溝通,從而決定請求或響應是應該成功,還是應該失敗。
IE8通過XDomainRequest物件支援CORS,其他瀏覽器通過XHR物件原生支援CORS。即使瀏覽器對CORS的支援程度並不都一樣,但所有瀏覽器都支援簡單的請求。跨瀏覽器方案:
檢查XHR是否支援CORS的最簡單方式,就是檢查是否存在withCredentials屬性,再結合檢測XDomainRequest物件是否存在,就可以兼顧所有瀏覽器了。

function createCORSRequest(method, url){
  var xhr = new XMLHttpRequest();
  If(“withCredentials” in xhr){
     xhr.open(method, url, true);
  }else if(typeof  XDomainRequest !=”undefined”){
     xhr = new XDomainRequest();
     xhr.open(method, url);
  }else {
     xhr = null;
} 
     return xhr;
}
var request = createCORSRequest(“get”,”http://www.somewhere-else.com/page/”);
If(request){
  request.onload = function(){
//對request.responseText進行處理
}
  request.send();
}

2)JSONP
JSONP由兩部分組成:回撥函式和資料。
JSONP為JSON的一種“使用模式”,可以用與解決主流瀏覽器的跨域資料訪問問題。一般而言,不同伺服器上網頁是無法溝通的,但是html中<script>元素為一個例外。JSONP就是利用<script>元素的開放策略,網頁中可以得到從其他源中動態產生的JSON資料,JSONP抓到的資料不是JSON而是js。類似的<script>、<img>、<iframe>均不受跨域影響。
JSONP是一個非正式的傳輸協議,實現了AJAX不可跨域的缺陷,該協議允許使用者傳遞一個callback引數給伺服器端,然後服務端返回資料時將這callback引數作為函式名來包裹住JSON資料,這樣客戶端就可以隨意定製自己的函式來自動處理返回的資料。
例子:

var flightHandler=function(data){...}
var url=".....?Code=123&callback=flightHandler";
var script=document.createElement(“script”);
script.setAttribute(“src”,url);
document.getElementsByTagName(“Head”)[0].appendChild(script); 

上面程式碼中url中傳遞了一個code引數,並通過callback引數告訴伺服器,我的本地回撥函式叫做fightHandler所以請將查詢結果傳入這個函式中呼叫。Ext和jQuery會自動生成回撥函式,並將資料取出來工success屬性方法呼叫,Ext和jQuery等框架將JSONP作為AJAX的一種形式進行封裝。

雖然很多框架將JSONP和AJAX以同樣的方式封裝在一起,操作方式也很相似,然而實際上這二者之間大相徑庭。主要表現在一下的不同:
① AJAX的原理是XMLHttpRequest,而JSONP的原理是script。
② JSONP的相容性更好,在更加古老的瀏覽器中都可以執行,但是AJAX則需要XMLHttpRequest和ActiveX的支援。
③ JSONP僅僅支援以GET方式請求,而AJAX支援GET和POST方式。

3)使用window.name來進行跨域
window物件有個name屬性,該屬性有個特徵:即在一個視窗(window)的生命週期內,視窗載入的所有的頁面都是共享一個window.name的,每個頁面對window.name都有讀寫的許可權,window.name是持久存在一個視窗載入過的所有頁面中的。

4)使用HTML5的window.postMessage方法跨域

跨文件訊息傳送(XDM),指來自不同域的頁面間傳遞訊息,XDM的核心是postMessage()方法,向另一個地方傳遞資料,指的是包含在當前頁面中的<iframe>元素,或者由當前頁面彈出的視窗。

7..
Q:什麼是CSRF攻擊?舉例?如何防禦CSRF?
A:CSRF 即:跨站點請求偽造

網站A :為惡意網站。
網站B :使用者已登入的網站。
當用戶訪問 A站 時,A站 私自訪問 B站 的操作連結,模擬使用者操作。

假設B站有一個刪除評論的連結:`http://b.com/comment/?type=delete&id=81723`
A站 直接訪問該連結,就能刪除使用者在 B站 的評論。

CSRF 的攻擊策略

因為瀏覽器訪問 B站 相關連結時,會向其伺服器傳送 B站 儲存在本地的Cookie,以判斷使用者是否登陸。所以通過 A站 訪問的連結,也能順利執行。

CSRF 防禦技巧

  • 驗證碼

幾乎所有人都知道驗證碼,但驗證碼不單單用來防止註冊機的暴力破解,還可以有效防止CSRF的攻擊。
驗證碼算是對抗CSRF攻擊最簡潔有效的方法。
但使用驗證碼的問題在於,不可能在使用者的所有操作上都需要輸入驗證碼。
只有一些關鍵的操作,才能要求輸入驗證碼。
不過隨著HTML5的發展。
利用canvas標籤,前端也能識別驗證碼的字元,讓CSRF生效。

  • Referer Check

Referer Check即來源檢測。
HTTP Referer 是 Request Headers 的一部分,當瀏覽器向web伺服器發出請求的時候,一般會帶上Referer,告訴伺服器使用者從哪個站點連結過來的。
伺服器通過判斷請求頭中的referer,也能避免CSRF的攻擊。

  • Token

CSRF能攻擊成功,根本原因是:操作所帶的引數均被攻擊者猜測到。

既然知道根本原因,我們就對症下藥,利用Token。
當向伺服器傳引數時,帶上Token。這個Token是一個隨機值,並且由伺服器和使用者同時持有。
Token可以存放在使用者瀏覽器的Cookie中,
當用戶提交表單時帶上Token值,伺服器就能驗證表單和Cookie中的Token是否一致。
(前提,網站沒有XSS漏洞,攻擊者不能通過指令碼獲取使用者的Cookie)

總結:問的都不難,但自己的理解都不夠深入,再加上語言表達能力不強,導致表現不佳。以上是查閱資料後寫的完整答案。不管怎樣,總歸是人生的第一次面試,再接再厲了。