更好閱讀,請移步這里
聊之前
最近暑期實習招聘已經開始,個人目前參加了阿里的內推及騰訊和百度的實習生招聘,在此總結一下
一是備忘、總結提升,二是希望給大家一些參考
其他面試及基礎相關可以參考其他博文:
每位面試官的面試時間基本都在 40-80 分鍾,下面先簡要介紹各個面試流程,問題詳情見具體公司分類
騰訊內推&校招 offer got
首先騰訊分為三面,都為技術面:
-
初試一個面試官
-
復試兩個面試官,包括電話遠程 online coding
(我也不知道為什么幾天內三個面試官面我,只算兩個面試過程 ヾ(´A`)ノ゚)
-
終面一位面試官
這位應該是大 boss ,他說並不是前端開發的,面試的問題也不會很深入,主要看個人對這個領域的想法以及對常見問題在前端領域的解決思路,不按套路出牌,emmm 感覺回答的不理想
類似這種問題:-
說說最能體現你能力的工作
-
網絡安全的實現
如何檢測惡意腳本
如何屏蔽
...
-
騰訊二面 & online coding
-
騰訊的遠程 online coding 時間較長,大概一個多小時,不僅要按要求編程,也要口述思路以及關鍵點,過程類似壓力面,面試官說完題目要求會給你思考及coding 時間,他可以遠程看到面試者的 coding 狀態,主要考察應變能力,思維活躍度,編碼習慣,調試能力,以及測試方法,本人在此過程中沒有太注意測試過程,導致對於特殊情況考慮不全面,測試樣例不完善,望小伙伴們注意 ヾ(´A`)ノ゚,不過,在口述代碼時發現也可以自己提出來需要完善的地方。
-
coding 為一到兩題,后續問題都是圍繞它結合實際應用進行拓展,主要考察是否能靈活運用以及相似的思路轉換,當時面試時間太長以及基礎知識較差,進制轉換,存儲那些個基礎被小學老師收回,一連串的炮轟簡直爽歪歪,個人表示此過程自己的表現較差,也要重視基礎基礎啊老鐵們 ( ̄_ ̄ )
經歷了騰訊雲的四個面試官,以及其他部門一個面試官的醬油面,騰訊的技術面試官普遍語速較快,思路轉換很快,要跟上面試官的節奏,聊到自己比較熟悉的可以多說幾句,他們也會順着你回答的內容進行深入,也會引導面試者的回答方向,如果不太熟盡量坦白一些,不懂裝懂很容易 gg
才接到通知另加兩輪面試委員會現場技術面,簡直爽歪歪 ヾ(´A`)ノ
騰訊校招現場
-
一面
面試官很 nice,基本看着簡歷問的,話題圍繞項目(項目隔得時間太久,內心寧願粗暴的技術面),基本為大致的了解,包括平時的學習習慣和關注的技術發展
-
問到了 ES5 ES6 ES7 甚至 ES8
-
項目中用過vue, 就問了 react vue 相關
-
實習中的工作方式,如何處理設計師的設計稿,回答了包括考慮響應式布局的常用方案
-
sass--定義function,使用rem
問是否有其他負面作用
想到了頁面閃爍,由於初始化時需要計算頁面尺寸,可能有縮放
如何避免:
<!-- 如果在頁面加載完成后,頁面是用js動態添加的,這個問題就不太明顯, --> doc.addEventListener('DOMContentLoaded‘', function(e) { <!-- doc.body.style.fontSize = 12 * dpr + 'px'; 淘寶處理 --> }, false);
-
媒體查詢
-
-
移動端和pc端的工作
-
-
二面
面試官不是前端的,所以沒有問很深入的問題,主要模擬實際場景考察實現思路以及前后端的方案,后端沒說出什么,基本上只是就前端層面進行了描述,包括實現思路、需要注意的問題、以及性能和安全方面的考慮
- 前端安全
- 場景考察
- 直播彈幕處理
- 后端返回大量數據且包含運算,如何實時顯示
- 。。。記不清了
-
HR 面
- 介紹經歷
- 家庭情況
- 職業規划
- 對騰訊的認識及與其他實習單位對比
- 對比實習過的企業
- 是否還報了其他企業
- 男朋友工作找的怎么樣了
阿里內推 二面 卒
我投的是螞蟻金服的前端開發,投過簡歷硬生生排隊等了12天,還被內推人提前告知螞蟻的前端很嚴格 ( ̄_ ̄ )
阿里分為在線測試,初試 ......
-
首先是在線測試
投完簡歷后官網會有相關在線測試題
阿里前端的在線測試只有一道coding 題,限時 30 分,由於初次在線答題,看着倒計時緊張的思路不通,未能准確理解題意,但實際題目並不難,考察使用原生 js 實現類似css 的層級選擇器的功能,具體題目記不太清,將僅存的記憶寫了下來並附上個人實現,詳情見本文后部分 -
一面
-
二面
-
掛掉了。。。
baidu offer got
百度投的是核心搜索部門,但一面后估計不合適,沒有了消息,后簡歷轉到百度金融
-
一面
面試官語速很快,一般不給太多思考時間--------感覺自己說話都打着節拍 ( ̄_ ̄ )
-
二面
面試官很和藹,是唯一一個認真看了我博客的面試官,很榮幸,也很緊張
基本偏向基礎
但由於沒安排好時間,面試時在戶外,聽不太清面試官的問題,在此提醒各位小伙伴提前選好面試場地,避免環境影響 -
三面
同樣和藹的面試官,開始考察基礎知識,編譯原理,網絡協議等基礎,后面考察個人素質,后續更注重個人經歷,包括如何學習,找實習,實習中遇到的問題及如何解決,最驕傲的事情等等
關於結尾
-
百度 & 阿里面試結束后都有問:你覺得在面試過程中有什么沒問到,但自己掌握比較好的技能么
面阿里時,頭腦發暈,回答:沒有,我感覺您面的很專業,問的知識點都是比較核心的 ( ̄_ ̄ )
百度經驗: 回答自己掌握還比較好的知識后,面試官真的會問很多問題(菜鳥可以參考以上做法),如果面試官面試的較偏,倒可以補充 -
有什么要問的
-
騰訊二面教訓
我:是否有導師帶,實習的大致安排以及部門業務等
效果 差
面試官曰:
你那么關系有沒有導師的問題,但學習中最重要的是靠自己,導師並不是負責回答你所有問題的
-
百度經驗
我:部門是否有定期交流分享的機會;工作中是按照職位還是業務部門划分,如何交流;偏向頁面還是業務邏輯
個人在自學過程中發現前端體系太大,不知對於前端學習,您有什么建議么
面試官:很多前端初學者都有類似的困惑,你最好自己從頭開始做一個項目,不管是自己瞎想的還是模仿的,在項目過程中去發現需要學習什么技術,在遇到問題的時候才知道去哪個方向發展和思考,只有在項目中才能持續的學習和提高,前端知識很碎,沒有項目很難有一連串的經歷
-
總體
總體來說,面試中有按照面試題出的,也有直接聊的,一般也會結合實際工作中會遇到的場景以及技術中的一些坑,回答時結合自己的項目經驗會更好,大廠的面試官更側重於面試者對深層原理的理解,對於實習生來說一般面基礎,如果有深查原理的習慣,個人的可塑造性也會較高
三廠體驗對比:
-
騰訊阿里面試官一面開始都比較側重實踐,如果簡歷上有過實踐項目和實習經歷,會問更實際的問題,構造場景讓解答
-
協議都會問到
-
相對來說,百度更多基礎知識點,更多考察對更基本的知識掌握,不限於前端,還包括組成原理和編譯原理的一些知識,當然前端偏多(比如選取一個class 標簽元素有幾種方式等小細節的問題來考察,細到要把每個表達說完整,把每個單詞拼出來)
-
阿里騰訊更側重應用中的注意事項(如:IE 和其他瀏覽器中的事件處理機制)不太揪細節
-
三廠都有問到算法,騰訊相對更注重對算法和邏輯,對面試者基礎知識要求較高,甚至涉及更底層的。
-
另兩廠對算法,數據結構的要求都是了解階段
以下為面試中的一些知識點以及個人的一些補充,敲黑板啦啦啦
1. Tencent
1.1. js 的事件機制
事件階段
一般的,事件分為三個階段:捕獲階段、目標階段和冒泡階段。
-
捕獲階段(Capture Phase)
-
事件的第一個階段是捕獲階段。事件從文檔的根節點流向目標對象節點。途中經過各個層次的DOM節點,並在各節點上觸發捕獲事件,直到到達事件的目標節點。捕獲階段的主要任務是建立傳播路徑,在冒泡階段,事件會通過這個路徑回溯到文檔跟節點。
-
或這樣描述:
任何事件產生時,如點擊一個按鈕,將從最頂端的容器開始(一般是html的根節點)。瀏覽器會向下遍歷DOM樹直到找到觸發事件的元素,一旦瀏覽器找到該元素,事件流就進入事件目標階段
-
-
目標階段(Target Phase)
- 當事件到達目標節點的,事件就進入了目標階段。事件在目標節點上被觸發,然后會逆向回流,直到傳播至最外層的文檔節點。
-
冒泡階段(Bubble Phase)
- 事件在目標元素上觸發后,並不在這個元素上終止。它會隨着DOM樹一層層向上冒泡,回溯到根節點。
- 冒泡過程非常有用。它將我們從對特定元素的事件監聽中釋放出來,如果沒有事件冒泡,我們需要監聽很多不同的元素來確保捕獲到想要的事件
事件處理程序
-
DOM0 級事件處理程序
var btn5 = document.getElementById('btn5'); btn5.onclick=function(){ console.log(this.id);//btn5 };
-
基於 DOM0 的事件,對於同一個 dom 節點而言,只能注冊一個,后邊注冊的 同種事件 會覆蓋之前注冊的。
利用這個原理我們可以解除事件,
btn5.onclick=null;
其中this就是綁定事件的那個元素; -
以這種方式添加的事件處理程序會在事件流的冒泡階段被處理;
-
-
DOM2 級事件處理程序
-
DOM2支持同一dom元素注冊多個同種事件,事件發生的順序按照添加的順序依次觸發(IE是相反的)。
-
DOM2事件通過
addEventListener
和removeEventListener
管理// addEventListener(eventName,handlers,boolean);removeEventListener() // 兩個方法都一樣接收三個參數,第一個是要處理的事件名,第二個是事件處理程序, // 第三個值為false時表示在事件冒泡階段調用事件處理程序,一般建議在冒泡階段使用, // 特殊情況才在捕獲階段; // 注意:通過addEventListener()添加的事件處理程序只能用removeEventListener()來移除 // 並且移除時傳入的參數必須與添加時傳入的參數一樣;比如 var btn2 = document.getElementById('btn2'); var handlers = function () { console.log(this.id); }; btn2.addEventListener('click',handlers,false); btn2.removeEventListener('click',handlers.false);
-
ie 事件處理程序
//IE事件處理程序(IE和Opera支持) /* IE用了attachEvent(),detachEvent(),接收兩個參數,事件名稱和事件處理程序, * 通過attachEvent()添加的事件處理程序都會被添加到冒泡階段,所以平時為了兼容更多的瀏覽器最好將事件添加到事件冒泡階段,IE8及以前只支持事件冒泡; */ var btn3 = document.getElementById('btn3'); var handlers2=function(){ console.log(this===window); // true,注意attachEvent()添加的事件處理程序運行在全局作用域; }; btn3.attachEvent('onclick',handlers2);
-
---ie 與其他瀏覽器的區別
總結
DOM事件模型中的事件對象常用屬性:
- type用於獲取事件類型
- target獲取事件目標
- stopPropagation()阻止事件冒泡
- preventDefault()阻止事件默認行為
- 判斷加載狀態 —— onload 事件
IE事件模型中的事件對象常用屬性:
-
type用於獲取事件類型
-
srcElement獲取事件目標
-
cancelBubble 阻止事件冒泡
-
returnValue 阻止事件默認行為
-
通過 readystate 屬性值判斷何時方法下載完畢可用
readystate共有以下幾個值:
- uninitialized: 對象存在但未初始化;
- loading:對象正在加載;
- loaded:對象數據加載完畢;
- interactive:可以操作對象了,但還沒加載完畢;
- complete:加載完畢。
注意上面5個值並不一定每個事件都全包含,並且不一定是什么順序。
Document.readyState 屬性
一個文檔的 readyState 可以是以下之一:-
loading / 加載
document 仍在加載。
-
interactive / 互動
文檔已經完成加載,文檔已被解析,但是諸如圖像,樣式表和框架之類的子資源仍在加載。
-
complete / 完成
T文檔和所有子資源已完成加載。狀態表示 load 事件即將被觸發。
當這個屬性的值變化時,document 對象上的readystatechange 事件將被觸發。
事件對象
-
IE
IE中事件對象是作為全局對象
window.event
存在的 -
Firefox
Firefox中則是做為句柄( handler )的第一個參數傳入
-
通用
var evt = window.event || arguments[0];
事件監聽
-
Chrome、FireFox、Opera、Safari、IE9.0及其以上版本
addEventListener(eventName,handler,boolean); removeEventListener() /* 兩個方法都一樣接收三個參數, * 事件名 * 事件處理程序 * boolean false時表示在事件冒泡階段調用事件處理程序,一般建議在冒泡階段使用 */
-
IE8.0及其以下版本
element.attachEvent(type, handler); element.detachEvent(type, handler); /* element 要綁定事件的對象,html 節點 * type 事件類型 +'on' 如: "onclick, onmouseover" * listener 事件處理程序(只寫函數名,不帶括號) */
-
早期瀏覽器
obj['on' + type] = handler
阻止冒泡
event.stopPropagation
event.cancelBubble = true //IE
阻止默認事件
event.preventDefault()
event.returnValue = false //IE
---通用的事件監聽器
// event(事件)工具集,來源:github.com/markyun
markyun.Event = {
// 頁面加載完成后
readyEvent: function (fn) {
if (fn == null) {
fn = document;
}
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = fn;
} else {
window.onload = function () {
oldonload();
fn();
};
}
},
// 視能力分別使用dom0||dom2||IE方式 來綁定事件
// 參數: 操作的元素,事件名稱 ,事件處理程序
addEvent: function (element, type, handler) {
if (element.addEventListener) {
//事件類型、需要執行的函數、是否捕捉
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent('on' + type, function () {
handler.call(element);
});
} else {
element['on' + type] = handler;
}
},
// 移除事件
removeEvent: function (element, type, handler) {
if (element.removeEnentListener) {
element.removeEnentListener(type, handler, false);
} else if (element.detachEvent) {
element.detachEvent('on' + type, handler);
} else {
element['on' + type] = null;
}
},
// 阻止事件 (主要是事件冒泡,因為IE不支持事件捕獲)
stopPropagation: function (ev) {
if (ev.stopPropagation) {
ev.stopPropagation();
} else {
ev.cancelBubble = true;
}
},
// 取消事件的默認行為
preventDefault: function (event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
// 獲取事件目標
getTarget: function (event) {
return event.target || event.srcElement;
},
// 獲取event對象的引用,取到事件的所有信息,確保隨時能使用event;
getEvent: function (e) {
var ev = e || window.event;
if (!ev) {
var c = this.getEvent.caller;
while (c) {
ev = c.arguments[0];
if (ev && Event == ev.constructor) {
break;
}
c = c.caller;
}
}
return ev;
}
};
1.2. vue
---Vue 生命周期
---雙向綁定原理 & 如何實現
---vuex 原理
---vue 數據更新后執行
1.3. 跨域
什么叫跨域
方案
1.4. 安全 && 怎樣預防
1.5. session & cookie
1.6. 本地存儲
1.7. 瀏覽器緩存
1.8. 頁面從輸入URL 到加載過程
1.9. HTTP
---content-type
-
application/x-www-form-urlencoded
最常見的 POST 提交數據的方式了。瀏覽器的原生 form 表單,如果不設置 enctype 屬性,那么最終就會以 application/x-www-form-urlencoded方式提交數據。
傳遞的key/val會經過URL轉碼,所以如果傳遞的參數存在中文或者特殊字符需要注意。
//例子 //b=曹,a=1 POST HTTP/1.1(CRLF) Host: www.example.com(CRLF) Content-Type: application/x-www-form-urlencoded(CRLF) Cache-Control: no-cache(CRLF) (CRLF) b=%E6%9B%B9&a=1(CRLF) //這里b參數的值"曹"因為URL轉碼變成其他的字符串了
-
multipart/form-data
常見的 POST 數據提交的方式。我們使用表單上傳文件時,必須讓 form 的 enctyped 等於這個值
並且Http協議會使用boundary來分割上傳的參數 -
text/xml
<!-- 例子 --> POST http://www.example.com HTTP/1.1(CRLF) Content-Type: text/xml(CRLF) (CRLF) <?xml version="1.0"?> <resource> <id>123</id> <params> <name> <value>homeway</value> </name> <age> <value>22</value> </age> </params> </resource>
-
application/json
用來告訴服務端消息主體是序列化后的 JSON 字符串
//例子 //傳遞json POST HTTP/1.1(CRLF) Host: www.example.com(CRLF) Content-Type: application/json(CRLF) Cache-Control: no-cache(CRLF) Content-Length: 24(CRLF) (CRLF) { "a":1, "b":"hello" }
(CRLF)指 \r\n
參考: HTTP常見Content-Type比較
1.10. get & post
1.11. TCP & UDP & 握手
---TCP (Transmission Control Protocol)
兩個序號和三個標志位:
- 序號:seq序號,占32位,用來標識從TCP源端向目的端發送的字節流,發起方發送數據時對此進行標記。
- 確認序號:ack序號,占32位,只有ACK標志位為1時,確認序號字段才有效,ack=seq+1。
標志位:共6個,即URG、ACK、PSH、RST、SYN、FIN等,具體含義如下:
- URG:緊急指針(urgent poin* 有效。
- ACK:acknowledgement 確認序號有效。
- PSH:接收方應該盡快將這個報文交給應用層。
- RST:reset 重置連接。
- SYN:synchronous 建立聯機,發起一個新連接。
- FIN:finish 釋放一個連接。
需要注意的是:
- 不要將確認序號ack與標志位中的ACK搞混了。
- 確認方ack=發起方req+1,兩端配對。
- 第一次握手:
Client將標志位SYN置為1,隨機產生一個值 seq=J,並將該數據包發送給Server
Client進入SYN_SENT狀態,等待Server確認。 - 第二次握手:
Server收到數據包后由標志位SYN=1知道Client請求建立連接,Server將標志位SYN和ACK都置為1,ack=J+1,隨機產生一個值seq=K,並將該數據包發送給Client以確認連接請求,Server進入SYN_RCVD狀態。 - 第三次握手:
Client收到確認后,檢查ack是否為J+1,ACK是否為1,如果正確則將標志位ACK置為1,ack=K+1,並將該數據包發送給Server,Server檢查ack是否為K+1,ACK是否為1,如果正確則連接建立成功,Client和Server進入ESTABLISHED狀態,完成三次握手,隨后Client與Server之間可以開始傳輸數據了
為什么需要確認
---四次揮手
由於TCP連接時全雙工的,因此,每個方向都必須要單獨進行關閉,這一原則是當一方完成數據發送任務后,發送一個FIN來終止這一方向的連接,收到一個FIN只是意味着這一方向上沒有數據流動了,即不會再收到數據了,但是在這個TCP連接上仍然能夠發送數據,直到這一方向也發送了FIN
首先進行關閉的一方將執行主動關閉,而另一方則執行被動關閉,上圖描述的即是如此。
- 第一次揮手:
Client發送一個FIN,用來關閉Client到Server的數據傳送,Client進入FIN_WAIT_1狀態。 - 第二次揮手:
Server收到FIN后,發送一個ACK給Client,確認序號為收到序號+1(與SYN相同,一個FIN占用一個序號),Server進入CLOSE_WAIT狀態。 - 第三次揮手:
Server發送一個FIN,用來關閉Server到Client的數據傳送,Server進入LAST_ACK狀態。 - 第四次揮手:
Client收到FIN后,Client進入TIME_WAIT狀態,接着發送一個ACK給Server,確認序號為收到序號+1,Server進入CLOSED狀態,完成四次揮手。
為什么TIME_WAIT狀態需要經過2MSL(最大報文段生存時間)才能返回到CLOSE狀態?
- 可靠的實現 TCP 全雙工連接的終止
- 允許老的重復分節在網絡中消失
MSL是Maximum Segment Lifetime英文的縮寫,中文可以譯為“報文最大生存時間”,他是任何報文在網絡上存在的最長時間,超過這個時間報文將被丟棄,RFC 793中規定MSL為2分鍾,實際應用中常用的是30秒,1分鍾和2分鍾等。2MSL即兩倍的MSL,TCP的TIME_WAIT狀態也稱為2MSL等待狀態
為什么連接的時候是三次握手,關閉的時候卻是四次握手?
- 因為當Server端收到Client端的SYN連接請求報文后,可以直接發送SYN+ACK報文。其中ACK報文是用來應答的,SYN報文是用來同步的。但是關閉連接時
- 當Server端收到FIN報文時,很可能並不會立即關閉SOCKET,所以只能先回復一個ACK報文,告訴Client端,"你發的FIN報文我收到了"。只有等到我Server端所有的報文都發送完了,我才能發送FIN報文,因此不能一起發送。故需要四步握手。
參考: TCP三次握手詳解及釋放連接過程
1.12. HTTP 加密
---加密對象
對於HTTP協議來說,加密的對象有以下兩個:
-
對通信的加密:
HTTP中沒有加密功能,但是可以通過和SSL(Secure Socket Layer,安全套接層)組合使用,加密通信內容。使用SSL建立安全通信線路后,就可以在這條線路上進行HTTP通信了。與SSL組合使用的HTTP被稱為HTTPS(HTTP Secure,超文本傳輸安全協議)。 -
對通信內容本身進行加密
即對HTTP報文里所包含的內容進行加密。這樣,首先客戶端要先對報文進行加密,然后再發給服務器。服務器在接受到請求時,需要對報文進行解密,再處理報文。該方式不同於SSL將整個通信線路進行加密處理,所以內容仍然有被篡改的風險。-
A、任何人都可以發起請求
HTTP協議中,並未有確認通信方這一步驟,所以,任何人都可以發送請求,而服務器在接受到任何請求時,都會做出相應的響應。-
解決方案:
查明對手的證書
雖然HTTP不能確認通信方,但SSL是可以的。SSL不僅提供了加密處理,還使用了"證書"的手段,可用於確認通信方。證書是由值得信賴的第三方機構頒布,可用於確定證明服務器和客戶端是實際存在的。所以,只要能確認通信方持有的證書,即可判斷通信方的真實意圖。
-
-
B、無法判斷報文是否完整(報文可能已遭篡改)
HTTP協議無法判斷報文是否被篡改,在請求或者響應發出后,在對方接收之前,即使請求或者響應遭到篡改是無法得知的。
-
防止篡改:
常用的,確定報文完整性方法:MD5、SHA-1 等 散列值校驗方法,以及用來確認文件的數字簽名方法。但是,使用這些方法,也無法百分百確保結果正確,因為MD5本身被修改的話,用戶是沒辦法意識到得。
-
為了有效防止這些弊端,可以采用HTTPS。
-
-
POST 用戶安全登陸
在關系到用戶隱私的時候,要時刻遵循兩個原則:
- 任何應用程序都不能在本地存儲與安全相關的用戶信息
- 任何應用程序在向服務器傳遞數據的時候,都不能直接傳遞與安全相關的用戶信息。
要想讓用戶信息安全,就必須對其進行加密,讓別人即便是拿到了安全信息,擺在眼前的也是一串亂碼,沒有半點用處
MD5是一種常用的加密方法,它是一種散列函數,利用MD5對用戶信息進行加密,會增加用戶信息安全性。
網上有關於MD5的第三方框架Category
利用這個第三方框架可以實現對密碼進行MD5加密
+隨機亂碼字符防止被破解
---加密算法
對稱加密
非對稱加密
---HTTPS 加密原理
HTTPS簡介
HTTPS其實是有兩部分組成:HTTP + SSL / TLS,也就是在HTTP上又加了一層處理加密信息的模塊。服務端和客戶端的信息傳輸都會通過TLS進行加密,所以傳輸的數據都是加密后的數據
SSL協議是通過非對稱密鑰機制保證雙方身份認證,並完成建立連接,在實際數據通信時通過對稱密鑰機制保障數據安全性
-
服務器 用RSA生成公鑰和私鑰
-
把公鑰放在證書里發送給客戶端,私鑰自己保存
-
客戶端首先向一個權威的服務器檢查證書的合法性,如果證書合法,客戶端產生一段隨機數,這個隨機數就作為通信的密鑰,我們稱之為對稱密鑰,用公鑰加密這段隨機數,然后發送到服務器
-
服務器用密鑰解密獲取對稱密鑰,然后,雙方就已對稱密鑰進行加密解密通信了
HTTPS 在傳輸數據之前需要客戶端(瀏覽器)與服務端(網站)之間進行一次握手,在握手過程中將確立雙方加密傳輸數據的密碼信息。TLS/SSL 協議不僅僅是一套加密傳輸的協議,更是一件經過藝術家精心設計的藝術品,TLS/SSL 中使用了非對稱加密,對稱加密以及 HASH 算法。握手過程的具體描述如下:
-
瀏覽器將自己支持的一套加密規則發送給網站。
-
網站從中選出一組加密算法與HASH算法,並將自己的身份信息以證書的形式發回給瀏覽器。證書里面包含了網站地址,加密公鑰,以及證書的頒發機構等信息。
-
瀏覽器獲得網站證書之后瀏覽器要做以下工作:
a) 驗證證書的合法性(頒發證書的機構是否合法,證書中包含的網站地址是否與正在訪問的地址一致等),如果證書受信任,則瀏覽器欄里面會顯示一個小鎖頭,否則會給出證書不受信的提示。
b) 如果證書受信任,或者是用戶接受了不受信的證書,瀏覽器會生成一串隨機數的密碼,並用證書中提供的公鑰加密。
c) 使用約定好的HASH算法計算握手消息,並使用生成的隨機數對消息進行加密,最后將之前生成的所有信息發送給網站。
-
網站接收瀏覽器發來的數據之后要做以下的操作:
a) 使用自己的私鑰將信息解密取出密碼,使用密碼解密瀏覽器發來的握手消息,並驗證HASH是否與瀏覽器發來的一致。
b) 使用密碼加密一段握手消息,發送給瀏覽器。
-
瀏覽器解密並計算握手消息的HASH,如果與服務端發來的HASH一致,此時握手過程結束,之后所有的通信數據將由之前瀏覽器生成的隨機密碼並利用對稱加密算法進行加密。
參考:圖解HTTPS
如何保證 HTTP 傳輸安全性
-
重要的數據,要加密
比如用戶名密碼(如果簡單的md5,是可以暴力破解),常見的是 md5(不可逆),aes(可逆),自由組合,還可以加一些特殊字符
舉例:
username = aes(username), pwd = MD5(pwd + username)
-
非重要數據,要簽名
簽名的目的是為了防止篡改,比如
http://www.xxx.com/getnews?id=1
,獲取id為1的新聞,如果不簽名那么通過id=2,就可以獲取2的內容等等。怎樣簽名呢?通常使用sign,比如原鏈接請求的時候加一個 sign 參數,sign=md5(id=1),服務器接受到請求,驗證sign是否等於 md5(id=1) ,如果等於說明正常請求。
這會有個弊端,假如規則被發現,那么就會被偽造,所以適當復雜一些,還是能夠提高安全性的。
-
登錄態怎么做
http是無狀態的,也就是服務器沒法自己判斷兩個請求是否有聯系,那么登錄之后,以后的接口怎么判定是否登錄呢
簡單的做法,在數據庫中存一個token字段(名字隨意),當用戶調用登陸接口成功的時候,就將該字段設一個值,(比如aes(過期時間)),同時返回給前端,以后每次前端請求帶上該值,服務器首先校驗是否過期,其次校驗是否正確,不通過就讓其登陸。(redis 做這個很方便哦,key有過期時間)
1.13. 排序:冒泡,選擇,快速
1.14. 數據庫
---觸發器
一種特殊的存儲過程,存儲過程一般通過定義的名字直接調用,而觸發器是通過增、刪、改進行觸發執行的。會在事件發生時自動強制執行
觸發器是一種特殊的存儲過程,主要是通過事件來觸發而被執行的。它可以強化約束,來維護數據的完整性和一致性,可以跟蹤數據庫內的操作從而不允許未經許可的更新和變化。可以聯級運算。如,某表上的觸發器上包含對另一個表的數據操作,而該操作又會導致該表觸發器被觸發。
---事務 & 鎖
-
事務
就是被綁定在一起作為一個邏輯工作單元的SQL語句分組,如果任何一個語句操作失敗那么整個操作就被失敗,以后操作就會回滾到操作前狀態,或者是上有個節點。
為了確保要么執行,要么不執行,就可以使用事務。要將一組語句作為事務考慮,就需要通過ACID測試,即原子性,一致性,隔離性和持久性。
-
鎖:
在所有的DBMS中,鎖是實現事務的關鍵,鎖可以保證事務的完整性和並發性。與現實生活中鎖一樣,它可以使某些數據的擁有者,在某段時間內不能使用某些數據或數據結構。當然鎖還分級別的。共享鎖(只讀不寫)、排他鎖(可讀可寫)
1.15. 軟件設計模式
設計原則
- 對接口編程而不是對實現編程
- 優先使用對象組合而不是繼承
---單例模式
單體是一個用來划分命名空間並將一批相關的屬性和方法組織在一起的對象,如果他可以被實例化,那么他只能被實例化一次
// 對象字面量
var Singleton = {
attr1: 1,
attr2: 2,
method1: function(){
return this.attr1;
},
method2: function(){
return this.attr2;
}
};
// 上面的所有成員變量都是通過Singleton來訪問的,但是它並不是單體模式;
// 因為單體模式還有一個更重要的特點,就是可以僅被實例化一次,上面的只是不能被實例化的一個類,因此不是單體模式;對象字面量是用來創建單體模式的方法之一;
/*要實現一個單體模式的話,我們無非就是使用一個變量來標識該類是否被實例化
如果未被實例化的話,那么我們可以實例化一次,否則的話,直接返回已經被實例化的對象
*/
// 單體模式
var Singleton = function(name){
this.name = name;
this.instance = null;
};
Singleton.prototype.getName = function(){
return this.name;
}
// 獲取實例對象
function getInstance(name) {
if(!this.instance) {
this.instance = new Singleton(name);
}
return this.instance;
}
// 測試單體模式的實例
var a = getInstance("aa");
var b = getInstance("bb");
console.log(a === b) // true
console.log(a.getName()) // aa
console.log(b.getName()) // aa
應用案例
-
彈窗
傳統創建:比如我點擊一個元素需要創建一個div,我點擊第二個元素又會創建一次div,我們頻繁的點擊某某元素,他們會頻繁的創建div的元素,雖然當我們點擊關閉的時候可以移除彈出代碼,但是呢我們頻繁的創建和刪除並不好,特別對於性能會有很大的影響,對DOM頻繁的操作會引起重繪等,從而影響性能;因此這是非常不好的習慣;我們現在可以使用單體模式來實現彈窗效果,我們只實例化一次就可以
編寫通用的單體模式
我們使用一個參數fn傳遞進去,如果有result這個實例的話,直接返回,否則的話,當前的getInstance函數調用fn這個函數,是this指針指向與這個fn這個函數;之后返回被保存在result里面;現在我們可以傳遞一個函數進去,不管他是創建div也好,還是創建iframe也好,總之如果是這種的話,都可以使用getInstance來獲取他們的實例對象;
// 創建div
var createWindow = function(){
var div = document.createElement("div");
div.innerHTML = "我是彈窗內容";
div.style.display = 'none';
document.body.appendChild(div);
return div;
};
// 創建iframe
var createIframe = function(){
var iframe = document.createElement("iframe");
document.body.appendChild(iframe);
return iframe;
};
// 獲取實例的封裝代碼
var getInstance = function(fn) {
var result;
return function(){
return result || (result = fn.call(this,arguments));
}
};
// 測試創建div
var createSingleDiv = getInstance(createWindow);
document.getElementById("Id").onclick = function(){
var win = createSingleDiv();
win.style.display = "block";
};
// 測試創建iframe
var createSingleIframe = getInstance(createIframe);
document.getElementById("Id").onclick = function(){
var win = createSingleIframe();
win.src = "http://cnblogs.com";
};
---以下為補充
---工廠模式
客戶類和工廠類分開。消費者任何時候需要某種產品,只需向工廠請求即可。消費者無須修改就可以接納新產品。
工廠模式是為了解決多個類似對象聲明的問題;也就是為了解決實列化對象產生重復的問題。
- 優點:能解決多個相似的問題。
- 缺點:
- 不能知道對象識別的問題(對象的類型不知道)。
- 當產品修改時,工廠類也要做相應的修改。
function CreatePerson(name,age,sex) {
var obj = new Object();
obj.name = name;
obj.age = age;
obj.sex = sex;
obj.sayName = function(){
return this.name;
}
return obj;
}
var p1 = new CreatePerson("longen",'28','男');
var p2 = new CreatePerson("tugenhua",'27','女');
---模塊模式
模塊模式的思路是為單體模式添加私有變量和私有方法能夠減少全局變量的使用
prototype + constructor
---裝飾者模式
裝飾者(decorator)模式能夠在不改變對象自身的基礎上,在程序運行期間給對像動態的添加職責(方法或屬性)。
與繼承相比,裝飾者是一種更輕便靈活的做法。
可以動態的給某個對象添加額外的職責,而不會影響從這個類中派生的其它對象。
// ES7裝飾器
function isAnimal(target) {
target.isAnimal = true
return target
}
// 裝飾器
@isAnimal
class Cat {
// ...
}
console.log(Cat.isAnimal) // true
// 作用於類屬性的裝飾器:
function readonly(target, name, descriptor) {
discriptor.writable = false
return discriptor
}
class Cat {
@readonly
say() {
console.log("meow ~")
}
}
var kitty = new Cat()
kitty.say = function() {
console.log("woof !")
}
kitty.say() // meow ~
---觀察者模式(發布-訂閱)
發布---訂閱模式又叫觀察者模式,它定義了對象間的一種一對多的關系,讓多個觀察者對象同時監聽某一個主題對象,當一個對象發生改變時,所有依賴於它的對象都將得到通知
發布訂閱模式的流程如下:
- 確定誰是發布者(比如我的博客)。
- 然后給發布者添加一個緩存列表,用於存放回調函數來通知訂閱者。
- 發布消息,發布者需要遍歷這個緩存列表,依次觸發里面存放的訂閱者回調函數。
- 退訂(比如不想再接收到這些訂閱的信息了,就可以取消掉)
【實現事件模型】
即寫一個類或是一個模塊,有兩個函數,一個bind一個trigger,分別實現綁定事件和觸發事件,核心需求就是可以對某一個事件名稱綁定多個事件響應函數,然后觸發這個事件名稱時,依次按綁定順序觸發相應的響應函數。
大致實現思路就是創建一個類或是匿名函數,在bind和trigger函數外層作用域創建一個字典對象,用於存儲注冊的事件及響應函數列表,bind時,如果字典沒有則創建一個,key是事件名稱,value是數組,里面放着當前注冊的響應函數,如果字段中有,那么就直接push到數組即可。trigger時調出來依次觸發事件響應函數即可
var Event = (function(){
var list = {},
listen,
trigger,
remove;
listen = function(key,fn){
if(!list[key]) {
list[key] = [];
}
list[key].push(fn);
};
trigger = function(){
var key = Array.prototype.shift.call(arguments),
fns = list[key];
if(!fns || fns.length === 0) {
return false;
}
for(var i = 0, fn; fn = fns[i++];) {
fn.apply(this,arguments);
}
};
remove = function(key,fn){
var fns = list[key];
if(!fns) {
return false;
}
if(!fn) {
fns && (fns.length = 0);
}else {
for(var i = fns.length - 1; i >= 0; i--){
var _fn = fns[i];
if(_fn === fn) {
fns.splice(i,1);
}
}
}
};
return {
listen: listen,
trigger: trigger,
remove: remove
}
})();
// 測試代碼如下:
Event.listen("color",function(size) {
console.log("尺碼為:"+size); // 打印出尺碼為42
});
Event.trigger("color",42);
---代理模式
代理是一個對象,它可以用來控制對本體對象的訪問,它與本體對象實現了同樣的接口,代理對象會把所有的調用方法傳遞給本體對象的
本地對象注重的去執行頁面上的代碼,代理則控制本地對象何時被實例化,何時被使用
優點:
- 代理對象可以代替本體被實例化,並使其可以被遠程訪問;
- 它還可以把本體實例化推遲到真正需要的時候;對於實例化比較費時的本體對象,或者因為尺寸比較大以至於不用時不適於保存在內存中的本體,我們可以推遲實例化該對象;
// 先申明一個奶茶妹對象
var TeaAndMilkGirl = function(name) {
this.name = name;
};
// 這是京東ceo先生
var Ceo = function(girl) {
this.girl = girl;
// 送結婚禮物 給奶茶妹
this.sendMarriageRing = function(ring) {
console.log("Hi " + this.girl.name + ", ceo送你一個禮物:" + ring);
}
};
// 京東ceo的經紀人是代理,來代替送
var ProxyObj = function(girl){
this.girl = girl;
// 經紀人代理送禮物給奶茶妹
this.sendGift = function(gift) {
// 代理模式負責本體對象實例化
(new Ceo(this.girl)).sendMarriageRing(gift);
}
};
// 初始化
var proxy = new ProxyObj(new TeaAndMilkGirl("奶茶妹"));
proxy.sendGift("結婚戒"); // Hi 奶茶妹, ceo送你一個禮物:結婚戒
- TeaAndMilkGirl 是一個被送的對象(這里是奶茶妹);
- Ceo 是送禮物的對象,他保存了奶茶妹這個屬性,及有一個自己的特權方法sendMarriageRing 就是送禮物給奶茶妹這么一個方法;
- 然后呢他是想通過他的經紀人去把這件事完成,於是需要創建一個經濟人的代理模式,名字叫ProxyObj ;
- 他的主要做的事情是,把ceo交給他的禮物送給ceo的情人,因此該對象同樣需要保存ceo情人的對象作為自己的屬性,同時也需要一個特權方法sendGift ,該方法是送禮物,因此在該方法內可以實例化本體對象,這里的本體對象是ceo送花這件事情,因此需要實例化該本體對象后及調用本體對象的方法(sendMarriageRing).
理解使用虛擬代理實現圖片的預加載
在網頁開發中,圖片的預加載是一種比較常用的技術,如果直接給img標簽節點設置src屬性的話,如果圖片比較大的話,或者網速相對比較慢的話,那么在圖片未加載完之前,圖片會有一段時間是空白的場景,這樣對於用戶體驗來講並不好,那么這個時候我們可以在圖片未加載完之前我們可以使用一個loading加載圖片來作為一個占位符,來提示用戶該圖片正在加載,等圖片加載完后我們可以對該圖片直接進行賦值即可;下面我們先不用代理模式來實現圖片的預加載的情況下代碼如下:
// 不使用代理的預加載圖片函數如下
var myImage = (function(){
var imgNode = document.createElement("img");
document.body.appendChild(imgNode);
var img = new Image();
img.onload = function(){
imgNode.src = this.src;
};
return {
setSrc: function(src) {
imgNode.src = "http://img.lanrentuku.com/img/allimg/1212/5-121204193Q9-50.gif";
img.src = src;
}
}
})();
// 調用方式
myImage.setSrc("https://img.alicdn.com/tps/i4/TB1b_neLXXXXXcoXFXXc8PZ9XXX-130-200.png");
//利用代理模式來編寫預加載圖片
var myImage = (function(){
var imgNode = document.createElement("img");
document.body.appendChild(imgNode);
return {
setSrc: function(src) {
imgNode.src = src;
}
}
})();
// 代理模式
var ProxyImage = (function(){
var img = new Image();
img.onload = function(){
myImage.setSrc(this.src);
};
return {
setSrc: function(src) {
myImage.setSrc("http://img.lanrentuku.com/img/allimg/1212/5-121204193Q9-50.gif");
img.src = src;
}
}
})();
// 調用方式
ProxyImage.setSrc("https://img.alicdn.com/tps/i4/TB1b_neLXXXXXcoXFXXc8PZ9XXX-130-200.png");
這種懶加載方法不用代理模式也是可以實現的,只是用代理模式。我們可以讓 myImage 只做一件事,只負責將實際圖片加入到頁面中,而loading圖片交給ProxyImage去做。從而降低代碼的耦合度。因為當我不想用loading的時候,可以直接調用myImage 方法。也即是說假如我門不需要代理對象的話,直接可以換成本體對象調用該方法即可
對比
- 不代理:不滿足單一職責原則,代碼耦合度高
- myimage 函數只負責一件事,其他交給代理
優點
- 用戶可以放心地請求代理,他們只關心是否能得到想要的結果。假如我門不需要代理對象的話,直接可以換成本體對象調用該方法即可。
- 在任何使用本體對象的地方都可以替換成使用代理。
1.2. Online Coding
js 實現兩個超大數相加
基礎:
- JS 中所有的數字類型,實際存儲都是通過 8 字節 double 浮點型 表示的,並不是能夠精確表示范圍內的所有數
- 大整數存儲(安全使用范圍)
- 其他語言
2^63 - 1
- js
Math.pow(2, 53) - 1
//js 最大和最小安全值 Number.MAX_SAFE_INTEGER //9007199254740991 Number.MIN_SAFE_INTEGER //-9007199254740991
- 其他語言
var largeNumberAdd = function(num1, num2) {
var arr1 = num1.split(''),
arr2 = num2.split(''),
tem = '',
num3 = 0,
result = []
var longDiff = arr1.length - arr2.length
if (longDiff > 0) {
for (let i = 0; i < longDiff; i++) {
arr2.unshift('0')
}
}else if (longDiff < 0) {
for (let i = 0; i < Math.abs(longDiff); i++) {
arr1.unshift('0')
}
}
for (let i = arr1.length - 1; i >= 0; i--) {
tem = parseInt(arr1[i]) + parseInt(arr2[i]) + num3
// check if tem > 10
if (tem >= 10) {
num3 = 1
result.push((tem + '')[1])
}else {
num3 = 0
result.push(tem)
}
}
return result.reverse().join('')
}
// console.log(largeNumberAdd('11111','11111'))
console.log(largeNumberAdd('00000000000000000000011111','333331999'))
console.log(11111+333331999)
// console.log(largeNumberAdd('3333333333333333333333333333333311111111111111111111111111111111111111','333333333333333331111111111111111111111111111166666666666666'))
js 每秒鍾的計算量
js 如何解析后台返回的超大數據
前提:
-
js 用浮點數表示所有64位數字,所有達到 2^53 的可以被精確表示,更大的數字都會被裁剪,——如何表示64位數字
雖然js 能夠解析進制數字表示64位數字,但底層的數字表示不支持 64 位
在瀏覽器中執行以下代碼<html> <head> <script language="javascript"> function showPrecisionLimits() { document.getElementById("r50").innerHTML = 0x0004000000000001 - 0x0004000000000000; document.getElementById("r51").innerHTML = 0x0008000000000001 - 0x0008000000000000; document.getElementById("r52").innerHTML = 0x0010000000000001 - 0x0010000000000000; document.getElementById("r53").innerHTML = 0x0020000000000001 - 0x0020000000000000; document.getElementById("r54").innerHTML = 0x0040000000000001 - 0x0040000000000000; } </script> </head> <body onload="showPrecisionLimits()"> <p>(2^50+1) - (2^50) = <span id="r50"></span></p> <p>(2^51+1) - (2^51) = <span id="r51"></span></p> <p>(2^52+1) - (2^52) = <span id="r52"></span></p> <p>(2^53+1) - (2^53) = <span id="r53"></span></p> <p>(2^54+1) - (2^54) = <span id="r54"></span></p> </body> </html>
在Firefox,Chrome和IE瀏覽器中,可以看到,如果能夠存儲64位數字,則以下減法結果皆為1。而結果相反,可以看到2 ^ 53 + 1和2 ^ 53 間的差異丟失
(2 ^ 50 + 1) - (2 ^ 50)= 1 (2 ^ 51 + 1) - (2 ^ 51)= 1 (2 ^ 52 + 1) - (2 ^ 52)= 1 (2 ^ 53 + 1) - (2 ^ 53)= 0 (2 ^ 54 + 1) - (2 ^ 54)= 0
位運算
因此,我們可以選擇用兩個 32 位的數字表示 64 位整數,然后進行按位與var a = [ 0x0000ffff, 0xffff0000 ]; var b = [ 0x00ffff00, 0x00ffff00 ]; var c = [ a[0] & b[0], a[1] & b[1] ]; document.body.innerHTML = c[0].toString(16) + ":" + c[1].toString(16); //結果 ff00:ff0000
網絡安全
前端網絡安全的實現
- 保證http傳輸安全性
如何檢測惡意腳本
如何屏蔽
2. 阿里
一面
- 簡單介紹一下自己
- 問題
- HTTP 相關
HTTP 與 HTTPS 區別,HTTPS 原理,如何加密 - 談談閉包
- 談談作用域鏈
- 常用的跨域方式
- vue
- 特點
- 生命周期
- vuex
實現原理(事件綁定)-如何實現 - 與 react 的不同
應用場景 - 如何實現雙向綁定
- 如何通信
- HTTP 相關
- 其他
- 還會哪些語言
- 還有什么感覺沒問到但掌握很好的
- 是否還有其他的問題
- 有什么問題想要問的
二面
- react
多次調用 setstate 為什么不馬上渲染 - 解析 json ,手寫 parse 函數
- csrf 舉例
- 獲取dom節點方式
- 將給定節點子元素第一個和最后一個元素替換
- 端口號的作用
端口
IP地址讓網絡上的兩個節點之間可以建立點對點的連接
端口號則為端到端的連接提供了可能 (程序間通訊的接口)
IP協議是由TCP、UDP、ARP、ICMP等一系列子協議組成的。其中
-
TCP和UDP協議
主要用來做傳輸數據使用的
在TCP和UDP協議中,都有端口號的概念存在
-
端口號的作用
主要是區分服務類別和在同一時間進行多個會話
-
服務類別
-
舉例來說,有主機A需要對外提供FTP和WWW兩種服務,如果沒有端口號存在的話,這兩種服務是無法區分的。
實際上,當網絡上某主機B需要訪問A的FTP服務時,就要指定目的端口號為21;
當需要訪問A的WWW服務時,則需要將目的端口號設為80,這時A根據B訪問的端口號,就可以區分B的兩種不同請求
-
-
多個會話
- 主機A需要同時下載網絡上某FTP服務器B上的兩個文件,那么A需要 與B同時建立兩個會話,而這兩個傳輸會話就是靠源端口號來區分的。在這種情況下如果沒有源端口號的概念,那么A就無法區分B傳回的數據究竟是屬於哪個會話,屬於哪個文件
- 通信過程是,A使用本機的1025號端口請求B的21號端口上的文件1,同時又使用1026號端口請求文件2。對於返回的數據,發現是傳回給1025號端口的,就認為是屬於文件1;傳回給1026號端口的,則認為是屬於文件2。這就是端口號區分多個會話的作用。
-
在線編程——編寫一個 css 層級選擇器
根據一個給定的元素生成一個css 選擇器,函數名為genCssSelector ,
點擊某元素彈出該元素及其父元素,類似 querySelector
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Document</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script language="javaScript">
// your code here
var genCssSelector = function (e) {
e = e || window.event
var tar = e.target || e.srcElement
var objArr = []
while (tar) {
if (tar.id) {
objArr.push('#' + tar.id)
console.log('id')
return objArr.reverse().join(' ') // 考慮 id 的唯一性,如果有 id,則停止查找
}else if (tar.className) {
objArr.push('.' + tar.className.split(' ')[0]) // 考慮如果有多個 class
}else {
objArr.push(tar.nodeName.toLowerCase())
}
tar = tar.parentNode
}
objArr.pop()
return objArr.reverse().join(' ')
}
document.addEventListener('click', function (e) {
//點擊li時,返回:html body #page .content.main .refer ul li
console.log(genCssSelector(e));
})
</script>
</head>
<body>
<div id="page">
<div class="main" id="main">
<div class="reference refer">
<ul>
<li></li>
<li></li>
23333
</ul>
</div>
</div>
</div>
</body>
</html>
獲取 dom 元素
JS獲取DOM元素的方法(8種)
-
getElementById
只獲取到一個元素,沒有找到返回null
-
getElementsByName
-
getElementsByTagName
-
getElementsByClassName
-
document.documentElement
獲取html
-
document.body
獲取body
-
querySelector
獲取一個元素
-
querySelectorAll
獲取一組元素
獲取子元素
- childNodes
dom.childNodes
返回一個nodeList(元素的所有子元素)-
nodeType
- 元素節點的nodeType屬性值為1
- 屬性節點的nodeType屬性值為2
- 文本節點的nodeType屬性值為3
-
nodeValue屬性
獲得和改變文本節點的值
-
- firstChild 第一個子元素
- lastChild
獲取父、兄
parentNode
nextSibling
previousSbiling
創建元素
-
createDocumentFragment
創建一個dom片段
-
createElement
創建一個具體的元素
-
createTextNode
創建一個文本節點
增刪改元素
appendChild
removeChild
replaceChild
insertBefore
如何用 JS 實現 JSON.parse
---eval
直接調用eval
var json = '{"a":"1", "b":2}';
var obj = eval("(" + json + ")"); // obj 就是 json 反序列化之后得到的對象
原理
JSON 脫胎於 JS,同時也是 JS 的子集,所以能夠直接交給 eval 運行
-
加上圓括號的目的是迫使eval函數在處理JavaScript代碼的時候強制將括號內的表達式(expression)轉化為對象,而不是作為語句(statement)來執行
例如對象字面量{},如若不加外層的括號,那么eval會將大括號識別為JavaScript代碼塊的開始和結束標記,那么{}將會被認為是執行了一句空語句
缺點
-
XSS 漏洞
如:參數 json 並非真正的 JSON 數據,而是可執行的 JS 代碼
-
對參數 json 做校驗,只有真正符合 JSON 格式,才能調用 eval
// 1. 用 4 個正則表達式分為兩個階段解決(包容ie 和safari 的regexp 引擎) // 2. 將 json 反斜杠替換為 '@' (non-json字符) // 3. 用 ']' 替換所有簡單標記 // 4. 刪除所有跟隨冒號,逗號或文本開始的方括號 // 5. 如果只剩下 '] , { }' 則是安全的 var rx_one = /^[\],:{}\s]*$/; var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g; var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g; var rx_four = /(?:^|:|,)(?:\s*\[)+/g; if ( rx_one.test( json .replace(rx_two, "@") .replace(rx_three, "]") .replace(rx_four, "") ) ) { var obj = eval("(" +json + ")"); }
---遞歸
第一種 eval 的方法,相當於一股腦兒把 JSON 字符串塞進去。
其實我們還可以手動逐個字符地掃描,然后進行判斷,這就是第二種方法:遞歸
// 所謂遞歸,就是重復調用value 函數
value = function () {
// Parse a JSON value. It could be an object, an array, a string, a number,
// or a word.
white();
// 根據當前字符是什么,我們便能推導出后面應該接的是什么類型
switch (ch) {
case "{":
return object();
case "[":
return array();
case "\"":
return string();
case "-":
return number();
default:
return (ch >= "0" && ch <= "9")
? number()
: word();
}
};
// 調用核心的 next 函數,逐個讀取字符
var next = function (c) {
// If a c parameter is provided, verify that it matches the current character.
if (c && c !== ch) {
error("Expected '" + c + "' instead of '" + ch + "'");
}
// Get the next character. When there are no more characters,
// return the empty string.
ch = text.charAt(at);
at += 1;
return ch;
};
- 對於常量token
false true null
進行匹配,不匹配返回錯誤
以 {"a":"1", "b":2}
為例
程序大致邏輯是:啟動 → 首次調用 value() → 發現是 { → 原來是對象,走 object() → 通過 string() 得到 key 值為 "a" → 讀取到冒號,哦,后面可能是對象、數組、布爾值等等,具體是什么,還得再次調用 value() 才知道 → ……
xml 解析
-
close tag
使用一個 nodeStack 棧,在 opentag 時推入節點,close tag 時檢查當前節點是否和棧尾節點是否匹配,匹配則推出末尾節點
-
comment
react setState
-
setState 不保證同步
- 可能會為了性能收益批量執行
setState()
不會立刻改變this.state
,而是創建一個即將處理的 state 轉變。在調用該方法之后訪問this.state
可能會返回現有的值。
解決方案
-
使用回調函數
setState 方法接收一個 function 作為回調函數。這個回掉函數會在 setState 完成以后直接調用,這樣就可以獲取最新的 state
this.setState({ selection: value }, this.fireOnSelect)
-
setTimeout
在 setState 使用 setTimeout 來讓 setState 先完成以后再執行里面內容this.setState({ selection: value }); setTimeout(this.fireOnSelect, 0);
-
造成不必要的渲染
-
shouldComponentUpdate
解決setState()
將總是觸發一次重繪,除非在shouldComponentUpdate()
中實現了條件渲染邏輯 -
和渲染無關的狀態盡量不要放在 state 中來管理
通常 state 中只來管理和渲染有關的狀態 ,從而保證 setState 改變的狀態都是和渲染有關的狀態。這樣子就可以避免不必要的重復渲染。其他和渲染無關的狀態,可以直接以屬性的形式保存在組件中,在需要的時候調用和改變,不會造成渲染。
-
-
不能很有效的管理所有的組件狀態
3. 百度
一面
jsonp cors
css
- 分欄布局哪些方式
- 詳細說下flex
js & jq
- 如何獲取一個元素(js jq)
- 異步事件是如何發送的,常用機制
- 有哪些接收后台數據的方法
- ajax
- fetch
- jsonp
- websocket
- SSE
- event loop
- 喜歡 es6 的哪些屬性
- 箭頭函數與普通函數的不同
- 閉包
- 簡述
- 應用
- 在循環中如何用其他方式代替閉包
vue
- 和 react 區別
- 如何向服務器傳遞數據
操作系統
- 線程 && 進程
計算機網絡
- 除了tcp 還用到哪些
- http 與 tcp 分別屬於第幾層
數據結構
- 有哪些線性存儲空間(array 棧)
二面
-
如何做一個 css 選擇器
見本文 阿里-在線編程
-
給定一組dom 節點,和一個css樣式表,找出不含有樣式的dom
面試官很耐心的解釋了,還是沒聽明白題目
-
鏈表操作
- 單向鏈表反轉
-
居中問題
沒有詳細問,我分了兩個方面分別回答
- 水平居中
- 垂直水平
詳細見 CSS 居中
-
get & post
回答了大多數應聘者的 “標准答案”, 但經面試官指點,頓悟,大概這就叫高手吧
三面
-
數字的存儲
-
正負數在計算機中是如何存儲的
-
無符號與有符號二進制存儲
-
-
前端的編碼問題
- 種類
- 亂碼如何處理
-
有哪些協議,分別有什么作用
-
關於實習經歷,找實習的過程,項目中的二三事及解決方案
-
大學中最有成就感的事
-
閑聊了一下,關於保研等等,總共 四十分鍾
分欄布局
---等分布局
-
float
-
原理:增大父框的實際寬度后,使用CSS3屬性box-sizing進行布局的輔助。
-
用法:先將父框設置為
margin-left: -*px
,再設置子框float: left、width: 25%、padding-left、box-sizing: border-box
.parent{ margin-left: -20px; } .column{ float: left; width: 25%; padding-left: 20px; box-sizing: border-box; /*包含padding區域 w+g*/ }
-
-
table
-
原理:通過增加一個父框的修正框,增大其寬度,並將父框轉換為 table,將子框轉換為 tabel-cell 進行布局。
-
用法:先將父框的修正框設置為
margin-left: -*px
,再設置父框display: table、width:100%、table-layout: fixed
,設置子框display: table-cell、padding-left
.parent-fix{ margin-left: -20px; } .parent{ display: table; width:100%; table-layout: fixed; } .column{ display: table-cell; padding-left: 20px; }
-
-
flex
-
原理:通過設置CSS3布局利器flex中的flex屬性以達到等分布局。
-
用法:將父框設置為display: flex,再設置子框flex: 1,最后設置子框與子框的間距margin-left。
-
如何獲取一個class
// 直接獲取---需要高版本瀏覽器支持
document.querySelectorAll("div.aa")
// 類似屬性選擇器的寫法
document.querySelectorAll("div[class='aa']")
// 補充一下還可以not選擇器
document.querySelectorAll(".aa:not(ul)")
document.getElementsByClassName('cls')
// jq
$('.className')
Event Loop
js 單線程:
用途決定,操作 DOM
任務隊列
排隊原因:計算量大的同步執行,IO設備(輸入輸出設備)很慢(比如Ajax操作從網絡讀取數據)異步。
異步任務指的是,不進入主線程、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程,某個異步任務可以執行了,該任務才會進入主線程執行
"任務隊列"中的事件,除了IO設備的事件以外,還包括一些用戶產生的事件(比如鼠標點擊、頁面滾動等等)。只要指定過回調函數,這些事件發生時就會進入"任務隊列",等待主線程讀取。
-
所有同步任務都在主線程上執行,形成一個執行棧(execution context stack)。
-
主線程之外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。
-
一旦"執行棧"中的所有同步任務執行完畢,系統就會讀取"任務隊列",看看里面有哪些事件。那些對應的異步任務,於是結束等待狀態,進入執行棧,開始執行。
-
主線程不斷重復上面的第三步。
只要主線程空了,就去檢查異步的任務隊列,如果異步事件觸發,則將其加到主線程的執行棧
深入了解定時器
-
零延遲
setTimeout(func, 0)
零延遲並不是意味着回調函數立刻執行。它取決於主線程當前是否空閑與“任務隊列”里其前面正在等待的任務。
-
調用setTimeout()之后,該方法會返回一直數值ID,表示超時調用。這個超時調用ID是計划執行代碼的唯一標識符,可以通過它來取消超時調用
-
超時調用的代碼都是在全局作用域中執行的,因此函數中this的值在非嚴格模式下指向window對象,嚴格模式下是undefined。
Event Loop
異步與event loop沒有太直接的關系,准確的來講event loop 只是實現異步的一種機制
主任務 ——> micro task ——> 渲染視圖 ——> macro task
主線程從"任務隊列"中讀取事件,這個過程是循環不斷的,所以整個的這種運行機制又稱為Event Loop(事件循環)
Javascript 中的事件循環是以任務為單位的,將很多個待執行的任務串聯在一起就形成了隊列 Task Queue,很多的隊列先后按順序執行任務就形成了 Event Loop
一個事件循環(EventLoop)中會有一個正在執行的任務(Task),而這個任務就是從 macrotask 隊列中來的。
當這個 macrotask 執行結束后,所有可用的 microtask 將會在同一個事件循環中執行
當這些 microtask 執行結束后還能繼續添加 microtask 一直到真個 microtask 隊列執行結束。
-
一個事件循環(event loop)會有一個或多個任務隊列(task queue)
task queue 就是 macrotask queue
-
每一個 event loop 都有一個 microtask queue
-
task queue == macrotask queue != microtask queue
-
一個任務 task 可以放入 macrotask queue 也可以放入 microtask queue 中
-
當一個 task 被放入隊列 queue(macro或micro) 那這個 task 就可以被立即執行了
Micro Task
當我們想以同步的方式來處理異步任務時候就用 microtask(比如我們需要直接在某段代碼后就去執行某個任務,就像Promise一樣)
- process.nextTick
- promise
- Object.observe
- MutationObserver
Macro Task
- setTimeout
- setInterval
- setImmediate
- I/O
任務隊列中,在每一次事件循環中,從 macrotask 隊列開始執行,macrotask只會提取一個執行,而microtask會一直提取,直到microsoft隊列為空為止。
也就是說如果某個microtask任務被推入到執行中,那么當主線程任務執行完成后,會循環調用該隊列任務中的下一個任務來執行,直到該任務隊列到最后一個任務為止。而事件循環每次只會入棧一個macrotask,主線程執行完成該任務后又會檢查microtasks 隊列並完成里面的所有任務后再執行macrotask的任務。
執行過程如下:
- 主線程空閑時首先執行 micro
- 之后從 macro 中提取一個 task 到主任務,完成后再次執行 micro queue(執行一個cycle)
- 反復過程2, 每個周期為一個事件循環
為啥要用 microtask?
- micro 執行總在 macro 之前
- micro 全部執行完畢后會更新 UI 和執行下一個macro
根據HTML Standard,在每個 task 運行完以后,UI 都會重渲染,那么在 microtask 中就完成數據更新,當前 task 結束就可以得到最新的 UI 了
// 驗證
(function () {
const $test = document.getElementById('test')
let counter = 0
function func1() {
$test.innerText = ++counter
alert('func1')
}
function func2() {
$test.innerText = ++counter
alert('func2')
}
function func3() {
$test.innerText = ++counter
alert('func3')
}
function func4() {
$test.innerText = ++counter
alert('func4')
}
(function () {
// main task
func1()
// macro task
setTimeout(() => {
func2()
// micro task
Promise.resolve().then(func4)
}, 0);
// macro task
setTimeout(func1, 0);
// micro task
Promise.resolve().then(func3)
// main task
func4()
})()
// alert func1
// alert func4
// alert func3
// UI update ---> counter = 3
// alert func2
// alert func4
// UI update ---> counter = 5
// alert func1
// UI update ---> counter = 6
})()
接收后台資源的方法(除了Ajax)
-
Ajax
-
fetch
返回一個Promise對象, 根據 Promise Api 的特性, fetch可以方便地使用then方法將各個處理邏輯串起來mode
// fetch可以設置不同的模式使得請求有效 fetch(url, {mode: 'cors'});
- same-origin
- cors
- cors-with-forced-preflight
- no-cors
推薦閱讀: Fetch 進階指南
-
Jsonp
-
websocket
服務器推送技術之一
全雙工 -
SSE(server-sent-events)
單向通道(服務器 -> 瀏覽器)
異步編程常用方法
ES 6以前:
- 回調函數
- 事件監聽(事件發布/訂閱)
- Promise對象
ES 6:
- Generator函數(協程coroutine)
ES 7:
- async和await
回調函數
一般是需要在一個耗時操作之后執行某個操作時可以使用回調函數
- 定時器
- 讀取文件
問題:
在回調函數之外無法捕獲到回調函數中的異常
var fs = require('fs');
try{
fs.readFile('not_exist_file', 'utf8', function(err, data){
console.log(data);
});
}
catch(e){
console.log("error caught: " + e);
}
嘗試讀取一個不存在的文件,這當然會引發異常,但是最外層的try/catch語句卻無法捕獲這個異常。這是異步代碼的執行機制導致的
為什么異步代碼回調函數中的異常無法被最外層的try/catch語句捕獲?
異步調用一般分為兩個階段,提交請求和處理結果,這兩個階段之間有事件循環的調用,它們屬於兩個不同的事件循環(tick),彼此沒有關聯。
異步調用一般以傳入callback的方式來指定異步操作完成后要執行的動作。而異步調用本體和callback屬於不同的事件循環。
try/catch語句只能捕獲當次事件循環的異常,對callback無能為力。
事件監聽(訂閱-發布)
典型的邏輯分離方式,對代碼解耦很有用處
把不變的部分封裝在組件內部,供外部調用,需要自定義的部分暴露在外部處理。
從某種意義上說,事件的設計就是組件的接口設計。
//發布和訂閱事件
var events = require('events');
var emitter = new events.EventEmitter();
emitter.on('event1', function(message){
console.log(message);
});
emitter.emit('event1', "message for you");
Promise 對象
用同步操作的流程寫法來表達異步操作,避免了層層嵌套的異步回調
-
Promise.prototype.then()
//原生Primose順序嵌套回調示例 var fs = require('fs') var read = function (filename){ var promise = new Promise(function(resolve, reject){ fs.readFile(filename, 'utf8', function(err, data){ if (err){ reject(err); } resolve(data); }) }); return promise; } read('./text1.txt') .then(function(data){ console.log(data); return read('./text2.txt'); // 返回了一個新的Promise實例 }) .then(function(data){ console.log(data); });
Promise構造函數的參數是一個函數,在這個函數中我們寫異步操作的代碼
在異步操作的回調中,根據err變量來選擇是執行resolve方法還是reject方法- 一般來說調用resolve方法的參數是異步操作獲取到的數據(如果有的話),但還可能是另一個Promise對象,表示異步操作的結果有可能是一個值
- 也有可能是另一個異步操作,調用reject方法的參數是異步回調用的err參數
調用read函數時,實際上返回的是一個Promise對象,通過在這個Promise對象上調用then方法並傳入resolve方法和reject方法來指定異步操作成功和失敗后的操作。
-
Promise.prototype.catch()
用於指定發生錯誤時的回調函數
read('./text1.txt') .then(function(data){ console.log(data); return read('not_exist_file'); }) .then(function(data){ console.log(data); }) .catch(function(err){ console.log("error caught: " + err); }) .then(function(data){ console.log("completed"); })
使用Promise對象的catch方法可以捕獲異步調用鏈中callback的異常
Promise對象的catch方法返回的也是一個Promise對象,因此,在catch方法后還可以繼續寫異步調用方法 -
Promise異步並發
-
Promise.all()
- 將多個Promise實例,包裝成一個新的Promise實例
var p = Promise.all([p1,p2,p3]);
- 接受一個數組作為參數,p1、p2、p3都是Promise對象實例
- 只有p1、p2、p3的狀態都變成fulfilled,p的狀態才會變成fulfilled,此時p1、p2、p3的返回值組成一個數組,傳遞給p的回調函數。
- 只要p1、p2、p3之中有一個被rejected,p的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。
var promises = [1, 2].map(function(fileno){ return read('./text' + fileno + '.txt'); }); Promise.all(promises) .then(function(contents){ console.log(contents); }) .catch(function(err){ console.log("error caught: " + err); })
- 將多個Promise實例,包裝成一個新的Promise實例
-
Promise.race()
- 將多個Promise實例,包裝成一個新的Promise實例
var p = Promise.race([p1,p2,p3]);
- p1、p2、p3只要有一個實例率先改變狀態,p的狀態就會跟着改變,那個率先改變的Promise實例的返回值,就傳遞給p的返回值。
- 如果Promise.all方法和Promise.race方法的參數不是Promise實例,就會先調用下面講到的Promise.resolve方法,將參數轉為Promise實例,再進一步處理
- 將多個Promise實例,包裝成一個新的Promise實例
-
Promise.resolve()
- 將現有對象轉換成Promise對象
var p = Promise.resolve('Hello'); p.then(function (s){ console.log(s) });
-
Promise.reject()
- 返回一個新的Promise實例,該實例的狀態為rejected。
Promise.reject
方法的參數reason,會被傳遞給實例的回調函數。
var p = Promise.reject('出錯了'); p.then(null, function (s){ console.log(s) });
-
-
Generator函數
-
可以交出函數的執行權(暫停執行)
-
整個Generator函數就是一個封裝的異步任務,或者說是異步任務的容器。異步操作需要暫停的地方,都用yield語句注明。
function* gen(x){ var y = yield x + 2; return y; } var g = gen(1); var r1 = g.next(); // { value: 3, done: false } console.log(r1); var r2 = g.next() // { value: undefined, done: true } console.log(r2);
Generator函數的函數名前面有一個"*"
- 調用Generator函數,會返回一個內部指針(即遍歷器)g,這是Generator函數和一般函數不同的地方,調用它不會返回結果,而是一個指針對象。
- 調用指針g的next方法,會移動內部指針,指向第一個遇到的yield語句
- next方法的作用是分階段執行Generator函數。每次調用next方法,會返回一個對象,表示當前階段的信息(value屬性和done屬性)
- value屬性是yield語句后面表達式的值,表示當前階段的值;
- done屬性是一個布爾值,表示Generator函數是否執行完畢,即是否還有下一個階段
-
-
Thunk 函數
Thunk 函數的含義和用法 -
ES 7中的async和await
-
fetch
- 替代瀏覽器原生的XMLHttpRequest異步請求
get & post 剖析
入門回答
區別:
-
get
- 參數包含在 URL 中
- 大小有限制
- 使用Request.QueryString 獲取變量的值
- 安全性差,直觀顯示
-
post
- 通過 request body 傳遞參數
- 通過 Request.Form 獲取變量的值
當我滿心充滿着自信和喜悅時,仿佛看到了面試官眉頭一皺,So?
“標准答案”
aspect | GET | POST |
---|---|---|
瀏覽器回退 | 無影響 | 回退會再次提交請求 |
地址標記 | 產生的 URL 地址可以被 Bookmark | 提交地址不被標記 |
cache | 該請求會被瀏覽器主動 cache | 該請求不會被緩存 |
編碼 | 只能進行url編碼 | 支持多種編碼方式 |
參數保留 | 請求參數會被完整保留在瀏覽器歷史記錄里 | POST中的參數不會被保留 |
長度限制 | 有(瀏覽器限制,IE-2083個字符) | 無(限制作用的是服務器的處理程序的處理能力) |
參數類型 | 只接受ASCII字符 | 沒有限制 |
參數傳遞 | 通過URL傳遞 | 放在Request body中 |
裸奔剖析
前端編碼
字符 | 表示 | 補充 |
---|---|---|
二進制 | 0/1 | 八個二進制位可以組合出256種狀態,這被稱為一個字節(byte) |
八進制 | 0~7 | |
十進制 | 0~9 | |
十六禁止 | 0~9 A~F |
- 8byte = 1bit
- 1024 字節 = 1k
- 1024k = 1M
- 1024M = 1G
- 1024G = 1T
編碼 | 特征 | 補充 |
---|---|---|
二十一進制碼(BCD碼) | 保留了十進制數的權,而數字則用二進制數碼0和1的組合來表示 | 在需要高精度的計算中BCD編碼比較常用(了解) |
ASCII碼 | 美國信息交換標准委員會制定的7位字符編碼,用7位二進制碼表示一個字符,第8 位用於確定附加的128 個特殊符號字符、外來語字母和圖形符號 | |
GB2312 | 為了保存非英文,使用127號之后的空位保存新的字母(一個8位的字節可以組合256種狀態,ASCII只編到127號),一直編到最后一位255,而且不同國家表示的符號也不一樣,也可以說GB2312是對ASCII的中文擴展 | 不夠用,后來只要求只要第一個字節是大於127就固定表示這是一個漢字的開始,稱之為GBK編碼 |
GB18030 / DBCS | 編碼中又增加了幾千個新的少數民族的字,GBK擴展成了GB18030統稱它們叫做DBCS | |
Unicode | ISO(國際標准化組織)廢棄了所有地區性編碼方案,做了一套包括了地球上所有文化、符號以及字母的編碼;ISO規定:必須用兩個字節,16位來統一表示所有的字符,無論是半角的英文字母,還是全角的漢字,它們都是統一的一個字符!也就是兩個字節 | |
UTF-8 | UTF-8 互聯網上使用最廣的一種 Unicode 的實現方式,每次以8個位為單位傳輸數據;UTF-16就是每次 16 個位 | UTF-8 最大的一個特點,就是它是一種變長的編碼方式,Unicode一個中文字符占 2 個字節,而UTF-8一個中文字符占3個字節,UTF-8是Unicode的實現方式之一 |
進制轉換
------十進制轉其他-------
var a = 24;
a.toString(2);//11000
a.toString(8);//30
a.toString(16);//18
------其他轉十進制-------
var b=11000,c=30,d=18;
console.log(parseInt(b, 2)); // 二進制轉十進制
console.log(parseInt(c, 8)); // 八進制轉十進制
console.log(parseInt(d, 16));// 十六進制轉十進制
前端編碼問題
在使用nodeJS編寫前端工具時,對文本文件的操作比較多,這就涉及到了文件的編碼問題,常用的文本編碼有UTF8和GBK兩種,並且UTF8文件還可能帶有BOM(字節順序標記),在讀取不同編碼的文本文件時,需要將文件內容轉換為JS使用的UTF8編碼字符串后才能正常處理
-
移除 BOM
BOM用於標記一個文本文件使用Unicode編碼,其本身是一個Unicode字符("\uFEFF"),位於文本文件頭部以告訴其他編輯器以utf8來顯示字符
但是在網頁上並不需要添加BOM頭識別,因為網頁上可以使用 head頭 指定charset=utf8告訴瀏覽器用utf8來解釋.但是你用window自動的編輯器,編輯,然后有顯示在網頁上這樣就會顯示出0xEF 0xBB 0xBF這3個字符。這樣網頁上就需要去除0xEF 0xBB 0xBF
-
可以使用editplus 選擇不帶BOM的編碼,這樣就可以去除了
-
js 去除
// 可以通過文件頭的幾個字節來判斷文件是否包含BOM以及使用哪種Unicode,如果讀取文件的時候不去掉BOM // 假如我們將幾個JS文件合並成一個,如果文件中含有BOM字符,就會導致語法錯誤 // 所以我們用 nodeJS讀取文件是一般先去掉BOM var bin = fs.readFileSync(pathname);//通過node 中fs模塊同步讀文件內容 //判斷文件頭的字節 if (bin[0] === 0xEF && bin[1] === 0xBB && bin[2] === 0xBF) { bin = bin.slice(3); }
-
-
GBK 轉 UTF8
NodeJS支持在讀取文本文件時,或者在Buffer轉換為字符串時指定文本編碼,但GBK編碼不在NodeJS自身支持范圍內,一般我們借助iconv-lite這個三方包來轉換編碼,首先使用npm下載這個第三方包,讀取GBK文件函數如下:
var iconv = require('iconv-lite'); function readGBKText(pathname) { var myFs = fs.readFileSync(pathname); return iconv.decode(myFs, 'gbk'); }
數據結構
常用的線性結構有:線性表,棧,隊列,循環隊列,數組
線性表中包括順序表、鏈表等,其中:
- 棧和隊列只是屬於邏輯上的概念,實際中不存在,僅僅是一種思想,一種理念;
- 線性表則是在內存中數據的一種組織、存儲的方式。
常見的排序算法及其時間復雜度
其他
- git 與 GitHub 有什么區別
- git 的一些指令
- Linux
計算機網絡
如有不足,歡迎交流,祝各位看官 offer 拿到手軟 O(∩_∩)O
我的博客即將搬運同步至騰訊雲+社區,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan