2020平凡碼農的求生之路 | 掘金技術徵文

前言

第一個在掘金投稿一月份剛找到工做,對於2020年寒冬的程序員求職,感觸頗深,因而決定寫下這篇掘金處女做。2019年12月-2020年1月,40天一共面了13家企業,包括上海螞蟻金服、上海愛樂奇、外包、小公司。先不囉嗦,看面經。
備註~筆者學歷:雙非本科; 筆者崗位:前端; 工做年限:1年; 求職時間:2020年1月;面試地點:上海

面經

Q一、請按本身的理解簡述http 2與http 1.0、http 1.1的區別


HTTP1.0:瀏覽器與web服務器的鏈接過程是短暫的,每次鏈接只處理一個請求和響應,客戶端與web服務器創建鏈接後,只能得到一個web資源。 css

HTTP1.1:在一個tcp鏈接上能夠傳送多個http請求和響應,多個請求和響應過程能夠重疊進行,增長了更多的請求頭和響應頭;容許客戶端與web服務器創建鏈接後,在一個鏈接上獲取多個web資源。HTTP/1.1 中增長了持久鏈接的方法,它的特色是在一個 TCP 鏈接上能夠傳輸多個 HTTP 請求,只要瀏覽器或者服務器沒有明確斷開鏈接,那麼該 TCP 鏈接會一直保持。 html

關於HTTP2,由於瀏覽器會有併發請求限制,在 HTTP / 1.1 時代,每一個請求都須要創建和斷開, 消耗了好幾個 RTT 時間,而且因爲 TCP 慢啓動的緣由,加載體積大的文件會須要更多 的時間。 在 HTTP / 2.0 中引入了多路複用,可以讓多個請求使用同一個 TCP 連接,極大 的加快了網頁的加載速度。而且還支持 Header 壓縮,進一步的減小了請求的數據大小。前端


Q二、除了 Etag,還有哪些緩存手段,哪些是 HTTP1.0 的?


Expires、Cache-Control、Last-Modified、Etag這是前端緩存經常使用4種手段。 Expires首部主要是針對HTTP vue

1.0版本,是響應頭裏的一個頭部,是以日期時間的形式返回給客戶端,指示能夠緩存這個資源(響應實體)直到指定的日期時間。 node

Cache-Control首部是在HTTP 1.1版本之後加入的,提供了細粒度的緩存策略。 react

Last-Modified 在HTTP1.0推出的,指服務器文件的最後修改時間,瀏覽器會帶上If-Modified-Since向服務器發送請求,與服務器文件修改時間Last-Modified作對比,若是時間不一樣,則獲取數據返回200,不然返回304後調用瀏覽器本地硬盤的緩存。 jquery

ETag 相似於文件指紋,在HTTP1.1推出,該版本號是由服務端隨機生成的,瀏覽器會帶上If-None-Match向服務器發送請求,與服務器文件修改版本ETag作對比,若是版本號不一樣,則獲取數據返回200,不然返回304後調用瀏覽器本地硬盤的緩存,這種方式比Last-Modified靠譜。git


Q三、經常使用檢測數據的方法有哪些,爲何typeof null 輸出object,如何正確輸出Null的類型?


instanceof 能夠正確的判斷對象的類型,由於內部機制是經過判斷對象的原型鏈 中是否是能找到類型的 prototype。此外還有typeof。 typeof null 輸出object是由於在JS 的最第一版本中,使用的是32位系統。爲了性能考慮使用低位存儲了變量的類型信息,000開頭表明是對象,然而null 表示爲全零,因此將它錯誤的判斷爲object。 若是咱們想得到一個變量的正確類型,能夠經過0bject. prototype. toString.call(xx)。這樣咱們就能夠得到相似[object Type]的字符串。程序員


Q四、如何解決js編譯速度太慢的問題?


將編譯產物緩存並提供隨機訪問。首先,把安全相關的js文件從靜態服務中剝離出來,由一個後端的webserver輸出js內容。該server上維護着一個長度必定的數組,構建工具編譯好一個js文件後,將該文件的內容發送給web server,web server將接收到的內容順序填充到數組中;當有用戶頁面時,瀏覽器向web server請求該js內容,web server從數組中隨機挑選一個,返回給瀏覽器。除了能夠保證安全js的隨機性,還能將signature的生成放到web server中完成。構建工具在編譯js時將編譯的元信息發送給web server,此時並不生成出signature。用戶須要請求該js時再根據元信息實時生成一個signature,填充到js文件內容中。這樣生成的signature每次都是獨立的,經過檢測signature的使用次數,能夠很容易標識並攔截重放的請求。es6


Q五、做爲前端,你是如何處理前端動態化代碼保護的


代碼安全,通常是指前端JavaScript代碼的安全。一般,若是一段JavaScript代碼只能在正常的瀏覽器中運行,沒法或還沒有在非正常瀏覽器的運行環境執行獲得結果、沒法被等價翻譯成其餘編程語言的代碼,則認爲這段代碼是安全的。前端代碼保護主要一個是以語法樹變換爲基礎的混淆保護,另外一個是以構建私有執行環境爲思路的虛擬機保護,谷歌則屬於後者。代碼混淆的效果因混淆器的負責程度而不一樣,基礎級別的混淆器混淆出來的代碼也很容易被逆向,而虛擬機保護的抗逆向效果好,其原理是在JS的執行環境之上再設計構建一個虛擬機,全部原有業務邏輯的JS代碼均轉換爲該虛擬機可識別的字節碼,這樣複雜度較高效果好。


Q六、如何權衡頁面性能?


前端頁面性能一直是前端同窗繞不開的話題,關於頁面性能,一種通用而有效的性能優化方式是合理地爲頁面中的資源文件設置緩存。一般對於一個模塊化良好且使用成熟打包工具打包的項目,入口html的緩存策略會被配置爲Cache-Control: no-cache,而js/css/image等資源文件會設置一個比較長的緩存時間。但負責數據保護的js文件若是含有動態生成的邏輯,該js文件將不能再使用緩存,不然一旦緩存時間控制不當,將會引起各種數據解密失敗的問題。在考慮前端安全的前提下,將數據保護相關的邏輯從整個工程的JavaScript代碼中剝離出來,直接inline編譯到html頁面中,或者編譯到一個獨立的js文件中,爲該js文件單獨設置Cache-Control:no-cache的response頭部。該js與其餘js之間能夠使用全局變量、postMessage等方式通訊。


Q七、手寫一個經常使用混淆方法?


