高級前端軟件工程師知識整理之基礎篇(三)

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章