--- tags: Learn --- # 前端工程師面試考題 ## JS ### closure (閉包) 簡單來說在一個帶有參數 a 的函式中返回另一個使用到參數 a 的函式 此時在函式外可以獲取到參數 a 就稱為閉包 即把參數存到一個新的函數中 保留其記憶體位置 就是閉包 ```javascript= function makeAdder(x) { return function(y) { // 函數被 return 後,還是能繼續存取環境中的 x 變數 return x + y; }; } // 建立一個新變數指向原本的函數 傳入 x 值,調用後可以得到 x 就是一個閉包 var add5 = makeAdder(5); console.log(add5(2)) // 所以這裡結果為 7 ``` 上述可以發現 add5 本身只有傳入 y 值 但還是可以獲取到 makeAdder 的 x 值 這就是所謂的閉包了 在記憶體中保留了 x 的位置 使其可以繼續被抓取使用 ### hoisting (提升) 在宣告變數前先引用變數 不會出現 xxx is not defined 報錯,而是出現 JS 中的預設值 undefined ,就是所謂的提升。 函數與 var 宣告的變數都會被提升,且函數被提升的權重會高於變數。 [參考文章 - 這篇我大概也只看懂一半](https://blog.techbridge.cc/2018/11/10/javascript-hoisting/) ### undefined vs null 先上一段有趣的小東西: ```javascript= console.log(undefined == null) console.log(undefined === null) ``` 上方代碼輸出結果第一行會為真,第二行則為假 原因是這兩者都屬於 falsey 但是兩者的型別是不同的 在 JS 中 null 屬於 object 型別 而 undefined 的型別則還是 undefined 這也導致在第一行程式碼判斷中 falsey = falsey 所以是 true 然後第二行程式碼會判斷兩者型別 發現 object != undefined 所以返回 false 有趣吧?也附上一篇短短的參考[文章](https://www.jstips.co/zh_tw/javascript/differences-between-undefined-and-null/) ### this 的 call 與 apply call 與 apply 都會把傳入的第一個參數定義為 this 的指向 其他參數則依序作為原本的參數傳入函數裡 兩者的差別是 apply 傳入的其餘參數必需要用陣列方式傳入 [參考文章](https://realdennis.medium.com/javascript-%E8%81%8A%E8%81%8Acall-apply-bind%E7%9A%84%E5%B7%AE%E7%95%B0%E8%88%87%E7%9B%B8%E4%BC%BC%E4%B9%8B%E8%99%95-2f82a4b4dd66) ### session/cookie/localstorage session 被存在伺服器端,cookie 被存在客戶端 上述兩者在瀏覽器關閉時都會失效, session 的安全性 > cookie 的安全性。 localstorage 被存在本地端, 除非被手動刪除,否則會永久保存在本地端。 ### scope (作用域) 每個執行環境都有一個參照的外部環境 這個外部環境會看你當前執行的程式碼處於哪種詞彙環境 即他看的是你在聲明時存在的位置所決定的,而不是執行或調用的位置外 所以下方程式碼: ```javascript= function b (){ console.log(x); } function a (){ var x = 2; b(); } var x = 1; a(); ``` 在函數 b 的外部參考會是全域中的 x 而不是執行它的函數 a 所以輸出的 x 會是全域中的 1 ,這就是詞彙環境(看該程式碼實際上在物理上存在的位置決定)。 [參考文章](https://medium.com/itsems-frontend/javascript-scope-and-scope-chain-ca17a1068c96) ### prototype(原型) 要先知道,在 JS 中每個物件都有自己的原型(`.prototype`), 而物件的原型還會有其原型(`.__proro__`), 直到該原型的原型(`.__proro__`)輸出為 null 為止。 這過程就稱為一個原型鏈。 原型主要用於建構函數中的方法。 當創建建構函數後, 在 new 出來的物件中就可以調用到建構函數中的方法與其原型中的方法, 我們可以通過原型去給建構函數建立方法, 而不需要在每個 new 出來的物件中重複創建新的方法, 通過原型建立的方法可以讓所有 new 出來的物件都使用到, 假設你在每個 new 出來的物件中撰寫方法, 會導致佔用非常多記憶體位置, 但如果你是使用原型方式建立方法, 就只需要佔用一個記憶體位置了。 [參考文章 - 內附程式碼說明,較清楚](https://blog.techbridge.cc/2017/04/22/javascript-prototype/) ### == vs === 雙等於:先轉換型別後比較,其值若相等則返回 true 全等於(三等於):嚴格比較,不會轉換型別,一開始先判斷型別,若不相等會直接返回 false 幾個要注意的判斷: - NaN == NaN // false - undefined === null // false - undefined == null // true - +0 === -0 // true 要注意的重點: - object 與 array 判斷是否等於,看的會是其參考地址是否相等。 ### by value vs by reference(傳值與傳參) 可以簡單理解為物件與函數是傳參考 其他變數字串陣列等都是傳值 傳值表示每個東西都是獨立存在 只是把值複製一份給其他東西使用 傳參考則表示每個東西所指向的是同一個記憶體位置 修改其值就會導致共享該記憶體的其他東西一起被修改 [參考文章](https://ithelp.ithome.com.tw/articles/10191057) ## JS(ES6) ### ES6 新增了哪些技術 class 物件導向設計, 箭頭函數, 函數參數預設值, export&import, 模板字串(使用反引號拼接字串), 解構賦值, promise, let&const, Symbol, ...展開/其餘運算符。 [參考文章 - 還有很多其他技術](https://zhuanlan.zhihu.com/p/87699079) ### promise 首先我們知道 JS 是屬於同步的程式語言,當遇到非同步事件時,他會先執行所有的同步事件,等所有同步事件處理完畢後才會執行那些非同步事件, JS 的非同步事件有計時器或 AJAX 事件等,假設需要在確保非同步事件完成後才繼續執行其他程式碼,就需要使用 promise 了。promise 的強大特性是他可以通過鏈接(.then().catch())執行下一個動作,而不需要使用回調地獄。 [參考文章](https://wcc723.github.io/development/2020/02/16/all-new-promise/) ### 解構賦值 可以簡單理解為等號右方的資料會對應到左方同位置的資料。 細節部分比較繁雜可以直接閱讀參考文章。 [參考文章](https://eyesofkids.gitbooks.io/javascript-start-from-es6/content/part4/destructuring.html) ### var let const var 的作用域是全域 let 的作用域是 block 即每個 {} 中獨立存在 const 是常數 聲明後即不能被修改 且在聲明時若沒有賦值會報錯 [參考文章](https://wcc723.github.io/javascript/2017/12/20/javascript-es6-let-const/) ### 為什麼要用箭頭函數 除了他可以有效的簡化寫法外,其 this 是完全綁定在語彙上的位置,也就是說在箭頭函數裡的 this 永遠都是語意上的 this ,不管是誰呼叫他,或是被如何 bind 、 call 、 apply ,他永遠都是拿到原先作用域的 this 。 這樣的做法可以讓你在物件上使用回調時,拿到正確的 this ,而非一言不合就 undefined [參考文章 - 箭頭函式與傳統函式之差異](https://hsuchihting.github.io/javascript/20200813/289345854/) ## Vue.js ### 為什麼要用 Vuex 在大型專案或專案結構較複雜時段於管理資料會方便很多 如兩個兄弟組件之間的溝通 在沒有使用 Vuex 的情況下會變得很麻煩 但使用 Vuex 就可以統一管理資料結構了 ### components 是否可以擁有自己的 style 可以,只需要在 style 標籤加上 scoped 屬性 即可把樣式限制在該元件中做使用 ## ajax ### API 串接方法有哪些 GET: 讀取(獲取)資源 PUT: 更新資源 DELETE: 刪除資源 POST: 創建資源 PATCH: 更新部分資源 ### SSR 是什麼 他的中文翻作伺服器渲染,執行過程如下: `輸入網址 => 發送請求 => 接收響應(響應回來的直接是一個頁面) => 瀏覽器解析渲染畫面` 另外有一個 CSR 中文翻作客戶端渲染,執行過程如下: `輸入網址 => 發送請求 => 接收響應(響應回來的是一個模板頁面 需通過 js 解析後才能渲染) => 在響應回來的模板中透過 js 逐行解析,當遇到 ajax 時再發送新的請求 => 最後渲染畫面` [參考文章 - 這篇跪著讀](https://hsiangfeng.github.io/other/20210529/2519649612/?fbclid=IwAR0OvTvBZln1dR8wWl6Gk1sKh4pd4wepTb1RI4DOXCFgni1gQ4UTNdtl8Jg) ### 從輸入 url 到畫面渲染發生了什麼事 瀏覽器輸入網址後會先查找 IP 地址, 接著通過 TCP 三次握手確認雙方已建立好連結, 然後瀏覽器就可以針對 IP 地址發送請求並等待響應, 此時響應會被拆成很多個封包,最後封包合併後傳送 200 ok 表示響應接收成功,瀏覽器再解析響應數據並渲染畫面, 最後會進行四次揮手道別中斷雙方連結。 [參考文章 - 這篇也很強大繼續跪著看](https://hulitw.medium.com/learning-tcp-ip-http-via-sending-letter-5d3299203660) ### http vs https https 在應用層與傳輸層之間多了一道 SSL 加密憑證 這個 SSL 可以防止在發送 HTTP 的 GET 或 POST 請求的時候被攔截獲取到資訊 ### 何謂 AJAX 非同步 通常都如何實踐 在客戶端向伺服器發送請求時不需等待響應,可以繼續執行其他動作,且接收到響應後不會刷新整個頁面,而是利用 JS 與 DOM 進行局部內容替換,這就是所謂的 AJAX 非同步。 最原生的實現方式是使用 XMLHttpRequest 物件 但因為寫法過於繁雜 後來 jQuery 又推出了 `$.ajax()` 方法 接著是結合 ES6 promise 的 `fetch()` 方法 最後出現了依賴於 ES6 promise 的 axios 輕量級套件 [參考文章](https://tw.alphacamp.co/blog/ajax-asynchronous-request) ## CSS ### box-model 包含 font-size padding border 嗎 沒有包含文字大小。 盒模型指的是:寬度、高度、邊框、內距與外距這五者。 ### z-index 使用時機? 有設置 position(非預設值 static)的元素才能使用 z-index 屬性 且 z-index 的值須為整數 ### box-sizing: border-box; 用在哪?你平常會用嗎? 幾乎都會使用到,可以方便的計算元素大小,省去很多煩雜的計算。 他讓尺寸不再只是寬度與高度,而是寬度與高度外,又包含了 padding & border [參考文章](https://titangene.github.io/article/css-box-sizing.html) ### CSS 權重順序? HTML tag 1分 class 10分 id 100分 行內 style 1000分 !imporant 10000分 [參考文章](https://ithelp.ithome.com.tw/articles/10196454) ### 會如何管理 CSS 結構 會用哪些設計模式、技術優化它? 大型專案上較常使用 SCSS 撰寫 使用語意化方式命名 多數使用小駝峰命名法 了解 OOCSS 的結構與樣式分離、容器與內容分離概念 了解 BEM SMACSS 等概念 [參考文章 - CSS 模組化有哪些方法](https://cythilya.github.io/2018/06/05/css-methodologies/) ### class 與 ID 的差異? 通常 id 都是跟 js 配合或作為錨點用的 且在 CSS 上會盡量避免使用 id ,因為兩者權重差很多 另外就是相同 class 可以有多個,但相同 id 只可以有一個 [參考文章](https://medium.com/@small2883/html%E7%9A%84id-class%E5%B1%AC%E6%80%A7%E4%BB%8B%E7%B4%B9-css-%E7%9A%84-class-%E5%92%8C-id-%E5%85%A9%E8%80%85%E6%9C%89%E4%BD%95%E5%B7%AE%E7%95%B0-25ce5315ece) ps 文章參考就好,親測 id 可以用數字,只是在樣式中用#數字會報錯,但JS不會出錯 ### 是否習慣用格線系統? 我個人通常是有使用 Bootstrap 就會用 但基本上會看設計稿需求 平常手刻不會刻意製作 但不排斥 也有嘗試手寫過格線系統 [參考文章 - 格線佈局的概念](https://developer.mozilla.org/zh-TW/docs/Web/CSS/CSS_Grid_Layout/Basic_Concepts_of_Grid_Layout) ### 通常如何解決跨瀏覽器問題? 使用 Sass 編譯 + CSS Reset [參考文章 - 我其實沒研究過這問題](https://www.gushiciku.cn/pl/ptVs/zh-tw) ### display 有哪些值 none、inline、inline-block、block、inline-flex、flex、table、inline-grid、grid、unset [參考文章 - 其他我不知道,但還有好多](https://developer.mozilla.org/en-US/docs/Web/CSS/display) ### 兩種 CSS Reset 差異 Normalize 保留一些常用的預設樣式 如列表 標題等 Reset 把所有瀏覽器預設樣式都清空了 [參考文章](https://www.itread01.com/content/1541043546.html) ### 描述 svg gif png 差異與使用時機: PNG:有效減少圖片檔案大小以及保留照片透明元素,圓角圖片就很適合使用。 SVG:向量檔,縮放不失真,用在 logo 很方便。 GIF:可以製作小型動態圖片檔案,適用於色彩簡單的檔案,也有支援透明背景。 [參考文章](https://www.cool3c.com/article/146971) ### rem 是什麼 rem 是指 root 層級的文字大小,即 html 標籤的大小,通常是 16px ,可以通過設定 html 的 font-size 影響到所有使用 rem 單位的尺寸。 [參考文章](https://www.hexschool.com/2016/01/02/2016-08-08-em-vs-rem/) ### !important 使用時機 用於強制性覆蓋樣式,因為 important 的權重在 CSS 中是最高的,可以通過這種方式強制把想要的樣式覆蓋上去。 ## 上機考 ### 做出 CSS 的三種水平垂直居中方法 ```css= /* 設在子元素,內容只有一行文字時才適用 */ .method1 { margin: auto; line-height: 父層高度; } /* 設在父元素 */ .method2 { display: flex; justify-content: center; align-items: center; } /* 設在子元素 */ .method3 { position: absolute; /*父層需加上 position: relative; */ transform: translate(-50%, -50%); top: 50%; left: 50%; } /* 設在父元素 */ .method4{ display: flex; /* 子元素設置 margin: auto; */ } ``` [參考文章 - CSS 多種居中方法](https://medium.com/%E9%BA%A5%E5%85%8B%E7%9A%84%E5%8D%8A%E8%B7%AF%E5%87%BA%E5%AE%B6%E7%AD%86%E8%A8%98/%E9%80%8F%E9%81%8E-css-%E5%9E%82%E7%9B%B4%E7%BD%AE%E4%B8%AD%E7%9A%84%E5%95%8F%E9%A1%8C%E8%88%87%E8%A7%A3%E6%B1%BA%E6%96%B9%E5%BC%8F-bf2fd91af3f9) ### 原生 JS Todolist 題目要求: 可以新增項目 完成的項目會有刪除線 可以刪除單個項目 網頁重整也能夠保存資料(使用 cookie or localstorage) ```javascript= const data = JSON.parse(localStorage.getItem("datas")) || []; oInput.addEventListener("keyup", function (e) { if (e.keyCode == 13) { let obj = {}; obj.content = oInput.value; obj.checked = false; data.push(obj); oInput.value = ""; init(); } }); oUl.addEventListener("click", function (e) { let i = e.target.getAttribute("data-num"); if (e.target.nodeName == "A" && e.target.getAttribute("class") == "delete") { e.preventDefault(); data.splice(i, 1); } else { data[i].checked = !data[i].checked; } init(); }); function init() { let str = ""; data.forEach(function (item, i) { if (item.checked) { str += `<li data-num=${i}><del>${item.content}</del><a class="delete" href="#" data-num=${i}>刪除</a></li>`; } else { str += `<li data-num=${i}>${item.content}<a class="delete" href="#" data-num=${i}>刪除</a></li>`; } }); oUl.innerHTML = str; localStorage.setItem("datas", JSON.stringify(data)); } init(); ``` [參考文章 - localstorage 用法](https://medium.com/%E9%BA%A5%E5%85%8B%E7%9A%84%E5%8D%8A%E8%B7%AF%E5%87%BA%E5%AE%B6%E7%AD%86%E8%A8%98/javascript-localstorage-%E7%9A%84%E4%BD%BF%E7%94%A8-e0da6f402453) ### 原生 JS 做出 99乘法表 ```javascript= function test() { for (var i = 1; i < 10; i++) { for (var j = 1; j < 10; j++) { console.log(`${i} * ${j} = ${i * j}`); } } } test(); ``` ### 用 AJAX 撈取前十筆資料 ```javascript= // XML 最基礎的原生方法 var req = new XMLHttpRequest(); req.open("GET", '網址'); req.send(); req.onreadystatechange = function () { if (this.readyState == 4 && this.status == 200) { var data = JSON.parse(req.responseText); for (i = 0; i < 10; i++) { console.log(data[i].name); } } }; // ES6 的 fetch 方法 fetch('網址') .then(res => { return res.json(); }) .then(result => { result.data.forEach((item, i) => { if(i < 10) { console.log(item.name); } }) }); // 引用 axios 方法 axios.get('網址') .then((res) => { res.data.forEach((item, i) => { if(i < 10) { console.log(item.name); } }) }); ```