function foo(x) {
  x = String.fromCharCode.apply(null, x.split('').map(i => i.charCodeAt(0) + 23);  return btoa(x)
}function bar(y) {
  y = String.fromCharCode.apply(null, y.split('').reverse().map(i => i.charCodeAt(0) + 13);  return btoa(y);
}
複製代碼

Q八、兩個瀏覽器標籤頁如何實現通訊?


對於同源頁面,常見的方式包括: 廣播模式:Broadcast Channe / Service Worker / LocalStorage + StorageEvent共享存儲模式:Shared Worker / IndexedDB / cookie口口相傳模式:window.open + window.opener基於服務端:Websocket / Comet / SSE 等。

而對於非同源頁面,則能夠經過嵌入同源 iframe 做爲「橋」,將非同源頁面通訊轉換爲同源頁面


Q九、什麼是進程、線程;瀏覽器爲何有時會由於單個頁面卡死最終崩潰致使全部頁面崩潰的狀況?


雖然如今的谷歌瀏覽器是多進程瀏覽器架構,可是Chrome的默認策略是,每一個標籤對應一個渲染進程。可是若是從一個頁面打開了新頁面,而新頁面和當前頁面屬於同一站點時,那麼新頁面會複用父頁面的渲染進程。官方把這個默認策略叫process-per-site-instance。簡單來講若是幾個頁面符合同一站點,那麼他們將被分配到一個渲染進程裏面去,這種狀況下,一個頁面崩潰了,會致使同一站點的頁面同時崩潰,由於他們使用了同一個渲染進程。


Q十、什麼是XSS,如何避免XSS攻擊?


XSS解釋略。1.利用模板引擎 2.避免內聯事件 3.避免拼接 HTML 4.經過 CSP、輸入長度配置、接口安全措施等方法,增長攻擊的難度,下降攻擊的後果 5.可以使用 XSS 攻擊字符串和自動掃描工具尋找潛在的 XSS 漏洞


Q十一、CSRF瞭解多少,針對web安全,若是讓你負責指定一個轉義庫,你須要考慮哪些規則?


針對CSRF,須要至少從3個方面去考慮。CSRF自動防護策略:同源檢測(Origin 和 Referer 驗證); CSRF主動防護措施:Token驗證 或者 雙重Cookie驗證 以及配合Samesite Cookie; 保證頁面的冪等性,後端接口不要在GET頁面中作用戶操做。 針對轉義庫,至少須要考慮HTML 屬性、HTML 文字內容、HTML 註釋、跳轉連接、內聯 JavaScript 字符串、內聯 CSS 樣式表等等


Q十二、瀏覽器的宏任務、微任務分別有哪些,執行順序是什麼?


微任務和宏任務是綁定的,每一個宏任務在執行時,會建立本身的微任務隊列。 微任務的執行時長會影響到當前宏任務的時長。好比一個宏任務在執行過程當中,產生了 100 個微任務,執行每一個微任務的時間是 10 毫秒,那麼執行這 100 個微任務的時間就是 1000 毫秒,也能夠說這 100 個微任務讓宏任務的執行時間延長了 1000 毫秒。因此你在寫代碼的時候必定要注意控制微任務的執行時長。 在一個宏任務中,分別建立一個用於回調的宏任務和微任務,不管什麼狀況下,微任務都早於宏任務執行。

微任務包括 process.nextTick ,promise ,Object.observe ,MutationObserver。宏任務包括 script , setTimeout ,setInterval ,setImmediate ,I/O ,UI rendering。 1. 執行同步代碼,這屬於宏任務 2. 執行棧爲空,查詢是否有微任務須要執行 3. 執行全部微任務 4. 必要的話渲染 UI 5. 而後開始下一輪 Event loop,執行宏任務中的異步代碼


Q1三、談一下V8 下的垃圾回收機制?


V8 實現了準確式 GC,GC 算法採用了分代式垃圾回收機制。新生代中的對象通常存活時間較短,使用 Scavenge GC 算法。 在新生代空間中,內存空間分爲兩部分,分別爲 From 空間和 To 空間。在這兩 個空間中,一定有一個空間是使用的,另外一個空間是空閒的。新分配的對象會被放入 From 空間中,當 From 空間被佔滿時,新生代 GC 就會啓動了。算法會檢查 From 空 間中存活的對象並複製到 To 空間中,若是有失活的對象就會銷燬。當複製完成後將 From 空間和 To 空間互換,這樣 GC 就結束了。


Q1四、在V8GC的老生代中,哪些狀況會先啓動標記清除算法?


某一個空間沒有分塊的時候、空間中被對象超過必定限制、 空間不能保證新生代中的對象移動到老生代中。而且新生區和老生區標記過程是同一個過程,以後新生代把存活的數據移動到空閒區,老生代把死去的對象加到空閒列表中。


Q1五、js是基於寄存器的,V8是基於棧的,能不能說一下這二者有什麼同樣嗎?


解析器是parser,而解釋器是interpreter。之因此存在編譯器和解釋器,是由於機器不能直接理解咱們所寫的代碼,因此在執行程序以前,須要將咱們所寫的代碼「翻譯」成機器能讀懂的機器語言。按語言的執行流程,能夠把語言劃分爲編譯型語言和解釋型語言。 JavaScriptCore從SquirrelFish版開始是「基於寄存器」的,V8則不適合用「基於棧」或者「基於寄存器」的說法來描述。不少資料會說,Python、Ruby、JavaScript都是「解釋型語言」,是經過解釋器來實現的。這麼說其實很容易引發誤解:語言通常只會定義其抽象語義,而不會強制性要求採用某種實現方式。 例如說C通常被認爲是「編譯型語言」,但C的解釋器也是存在的,例如Ch。一樣,C++也有解釋器版本的實現,例如Cint。 通常被稱爲「解釋型語言」的是主流實現爲解釋器的語言,但並非說它就沒法編譯。例如說常常被認爲是「解釋型語言」的Scheme就有好幾種編譯器實現,其中率先支持R6RS規範的大部份內容的是Ikarus,支持在x86上編譯Scheme;它最終不是生成某種虛擬機的字節碼,而是直接生成x86機器碼。實際上不少解釋器內部是以「編譯器+虛擬機」的方式來實現的,先經過編譯器將源碼轉換爲AST或者字節碼,而後由虛擬機去完成實際的執行。所謂「解釋型語言」並非不用編譯,而只是不須要用戶顯式去使用編譯器獲得可執行代碼而已。

V8是能夠直接編譯JavaScript生成機器碼,而不經過中間的字節碼的中間表示的JavaScript引擎,它內部有虛擬寄存器的概念,但那只是普通native編譯器的正常組成部分。我以爲也不該該用「基於棧」或「基於寄存器」去描述它。 V8在內部也用了「求值棧」(在V8裏具體叫「表達式棧」)的概念來簡化生成代碼的過程,在編譯過程當中進行「抽象解釋」,使用所謂「虛擬棧幀」來記錄局部變量與求值棧的狀態;但在真正生成代碼的時候會作窺孔優化,消除冗餘的push/pop,將許多對求值棧的操做轉變爲對寄存器的操做,以此提升代碼質量。因而最終生成出來的代碼看起來就不像是基於棧的代碼了。

至於面試官所說的基於棧與基於寄存器的架構,誰更快?看看如今的實際處理器,大多都是基於寄存器的架構,從側面反映出它比基於棧的架構更優秀。

而對於VM來講,源架構的求值棧或者寄存器均可能是用實際機器的內存來模擬的,因此性能特性與實際硬件又有點不一樣。通常認爲基於寄存器的架構對VM來講也是更快的,緣由是:雖然零地址指令更緊湊,但完成操做須要更多的load/store指令,也意味着更多的指令分派(instruction dispatch)次數與內存訪問次數;訪問內存是執行速度的一個重要瓶頸,二地址或三地址指令雖然每條指令佔的空間較多,但整體來講能夠用更少的指令完成操做,指令分派與內存訪問次數都較少。


Q1六、在日常工做種,你是如何判斷 JavaScript 中內存泄漏的?


1. 通常是感官上的長時間運行頁面卡頓,猜可能會有內存泄漏。經過DynaTrace(IE)profiles等工具一段時間收集數據,觀察對象的使用狀況。而後判斷是否存在內存泄漏。 2. 工做中避免內存泄漏方法:肯定不使用的臨時變量置爲null,當前es6普及場景下少使用閉包也是一種方法。


Q1七、如何理解JS是單線程的?


整個JS代碼是執行在一條線程裏的,它並不像咱們使用的OC、Java等語言,在本身的執行環境裏就能申請多條線程去處理一些耗時任務來防止阻塞主線程。JS代碼自己並不存在多線程處理任務的能力。可是爲何JS也存在多線程異步呢?強大的事件驅動機制,是讓JS也能夠進行多線程處理的關鍵。


Q1八、你認爲安全沙箱能防止 XSS 或者 CSRF一類的攻擊的嗎?爲何?


將渲染進程和操做系統隔離的這道牆就是咱們要聊的安全沙箱。安全沙箱是不能防止 XSS 或者 CSRF 一類的攻擊, 安全沙箱的目的是隔離渲染進程和操做系統,讓渲染進行沒有訪問操做系統的權利 XSS 或者 CSRF 主要是利用網絡資源獲取用戶的信息,這和操做系統沒有關係的。


Q1九、instanceof 是如何判斷對象的類型的?手寫一個instanceof方法


instanceof 能夠正確的判斷對象的類型,由於內部機制是經過判斷對象的原型鏈 中是否是能找到類型的 prototype。

function instanceof(left, right) {    
    // 得到類型的原型    
    let prototype = right.prototype    
    // 得到對象的原型    
    left = left.__proto__    
    // 判斷對象的類型是否等於類型的原型    
    while (true) {     
        if (left === null)      
            return false     
        if (prototype === left)      
            return true     
        left = left.__proto__    
    } 
}
複製代碼

Q20、談一談vue的數據劫持?手寫一個簡易的數據劫持。


簡單來講數據劫持就是利用Object.defineProperty()來劫持對象屬性的setter和getter操做。vue2利用Object.defineProperty來劫持data數據的getter和setter操做。這使得data在被訪問或賦值時,動態更新綁定的template模塊。Vue採用了Proxy,Proxy不須要各類hack技術就能夠無壓力監聽數組變化;甚至有比hack更強大的功能——自動檢測length。

let onWatch = (obj, setBind, getLogger) => {  
    let handler = {    
        get(target, property, receiver) {      
            getLogger(target, property)      
            return Reflect.get(target, property, receiver);    
        },    
        set(target, property, value, receiver) {      
            setBind(value);      
            return Reflect.set(target, property, value);    
        }  
};  
    return new Proxy(obj, handler); 
};
let obj = { a: 1 } 
let value let p = onWatch(obj, (v) => {  
    value = v 
}, (target, property) => {  
    console.log(`Get '${property}' = ${target[property]}`); 
}) 
複製代碼

Q2一、Service Worker是什麼,日常用過嗎?


Service workers 本質上充當 Web 應用程序與瀏覽器之間的代理服務器,也能夠在 網絡可用時做爲瀏覽器和網絡間的代理。它們旨在(除其餘以外)使得可以建立有效的 離線體驗,攔截網絡請求並基於網絡是否可用以及更新的資源是否駐留在服務器上來採 取適當的動做。他們還容許訪問推送通知和後臺同步 API。 Service workers能夠用來作緩存文件,提升首屏速度


Q2二、如何理解 Redux 異步流中間件,它是幹什麼的?


Redux核心理念很清晰明瞭,單一數據源,不可變數據源(單一)state以及純函數修改state。它的主要流程就是:舊Store -> 用戶觸發action(from view) -> reducer -> 新State -> view。Redux有一個全局倉庫store來保存整個應用程序的state,而且修改store的惟一方式就是經過用戶觸發action 而後dispatch出去(action), dispatch 函數內部會調用 reducer 而且返回建立一個全新的state(而且銷燬舊的state)來更新咱們的store。當store發生更新,view就會觸發render函數進行更新。

不過Redux自己只能處理同步事件,Redux 做者(@dan_abramov)將異步流的處理經過提供中間件的方式讓開發者自行選擇,經常使用的異步流中間件有redux-thunk,還有redux-saga。


Q2三、redux-thunk 中間件的做用是什麼,日常使用須要注意哪些地方?


中間件 redux-thunk 中間件,它的機制主要經過判斷 action 是否爲一個函數(內部返回一個promise)。若是是則會當即調用,action 在函數內部能夠進行異步流處理(本質仍是同步),而後繼續經過dispatch(action)進行同步數據的處理;若是不是函數,則經過next(action)調用下一個中間件或者是進入reducer。redux-thunk的核心思想是擴展action,使得action從一個對象變成一個函數。redux-thunk處理異步便捷,配合asyvc/await更能夠使得action同步化。不過redux-thunk中action會所以變的複雜,後期可維護性降低;同時若多人協做,當本身的action調用了別人的action,別人action發生改動,則須要本身主動修改。

// 簡化後的核心部分
function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }
    return next(action);
  };
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;
複製代碼

