11. 介紹一下sessionStorage 、localStorage 和 cookie 之間的區別?cookie和token作爲驗證手段又有什麼區別?
sessionStorage 、localStorage 和 cookie 都保存在瀏覽器端,它們的區別是:
- sessionStorage:作爲臨時數據存儲,在不同的瀏覽器窗口裏並不共享,當關閉瀏覽器後將會被自動刪除。
- localStorage:存儲的數據始終有效,而且在不同的瀏覽器窗口可以共享數據,當關閉瀏覽器後也不會被自動刪除。
- cookie:存儲的數據用於在服務端與瀏覽器之間傳遞,只要發生請求都會自動加到HTTP的請求數據中,常用於辨別用戶身份,一般都會經過加密處理。
token是經過一定規則加密的信息字段,是前後端分離後比較常用的一種驗證手段,由服務端生成並存儲在客戶端,在客戶端向服務端發送請求時,會把token放在header中一起發送給服務端以驗證請求的有效性。
token與cookie的區別是:
- cookie:cookie的驗證是有狀態的,就是說驗證或者會話信息必須同時在客戶端和服務端保存。這個信息服務端一般在數據庫中記錄,而前端會保存在cookie中。
- token:token的驗證是無狀態的。服務器不記錄哪些用戶已登陸。對服務器的每個請求都需要帶上驗證請求的token。該標記既可以加在header中,可以在POST請求的主體中發送,也可以作爲查詢參數發送。
token相對cookie有更多的優勢:
- 優勢1:它是無狀態的,後端服務不需要記錄token。每個令牌都是獨立的,包括檢查其有效性所需的所有數據,並通過聲明傳達用戶信息。
- 優勢2:防跨站請求僞造(CSRF),如防止已驗證的cookie被攔截。
- 優勢3:一個token支持多站點使用,而cookie只能是一對一。
- 優勢4:支持移動端。
- 優勢5:性能更優,免去服務器向數據庫查詢session信息以驗證cookie。
- 優勢6:現在的後端API大多設計成規範的無狀態RESTful,token更搭配。
12. 介紹從輸入URL到頁面加載全過程?
這是一道經典的面試題,涉及到的知識點也很多。其大概過程如下:
(1)瀏覽器的地址欄輸入URL並按下回車。
URL的構成包括:
其中主機爲域名+端口,http協議默認端口80,https協議默認端口443。
(2)瀏覽器查找當前URL是否存在緩存,並檢測緩存是否過期。
緩存的常用字段是last-modified和Etag:
- last-modified — 第一次請求資源時,服務器返回的字段,表示最後一次服務更新的時間。
- Etag — 資源的實體標識(哈希字符串),當資源內容更新時,Etag會改變。
如果緩存已過期就返回新資源給瀏覽器,否則返回403狀態碼告訴服務器使用緩存內容,渲染界面。
(3)DNS域名解析
這步是指將域名解析成IP地址,選擇解析器的順序爲:本地hosts文件、本地DNS解析器緩存、本地DNS解析器。只要其中一步解析成功就停止解析,進入下一步。
(4)TCP連接
獲取到具體IP地址後,便會開始建立一次連接,這是由TCP協議完成的,主要通過三次握手進行連接。主要目的是檢查服務器是否在線,如果在線則開始發送數據請求。
(5)瀏覽器向服務器發送HTTP請求
請求遵循HTTP協議,格式包括請求行、請求頭和請求體三部分
(6)服務器響應
格式格式包括狀態行、響應頭和響應體三部分
(7)頁面渲染
瀏覽器渲染頁面。
13. == 和 === 的區別,什麼情況下用 ==?
==兩個等號稱爲等值符,當等號兩邊的值爲相同類型時比較值是否相同,類型不同時會發生類型的自動轉換,轉換爲相同的類型後再作比較。
===三個等號稱爲等同符,當等號兩邊的值爲相同類型的時候,直接比較等號兩邊的值,值相同則返回true,若等號兩邊的值類型不同時直接返回false。
有三種情況下可以使用等值符==:
- 清楚兩個比較對象是同等類型的時候,因爲類型相同其意義等同於===。
- undefined和null比較的時候,因爲它們都表示無意義,undefined == null、null == null 都返回true。
- 字符串和數組比較的時候,可以實現字符串格式自動轉換爲數字後再比較。
14. bind、call、apply的區別是什麼?
bind、call、apply都是函數自帶的方法,目的都是用於改變自身this的指向,而在ES5中,function(){ this.x = xxx } 中的this是執行中綁定的。關於this的概念可以查看《18~19年大廠高級前端面招彙總之基礎篇(二)》中的 “10.介紹this各種情況”。 bind、call、apply的區別是:
apply和call意義是一樣的,只是入參方式不同;bind則是會生成一個新的實例函數,需要通過該實例函數觸發todo函數。看下面代碼:
function todo(key){
console.log(key, this.a);
}
var a = 1;
var obj = {a:2};
todo('fun'); // this指向window全局變量。打印:fun,1
todo.call(obj,'call'); // this指向obj,打印:call,2
todo.apply(obj,['apply']); // this指向obj,打印:apply,2
var bindTD = todo.bind(obj); // this指向obj,生成一個新的實例函數bindTD
bindTD('bind'); // 打印:bind,2
注意,如果obj也是一個函數,則可以用arguments表示其入參,如:
function todo(key) {
console.log(key, this.a);
}
var a = 1;
var obj = function(){
this.a = 2;
return todo.apply(this, arguments)
}
obj('apply'); // apply 2
15. 介紹一下原型鏈?
原型鏈示意圖,這些指向就是原型鏈,原型和原型鏈常用於實現繼承。
要千萬記住一點:__proto__是每個對象都有的一個屬性,而prototype是函數纔會有的屬性!!! function Person() { } 是函數,var person 則是對象。
詳細可以看一下我的另一篇文章《透徹解讀prototype與__proto__》
16. ES6中的Map和原生對象Object有什麼區別?
JavaScript 的對象(Object),本質上是鍵值對的集合(Hash 結構),但是傳統上只能用字符串當作鍵。這給它的使用帶來了很大的限制。爲了解決這個問題,ES6 提供了 Map 數據結構。它類似於對象,也是鍵值對的集合,但是“鍵”的範圍不限於字符串,各種類型的值(包括對象)都可以當作鍵。也就是說,Object 結構提供了“字符串—值”的對應,Map 結構提供了“值—值”的對應,是一種更完善的 Hash 結構實現。如果你需要“鍵值對”的數據結構,Map 比 Object 更合適。
const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
上面的例子展示瞭如何向 Map 添加成員。作爲構造函數,Map 也可以接受一個數組作爲參數。該數組的成員是一個個表示鍵值對的數組。
const map = new Map([
['name', '張三'],
['title', 'Author']
]);
map.size // 2
map.has('name') // true
map.get('name') // "張三"
map.has('title') // true
map.get('title') // "Author"
Map 結構原生提供三個遍歷器生成函數和一個遍歷方法。
keys()
:返回鍵名的遍歷器。values()
:返回鍵值的遍歷器。entries()
:返回所有成員的遍歷器。forEach()
:遍歷 Map 的所有成員。
const map = new Map([
['F', 'no'],
['T', 'yes'],
]);
for (let key of map.keys()) {
console.log(key);
}
// "F"
// "T"
for (let value of map.values()) {
console.log(value);
}
// "no"
// "yes"
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
// 等同於使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
Map 的forEach
方法,與數組的forEach
方法類似,也可以實現遍歷。
map.forEach(function(value, key, map) {
console.log("Key: %s, Value: %s", key, value);
});
forEach
方法還可以接受第二個參數,用來綁定this
。
const reporter = {
report: function(key, value) {
console.log("Key: %s, Value: %s", key, value);
}
};
map.forEach(function(value, key, map) {
this.report(key, value);
}, reporter);
Map轉換爲數組可以使用...擴展符,如
const myMap = new Map()
.set(true, 7)
.set({foo: 3}, ['abc']);
[...myMap]
// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
其它更詳細方法可以參考http://es6.ruanyifeng.com/#docs/set-map#Map
17. 如何設計一個localStorage,保證數據的實效性?
設計思路:在localStorage存儲鍵值的同時,多存入一組帶時間參數的鍵值對象,該對象的鍵由key+標識符組成,值爲有效期時間。在取值時,先驗證是否在有效期範圍,如果已經過期則返回null。其存儲效果如圖:
具體實現代碼如下:
// 添加帶有效期的set方法,expired 單位(毫秒)
localStorage.__proto__.setExpiredItem = function(key, value, expired){
this.setItem(key, value);
if(expired){
this.setItem([`${key}__expires__`], Date.now() + expired);
}
}
// 添加帶有效期的get方法
localStorage.__proto__.getExpiredItem = function(key){
var expired = this.getItem([`${key}__expires__`]);
var result;
if(expired){
var nowDate = Date.now();
if(nowDate-expired > 0){
// 已經超時, 刪除該鍵值
this.removeItem(key);
this.removeItem([`${key}__expires__`]);
result = null;
}else{
result = this.getItem(key);
}
}else{
result = this.getItem(key);
}
return result;
}
// 過期。結果爲null,而且localStorage裏的相關鍵值被移除
localStorage.setExpiredItem('id','1', 4000);
setTimeout(function(){
console.log(localStorage.getExpiredItem('id')); // null
},5000)
// 未過期。結果爲1,localStorage裏鍵值依舊存在
localStorage.setExpiredItem('id','1', 6000);
setTimeout(function(){
console.log(localStorage.getExpiredItem('id')); // 1
},5000)
// 當未設置有效期時,效果等同於setItem和getItem
localStorage.setExpiredItem('id','1');
setTimeout(function(){
console.log(localStorage.getExpiredItem('id')); // 1
},5000)
18. 編寫一個求和函數sum,使輸入sum(2)(3)或輸入sum(2,3),輸出結果都爲5
思路:使用arguments,要記住,arguments是函數自帶的表示參數的對象,格式爲數組。
function sum(){
if(arguments.length == 1){
var first = arguments[0];
return function(second){
return (first+second);
}
}else{
var total = 0;
for(var i=0;i<arguments.length;i++){
total += arguments[i];
}
return total;
}
}
console.log(sum(2,3)); // 5
console.log(sum(2)(3)); // 5
19. 如何比較兩個對象是否相同?
轉換成字符串格式後比較,JSON.stringify(objA)==JSON.stringify(objB),格式可以任何字符串、數字、數組或對象。
20. 介紹變量的作用域鏈?
當代碼在一個環境中執行時,就會創建變量對象的作用域鏈。作用域鏈(Scope Chain)是javascript內部中一種變量、函數查找機制,它決定了變量和函數的作用範圍。舉個例子,每一段js代碼(全局代碼和函數)都有一個與之關聯的作用域鏈,這個作用域鏈是一個對象列表或者鏈表,這組對象定義了這段代碼‘作用域中的變量’,當js需要查找變量x的值得時候,它會從鏈的第一個對象開始查找,如果這個對象有個一個名爲x的屬性,則直接使用這個屬性的值,如果不存在x屬性,js會查找鏈上的下一個對象,如果仍然沒有則繼續下一個對象,以此類推。如果該鏈上沒有任何一個對象用於x屬性,那麼就認爲該段代碼作用域鏈上不存在x,最終拋出一個引用異常錯誤。
判斷方法:看函數構造體本身外層是否有其它函數,如果有,外N層函數中的作用域就是該變量的作用域,越靠近優先級越高; 如果沒有,就是window。如:
var a = 1;
// 這裏funA構造體本身外層就是window,故a取值於全局變量
function funA(){
console.log(a);
}
function funB(){
var a = 2;
funA();
}
funB(); // 1
var a = 1;
// 這裏funA構造體本身外層是funB,故a取值於funB的作用域
function funB(){
var a = 2;
var funA = function (){
console.log(a);
}
funA();
}
funB(); // 2