Q2四、Redux-saga中間件是用來作什麼的?


Redux-saga是一個用於管理redux應用異步操做的中間件之一,redux-saga經過建立sagas將全部異步操做邏輯收集在一個地方集中處理,能夠用來代替redux-thunk中間件。Redux-saga相對於其餘異步流中間件,將異步處理單獨放在一塊兒,不須要修改action,action仍是同步。同時redux-saga的異步控制流程也很強大,好比對於競態的處理就經過takeLatest()來處理。 redux-saga 是一個用於管理應用程序 Side Effect(反作用,例如異步獲取數據,訪問瀏覽器緩存等)的 library,它的目標是讓反作用管理更容易,執行更高效,測試更簡單,在處理故障時更容易。能夠想像爲,一個 saga 就像是應用程序中一個單獨的線程,它獨自負責處理反作用。 redux-saga 是一個 redux 中間件,意味着這個線程能夠經過正常的 redux action 從主應用程序啓動,暫停和取消,它能訪問完整的 redux state,也能夠 dispatch redux action。


Q2五、redux是如何進行數據更新的?


redux不能夠像jquery同樣直接更改組件的數據,若是咱們要更新某個節點上的值,首先要產生一個徹底全新的對象,好比圖中的節點,在路徑上全部的對象都處理更新過的狀態,其餘節點沒有更新變化,由上至下,逐層比較。react節點的數據是不能直接修改的,若想修改就必須先進行復制,不管是淺複製仍是深複製,而後去包含你要修改的部分。


Q2六、React redux爲什麼須要不可變數據?


爲了性能優化。由於當一個store發生變化,咱們須要通知全部的組件須要更新了。全部的變化都是由action觸發,觸發在原來舊的state上,造成一個新的state,新舊state是兩個徹底不一樣的對象,因此當新舊state不是同一個對象的時候,咱們就知道store發生了變化,咱們不須要比較它其中的值是否發生變化,咱們只須要比較兩個引用的狀態是否是同樣便可,這樣就能夠達到一個性能優化的目的。

同時也代表了redux中的store都是不可變數據,每一個節點都是不可變數據。這樣當一個組件綁定在一個節點上,這樣我只要判斷一個組件先後的引用狀態是否相等,就能夠知道當前的store有沒有變,從而決定是否要更新你的組件,省去了深層次的遍歷每一個值是否相等,只比較引用便可。同時便於調試和跟蹤


Q2七、用戶在瀏覽器URL輸入並回車後,瀏覽器是如何渲染的?


①.處理 HTML 並構建 DOM 樹;②.處理 CSS 構建 CSSOM 樹;③.將 DOM 與 CSSOM 合併成一個渲染樹;④.根據渲染樹來佈局,計算每一個節點的位置;⑤.調用 GPU 繪製,合成圖層,再通過佈局與具體WebKit Ports的渲染接口,把渲染樹渲染輸出到屏幕上,成爲了最終呈如今用戶面前的Web頁面。


Q2八、什麼是防抖與節流,手寫一個簡易的函數防抖?


防抖:把觸發很是頻繁的事件(好比按鍵)合併成一次執行;節流:保證每 X 毫秒恆定的執行次數,間斷執行。函數防抖,在一段連續操做結束後,處理回調,利用 clearTimeout 和 setTimeout 實現;函數節流,在一段連續操做中,每一段時間只執行一次,頻率較高的事件中使用來提升性能。函數防抖關注必定時間連續觸發,只在最後執行一次,而函數節流側重於一段時間內只執行一次。

_.debounce = function(func, wait, immediate) {
  var timeout, args, context, timestamp, result;

  var later = function() {
    // 據上一次觸發時間間隔
    var last = _.now() - timestamp;

    // 上次被包裝函數被調用時間間隔last小於設定時間間隔wait
    if (last < wait && last > 0) {
      timeout = setTimeout(later, wait - last);
    } else {
      timeout = null;
      // 若是設定爲immediate===true,由於開始邊界已經調用過了此處無需調用
      if (!immediate) {
        result = func.apply(context, args);
        if (!timeout) context = args = null;
      }
    }
  };

  return function() {
    context = this;
    args = arguments;
    timestamp = _.now();
    var callNow = immediate && !timeout;
    // 若是延時不存在,從新設定延時
    if (!timeout) timeout = setTimeout(later, wait);
    if (callNow) {
      result = func.apply(context, args);
      context = args = null;
    }

    return result;
  };
};
複製代碼

Q2九、函數柯里化的面試題,實現add(1)(2)(3) = 6; add(1, 2, 3)(4) = 10?


柯里化(Currying),又譯爲卡瑞化或加里化,是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數並且返回結果的新函數的技術。

function add(){
    var args = [].slice.call(arguments);
    var fn = function(){
        var newArgs = args.concat([].slice.call(arguments));
        return add.apply(null,newArgs);
    } 
    fn.toString = function(){
        return args.reduce(function(a, b) {
            return a + b;
        })
    }
    return fn ;
}

// 能夠接受任意個數的參數
add(1)(2,3) //6
add(1)(2)(3)(4)(5) //15
複製代碼

Q30、CommonJS 和 ES6 中的模塊化的二者區別有哪些?


前者支持動態導入,也就是 require(${path}/xx.js),後者目前不支持,可是 已有提案;

前者是同步導入,由於用於服務端,文件都在本地,同步導入即便卡住主線程 影響也不大。然後者是異步導入,由於用於瀏覽器,須要下載文件,若是也採 用導入會對渲染有很大影響;

前者在導出時都是值拷貝,就算導出的值變了,導入的值也不會改變,因此如 果想更新值,必須從新導入一次。可是後者採用實時綁定的方式,導入導出的 值都指向同一個內存地址,因此導入值會跟隨導出值變化;

後者會編譯成 require/exports 來執行的;


Q3一、談一談MVVM模式?


MVVM 由如下三個內容組成

View:界面;

Model:數據模型;

ViewModel:做爲橋樑負責溝通 View 和 Model;

在 JQuery 時期,若是須要刷新 UI 時,須要先取到對應的 DOM 再更新 UI,這樣數據和業務的邏輯就和頁面有強耦合。在 MVVM 中,UI 是經過數據驅動的,數據一旦改變就會相應的刷新對應的 UI,UI若是改變,也會改變對應的數據。這種方式就能夠在業務處理中只關心數據的流轉,而無需直接和頁面打交道。ViewModel 只關心數據和業務的處理,不關心 View 如何處理數據,在這種狀況下,View 和 Model 均可以獨立出來,任何一方改變了也不必定須要改變另外一方,而且能夠將一些可複用的邏輯放在一個 ViewModel 中,讓多個 View 複用這個 ViewModel。在 MVVM 中,最核心的也就是數據雙向綁定,例如 Angluar 的髒數據檢測,Vue中的數據劫持。


Q3二、你在負責H5頁遊、H5活動營銷頁開發的時候是如何設計和管理頁面的?


在開發H5營銷頁的時候,能夠使用引擎Egret,也能夠使用canavs,或者幀動畫、瓦片地圖等等。在一些複雜or附帶角色場景的頁面,這些場景確定有進入場景、退出場景、再次進入場景、退出場景、清理場景、更新場景的基本功能,大多遊戲基本都是這樣,場景有了,這個時候就須要手動建立一個管理器用於保障場景的複用性。

比較保守的作法就是,場景歸場景管、UI歸UI管,場景切換有管理類、UI應該也須要作一個LRU策略,在這裏,UI須要根據使用頻率來進行清理,好比15分鐘一次都沒用到,咱們就能夠認爲該UI資源使用不頻繁,這樣咱們就能夠幹掉圖集和內存,這裏只須要設置一個計時器,每次打開的時候記錄上時間,而且我只針對關閉的面板遍歷,由於已經打開的面板根本不須要去清理,亦不須要去檢測,UI資源中最佔內存的仍是圖集,若是能夠清理掉低頻使用的圖集就能夠剩下不少內存,減小遊戲總體內存壓力,並且大型遊戲UI資源佔遊戲體積是最大的。

咱們日常使用的H5遊戲引擎,底層默認的操做是隻要關閉UI就自動清理,這樣會形成CPU發熱上升,雖然這樣積極地清理內存,可是用戶體驗就降低了。因此,咱們在這裏建立的UI管理器,不須要管理UI的釋放,只須要給出一個UI面板名,負責調用管理器的開、關就好。

至於UI的釋放,能夠設計一個機制,除了當前場景,已啓用的UI資源15分鐘內沒有再次啓用就自動釋放(有點模仿GC的感受)。這裏的15分鐘計數器檢測的是關閉面板的使用次數,面板打開一次就記一次使用次數,這裏關閉的面板值得是曾開啓的面板。在這裏,咱們不須要記錄每一個面板以前的開啓次數、如今的開啓次數,只須要在打開的時候遞增次數,給出每一個面板的總次數便可。15分鐘檢測的時候若是在你設定的頻率,咱們能夠認爲它使用的頻繁,若是低於你設置的頻率,就能夠認爲它是低頻率使用的面板UI,15分鐘後能夠清理了。在以上的UI管理器設計思路中,最麻煩的是有些UI資源,它橫跨幾個面板,屬於交叉複用資源,sceneMenu使用了sceneBoot裏的資源sound,可是又來該面板的UI被清理掉了,玩家切換到sceneBoot的時候發現沒有UI又要從新加載。


Q3三、什麼是MVP模式,MVVM 和 MVP 的關係?


這裏的MVP,不是LOL裏的全場最佳那個意思。View:對應於Activity,負責View的繪製以及與用戶交互Model:依然是業務邏輯和實體模型Presenter:負責完成View與Model間的交互View不直接與Model交互,而是經過與Presenter交互來與Model間接交互。 Presenter與View的交互是經過接口來進行的。 而MVVM 模式是將 Presenter 更名爲 ViewModel,基本上與 MVP 模式徹底一致。惟一的區別是,它採用雙向綁定(data-binding):View的變更,自動反映在 ViewModel,反之亦然。這樣開發者就不用處理接收事件和View更新的工做,框架已經幫你作好了。


Q3四、不管是小程序,仍是vue都會常常遇到模板解析,手寫一個簡易的 htmlParse 解析器?


// 轉化HTML至AST對象
  function parse(template){
    var currentParent; //當前父節點
    var root; //最終生成的AST對象
    var stack = []; //插入棧
    var startStack = []; //開始標籤棧
    var endStack = [];  //結束標籤棧
    //console.log(template);
    parseHTML(template,{
      start:function start(targetName,attrs,unary,start,end,type,text){//標籤名 ,attrs,是否結束標籤,文本開始位置,文本結束位置,type,文本,
        var element = {   //咱們想要的對象
          tag:targetName,
          attrsList:attrs,
          parent:currentParent,  //須要記錄父對象吧
          type:type,
          children:[]
        }
        if(!root){ //根節點哈
          root = element;
        }
        if(currentParent && !unary){ //有父節點而且不是結束標籤?
          currentParent.children.push(element);  //插入到父節點去
          element.parent = currentParent;  //記錄父節點
        }
        if (!unary) {  //不是結束標籤?
            if(type == 1){
               currentParent = element;//不是結束標籤,當前父節點就要切換到如今匹配到的這個開始標籤哈,後面再匹配到
               startStack.push(element);  //推入開始標籤棧
            }
             stack.push(element);  //推入總棧
         }else{
           endStack.push(element);  //推入結束標籤棧
           currentParent = startStack[endStack.length-1].parent;   //結束啦吧當前父節點切到上一個開始標籤,這能理解吧,當前這個已經結束啦
         }
         //console.log(stack,"currentstack")
      },
      end:function end(){

      },
      chars:function chars(){

      }
    });
    console.log(root,"root");
    return root;
  };

  // Regular Expressions for parsing tags and attributes
  var singleAttrIdentifier = /([^\s"'<>/=]+)/; var singleAttrAssign = /(?:=)/; var singleAttrValues = [ // attr value double quotes /"([^"]*)"+/.source,
    // attr value, single quotes
    /'([^']*)'+/.source, // attr value, no quotes /([^\s"'=<>`]+)/.source
  ];
  var attribute = new RegExp(
    '^\\s*' + singleAttrIdentifier.source +
    '(?:\\s*(' + singleAttrAssign.source + ')' +
    '\\s*(?:' + singleAttrValues.join('|') + '))?'
  );
  // could use https://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-QName
  // but for Vue templates we can enforce a simple charset
  var ncname = '[a-zA-Z_][\\w\\-\\.]*';
  var qnameCapture = '((?:' + ncname + '\\:)?' + ncname + ')';
  var startTagOpen = new RegExp('^<' + qnameCapture);
  var startTagClose = /^\s*(\/?)>/;
  var endTag = new RegExp('^<\\/' + qnameCapture + '[^>]*>');
  var doctype = /^<!DOCTYPE [^>]+>/i;
  var comment = /^<!--/;
  var conditionalComment = /^<!\[/;

//偷懶哈  上面的正則是我在vue上拿下來的,這個後期能夠研究,下面的話簡單的寫兩個用用,和vue原版的是有一些差異的

  var varText = new RegExp('{{' + ncname + '}}');
  //空格與換行符
  var space = /^\s/;
  var checline = /^[\r\n]/;
    /**
      type 1普通標籤
      type 2代碼
      type 3普通文本
    */
  function parseHTML(html,options){
    var stack = []; //內部也要有一個棧
    var index = 0;  //記錄的是html當前找到那個索引啦
    var last; //用來比對,當這些條件都走完後,若是last==html 說明匹配不到啦,結束while循環
    var isUnaryTag = false;

    while(html){
      last = html;
      var textEnd = html.indexOf('<');
      if(textEnd === 0){ //這一步若是第一個字符是<那麼就只有兩種狀況,1開始標籤  2結束標籤
        //結束標籤
        var endTagMatch = html.match(endTag); //匹配
        if(endTagMatch){
          console.log(endTagMatch,"endTagMatch");
          isUnaryTag = true;
          var start = index;
          advance(endTagMatch[0].length); //匹配完要刪除匹配到的,而且更新index,給下一次匹配作工做
          options.start(null,null,isUnaryTag,start,index,1);
          continue;
        }
        //初始標籤
        var startMatch = parseStartTag();
        if(startMatch){
          parseStartHandler(startMatch);//封裝處理下
          console.log(stack,"startMatch");
          continue;
        }
      }

      if(html === last){
        console.log(html,"html");
       break;
      }
    }
    function advance (n) {
      index += n;
      html = html.substring(n);
    }
    //處理起始標籤 主要的做用是生成一個match 包含初始的attr標籤
    function parseStartTag(){
      var start = html.match(startTagOpen);
      if(start){
        var match = {
           tagName: start[1],       // 標籤名(div)
           attrs: [],               // 屬性
           start: index             // 遊標索引(初始爲0)
       };
       advance(start[0].length);
       var end, attr;
       while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {//在endClose以前尋找attribute
           advance(attr[0].length);
           match.attrs.push(attr);
       }
       if (end) {
           advance(end[0].length);      // 標記結束位置
           match.end = index;      //這裏的index 是在 parseHTML就定義 在advance裏面相加
           return match         // 返回匹配對象 起始位置 結束位置 tagName attrs
       }

      }
    }
    //對match進行二次處理,生成對象推入棧
    function parseStartHandler(match){
      var _attrs = new Array(match.attrs.length);
      for(var i=0,len=_attrs.length;i<len;i++){  //這兒就是找attrs的代碼哈
        var args = match.attrs[i];
        var value = args[3] || args[4] || args[5] || '';
        _attrs[i] = {
          name:args[1],
          value:value
        }
      }
      stack.push({tag: match.tagName,type:1, lowerCasedTag: match.tagName.toLowerCase(), attrs: _attrs}); //推棧
      options.start(match.tagName, _attrs,false, match.start, match.end,1);  //匹配開始標籤結束啦。
    }
  }
複製代碼

Q3五、什麼是虛擬DOM,虛擬DOM與真實DOM的區別是什麼?


虛擬 DOM 是一樣操做 DOM,不過是把 DOM 樹抽象成數據對象,Virtual Dom 它能夠使咱們操做這塊的數據對象,用數據對象來呈現 DOM 樹,在每次視圖渲染的時候 patch 取得最優,這種作法使咱們最小量地去修改 DOM,此外 Virtual DOM 還有一個很大的做用是簡化 DOM 操做,讓數據與 DOM 之間的關係更直觀更簡單。

虛擬DOM不會進行形成排版與重繪操做;

虛擬DOM進行頻繁修改,而後一次性比較並修改真實DOM中須要改的部分(DIFF算法),最後並在真實DOM中進行排版與重繪,減小過多DOM節點排版與重繪損耗;

真實DOM頻繁排版與重繪的效率是至關低的;

虛擬DOM有效下降大面積(真實DOM節點)的重繪與排版,由於最終與真實DOM比較差別,能夠只渲染局部使用虛擬DOM的損耗計算。像JQuery這種是屬於以選擇器爲導向的框架,Vue和React屬於有明確分層架構的MV*框架,而虛擬DOM屬於框架中的節點模塊,也是核心模塊,重中之重。


Q3六、標準的Diff 算法複雜度是 O(n^3),FB團隊作了什麼操做得以將時間複雜度降到線性的有了解過嗎?


①.兩個相同組件產生相似的 DOM 結構,不一樣的組件產生不一樣的 DOM 結構;

②.對於同一層次的一組子節點,它們能夠經過惟一的 id 進行區分。算法上的優化是 React 整個界面 Render 的基礎,事實也證實這兩個假設是合理而精確的,保證了總體界面構建的性能。


Q3七、如何規避迴流與重繪?


1.減小回流,避免頻繁改動;2.避免逐條改變樣式,使用類名去合併樣式;3.經常使用優化小操做以下:

使用 translate 替代 top

使用 visibility 替換 display: none ,由於前者只會引發重繪,後者會引起迴流(改變了佈局)

把 DOM 離線後修改,好比:先把 DOM 給display:none (有一次 Reflow),而後你修改100次,而後再把它顯示出來

不要把 DOM 結點的屬性值放在一個循環裏當成循環裏的變量

不要使用 table 佈局,可能很小的一個小改動會形成整個 table 的從新佈局

動畫實現的速度的選擇,動畫速度越快,迴流次數越多,也能夠選擇使用requestAnimationFrame

CSS 選擇符從右往左匹配查找,避免 DOM 深度過深

將頻繁運行的動畫變爲圖層,圖層可以阻止該節點回流影響別的元素。好比對於 video 標籤,瀏覽器會自動將該節點變爲圖層。

避免頻繁讀取會引起迴流/重繪的屬性,若是確實須要屢次使用,就用一個變量緩存起來。

對具備複雜動畫的元素使用絕對定位,使它脫離文檔流,不然會引發父元素及後續元素頻繁迴流。


Q3八、你這麼喜歡造輪子,如何拓展和設計jQ?


首先,jQ是以選擇器爲導向的。

class jQuery {
	constructor() {
		const result = document.querySelectorAll(selector) const length = result.length
		for (let i = 0; i < length; i++) {
			this[i] = result[i]
		}
		this.length = length this.selector = selector
	}
	get(index) {
		return this[index]
	}
	each(fn) {
		for (let i = 0; i < this.length; i++) {
			const elem = this[i] fn(elem)
		}
	}
	on(type, fn) {
		return this.each(elem => {
			elem.addEventListener(type, fn, false)
		})
	}
	style(data) {}
}
const $p = new jQuery('p') $p.get(1) $p.each((elem) => console.log(elem.nodeName))
複製代碼

Q3九、React的Context兩種方式childContectType、createContext,爲何前者後來被棄用了?


棄用的緣由,就是中間哪些不須要這些數據的組件,也會由於接收到這些數據而去二次渲染


Q40、在webapp或混合開發中,讓你優化性能和體驗,你該怎麼作?


①WebView初始化慢,能夠在初始化同時先請求數據,讓後端和網絡不要閒着。

②後端處理慢,可讓服務器分trunk輸出,在後端計算的同時前端也加載網絡靜態資源。③腳本執行慢,就讓腳本在最後運行,不阻塞頁面解析。

④同時,合理的預加載、預緩存可讓加載速度的瓶頸更小。

⑤WebView初始化慢,就隨時初始化好一一個WebView待用。

⑥DNS和連接慢,想辦法複用客戶端使用的域名和連接。

⑦腳本執行慢,能夠把框架代碼拆分出來,在請求頁面以前就執行好。


Q4一、React中受控組件和非受控組件是什麼?


React的核心組成之一就是可以維持內部狀態的自治組件,不過當咱們引入原生的HTMI.表單元素時

( input, select, textarea等),咱們是否應該將全部的數據託管到React組件中仍是將其仍然保留在DOM 元素中呢?這個問題的答案就是受控組件與非受控組件的定義分割。

受控組件(Controlled Component)代指那些交由React 控制而且全部的表單數據統一存放的組件。譬以下面這段代碼中username變量值並無存放到DOM元素中,而是存放在組件狀態數據中。任什麼時候候咱們須要改變username變量值時,咱們應當調用setState函數進行修改。

非受控組件(Uncontrol led Couponent )則是由DOM存放表單數據,並不是存放在React 組件中。咱們能夠使用refs來操控DOM元素:

不過實際開發中咱們並不提倡使用非受控組件,由於實際狀況下咱們須要更多的考慮表單驗證、選擇性的開啓或者關閉按鈕點擊、強制輸入格式等功能支持,而此時咱們將數據託管到React中有助於咱們更好地以聲明式的方式完成這些功能。引入React 或者其餘MWW框架最初的緣由就是爲了將咱們從繁重的直接操做DOM中解放出來。


Q4二、React中key的做用是什麼?


Keys 是React用於追蹤哪些列表中元素被修改、被添加或者被移除的輔助標識。

在開發過程當中,咱們須要保證某個元素的key 在其同級元素中具備惟-一性。 在React Diff算法中React 會藉助元素的Key值來判斷該元素是新近建立的仍是被移動而來的元素,從而減小沒必要要的元素重渲染。此外,React還須要藉助Key值來判斷元素與本地狀態的關聯關係,所以咱們毫不可忽視轉換函數中Key的重要性。


Q4三、看你作過flutter混合應用,說一下Flutter和原生代碼的通訊?


以網絡請求爲例能夠在Dart中定義一個MethodChannel對象,而後在Java端實現相同名稱的MethodChannel,在Flutter頁面中註冊後,調用post方法就能夠調用對應的Java實現。


Q4四、在小程序、APP裏,針對WebView被運營商劫持、注入問題,你是如何處理的?


使用CSP攔截頁面中的非白名單資源、使用HTTPS、讓App將其轉換爲一個Socket請求,並代理WebView的訪問、在內嵌的WebView中應該限制容許打開的WebView的域名,並設置運行訪問的白名單。或者當用戶打開外部連接前給用戶強烈而明顯的提示。


Q4五、如今有個業務場景,在移動端廣告業,我想實現一個小人 從頂部走到底部,走的是S曲線,或者是用戶手指劃過的路線,你將如何實現,你怎麼保證它走的路線不會偏?


不會


Q4六、如今有個場景,在淘寶APP廣告頁,有一個藝術字,福字,須要用戶手描它,類似度很是高的狀況(80%)下打開紅包,用前端方法你將採用什麼方式去實現,爲何算覆蓋率,爲何算指定路徑點,有什麼區別,日常作過嗎?


回答用覆蓋率,具體實現方法不會。


Q4七、針對大量echart數據、或者大量其餘圖標,你將如何優化?


瓦片地圖


Q4八、若是要你寫一個混合app,你將如何技術選型,爲何選擇flutter或者uniapp,理由是什麼,你選擇方案的依據是什麼?


我選flutter,理由在下面。


Q4九、Virtual DOM 真的比操做原生 DOM 快嗎?


不必定,這是一個性能 vs. 可維護性的取捨。框架的意義在於爲你掩蓋底層的 DOM 操做,讓你用更聲明式的方式來描述你的目的,從而讓你的代碼更容易維護。沒有任何框架能夠比純手動的優化 DOM 操做更快,由於框架的 DOM 操做層須要應對任何上層 API 可能產生的操做,它的實現必須是普適的。框架給你的保證是,你在不須要手動優化的狀況下,我依然能夠給你提供過得去的性能。


Q50、SameSite cookies有用過嗎?


SameSite屬性是cookie除了經常使用的path, domain, expire, HttpOnly, Secure的一個專門用於防止csrf漏洞的屬性。在cookie上引入SameSite屬性提供了三種不一樣的方法來控制CSRF漏洞。咱們能夠選擇不指定屬性,也能夠使用Strict或Lax將cookie限制爲同站點請求。1.設置SameSite = Strict,則表示您的cookie僅在第一方網站中發送使用;2.設置SameSite = Lax,當用戶發送GET方法的同步請求時,將會發送cookie;3.當SameSite = None時,這意味着你能夠在第三方環境中發送cookie。

以上的面試題80%主要來自螞蟻金服 【面試已跪】

前端方向

這個章節主要是表達筆者我的去前端方向的粗淺觀點,去年看了D2論壇的視頻,很是贊同狼叔的那段話,現階段框架之間的紛爭早已結束,如今更重要的是"提效"。本人工做時間較短,所接觸的框架和庫都不是不少,下面是我簡單的一個小總結,針對跨端方面的。同時也是對上面第48題的做出我我的的解答。

縱觀已有的跨端方案,能夠分爲三類:Web 容器、泛 Web 容器、自繪引擎框架。

基於web容器即基於瀏覽器的跨平臺也作得愈來愈好,天然管線也愈來愈短,與native的一些技術手段來實現性能上的相互補充。好比Egret、Cocos、Laya這些遊戲引擎,它們在跨平臺方面的作法多以Typescript編寫,在iOS和安卓平臺的各類瀏覽器中輕鬆的運行HTML5遊戲,並在不一樣平臺瀏覽器裏提供近乎一致的用戶體驗,好比Egret還會提供高效的 JS-C Binding 編譯機制,以知足遊戲編譯爲原生格式的需求,不過大多數HTML遊戲引擎(好比egret和laya)也屬於web容器這個範疇內。web容器框架也有一個明顯的致命(在對體驗&性能有較高要求的狀況下)的缺點,那就是WebView的渲染效率和JavaScript執行性能太差。再加上Android各個系統版本和設備廠商的定製,很難保證所在全部設備上都能提供一致的體驗。 泛 Web 容器框架好比ReactNative和Weex,即上層經過面向前端友好的UI,下層經過native的渲染形式,雖然一樣使用類HTML+JS的UI構建邏輯,可是最終會生成對應的自定義原生控件,以充分利用原生控件相對於WebView的較高的繪製效率,同時H5與native相互補充來達到更好的用戶體驗,這也是一種很好的解決方案。缺陷也很明顯,隨着系統版本變化和API的變化,開發者可能也須要處理不一樣平臺的差別,甚至有些特性只能在部分平臺上實現,這樣框架的跨平臺特性就會大打折扣。

自繪引擎框架這裏專指Flutter框架,從底層就承擔跨端的任務和渲染方式,就使用方面來看,就是寫樣式有點費勁,嵌套警告(根本緣由是我菜)。

2020寒冬

對於這個話題,從我自身的角度而言有一些感觸,在網上就有大佬曾說,2019年是過去十年最差的一年,也是將來10年裏最好的一年。其實從2019年開始互聯網就一直有寒冬的說法,各大互聯網開始裁人、以及個別大廠員工自身利益受損的新聞。從我2019-2020面試的狀況來看,2020確實不如以前,可是遠沒有網上傳的寒冬已至那麼嚴重。

小弟以爲,當下互聯網就業環境並非寒冬,只不過是更加理智,投資人不再是拿張PPT隨便說說就能搞到錢了,面試者不再是背背面試題就能找到工做的,就業環境的嚴峻,提醒咱們更加要注重自身能力的培養,而不是糊弄。更加註重本身的理論知識怎樣可以爲公司、產品帶來更大的價值,曾有面試官這樣問我,"你以爲公司招你來是作什麼的,招你來是讓你解決問題的",這同時也要求不只僅要有足夠的硬實力,從軟實力方面來講,不只要把事情作好,更要作好向上管理和向下管理。

大多數狀況下,在面試過程當中,面試官或者公司是主導方,節奏是跟着面試官走,可是我以爲面試其實就是一個相互探討的過程,不只是公司在選擇你,你也在選擇公司、之後工做的同事和leader。雖然我在面試螞蟻金服的過程當中失敗了,可是我我的的面試風格就是,我會就是會說一堆,不會我就會說不會,我歷來不會嘗試搪塞、企圖矇混過關(在有些人看來可能有點傻),可是我就是我,我會就是會,我所能作的,頂多就是向面試官請教和探討,儘量嘗試向面試官表達個人所知所學。因此我此次面試中,更多的是和麪試官探討,也會反問面試官一些問題,不只僅是最後面試官說能夠問他幾個問題,在面試過程當中就會詢問一些,優秀的面試官會跟你探討,而且討論出一個合理的方案或者正確的答案,整個過程很是愉悅,好比,阿里的面試官們,面試官打開個人github針對裏面的項目一個個詢問,並鼓勵我繼續完善下去。

此次寒冬面試,學歷、實力均受限,直接致使沒有入職BAT TMD,不過最大的收穫就是跟10+多位面試官的交流,再次更加讓我認清我本身,認清本身的長處、短板和之後的規劃,和對作人作事的見解,在這裏真誠的感謝10多位面試官。

最後,我到底去了大公司、小公司仍是外包,不便透露,我只是一個平凡的新手前端。對於一些同窗對於求職去大公司仍是小公司的諮詢,個人態度是,在實力容許的狀況下,建議本着"工做是爲了更好的生活"這個宗旨去綜合考慮,由於人生本就是一場不可重啓的賭博。

學歷

乞丐不必定會妒忌百萬富翁,但可能會妒忌收入更高的乞丐。沒有更高的視野,你會糾結於如今的圈子;當有了更高的視野,你會把身邊的人與事看淡。
程序員求職,學歷是否重要,個人答案是: 重要,我吃了學歷的虧不止一次兩次,曾在求人內推,最終栽在鵝*HR面、前程**HR面、萬*HR面被拒,其中某家HR甚至直言以爲我不能和那些985的同事工做,也問了一些關於項目緊急我該如何處理的問題。 我只能表示,高考犯的過,餘生本身慢慢懺悔好吧。在互聯網的圈子中,核心是咱們自己的技術,可是有多少人的技術能達到逆天或者很是牛逼的地步,若是你是這樣的人,啥都不用說了,可是我以爲絕大部分的程序員都是普通人、日常人,(像我這樣的雙非就更底層了),不要說什麼3年之後公司就不看學歷了,學歷是一生的事情,那些高考可以碾壓你的人,出了社會只要他們願意,照樣能碾壓你,你努力,你拼命,別人也不會睡大覺等你,因此,能提高本身的學歷,仍是要儘可能提高的。

最後

我只是一名普普統統平平凡凡的小前端,但願個人面經和經歷對你們求職有些許幫助。想要生活得漂亮,須要付出極大忍耐和努力,一不抱怨,二不解釋。程序員更是如此,碼生共勉。
相關文章
相關標籤/搜索