原本想分一期二期寫的,想一想第一篇博客要特殊點,哈哈。那就像流水帳同樣一直往下寫吧。css
題目不分前後不分重要,包括本身面試的一些比較有表明的知識點和別處看到的面試點。html
一、JS繼承(原型鏈繼承)前端
繼承這個題目其實挺大的,繼承的方式也有不少種,目前我也不是所有理解各類方法的繼承,之後摸透了寫個繼承專題吧。vue
直接掛代碼nginx
1 // 聲明 人 這個類
2 function People(sex) { 3 this.sex = sex; 4 this.saySex = function saySex() { 5 console.log(`個人性別是${this.sex}`); 6 } 7 } 8
9 // 建立女性
10 function Woman() { 11
12 } 13 Woman.prototype = new People('女'); 14 // 這樣Woman這個類別的原型對象是 人 構造的;
15 const girl = new Woman(); 16 girl.saySex();
二、什麼是閉包,手寫一個閉包面試
這又是一個很大的問題啊,哈哈之後寫個閉包專題詳解。其實簡單理解一下就是:一個函數記住並可以訪問他所在的詞法做用域時,即便函數在其餘的做用域調用,這就產生了閉包。segmentfault
貼一個典型的閉包例子跨域
1 function fn () { 2 const str = 'HelloWorld'; 3 function bar () { 4 console.log(str); 5 }; 6 return bar; 7 } 8
9 var fo = fn(); 10 fo() // 輸出HelloWorld;
fo其實就是fn做用域中的函數bar,可是fo在調用的時候並非在fn內部做用域中調用,即便如此依舊訪問到了str這個變量,在這裏產生了閉包。數組
三、使用閉包實現數據緩存瀏覽器
廢話很少說,直接上代碼
1 const foo = (() => { 2 const cache = {}; 3 return { 4 getCache(key) { 5 return cache[key]; 6 }, 7 setCache(key, val) { 8 cache[key] = val; 9 }, 10 }; 11 })(); 12
13 foo.setCache('name', 'dsj'); 14 console.log(foo.getCache('name'));
直接用IIEF返回了一個對象,只暴露出兩個方法用來存和取,在這裏就用了閉包,函數調用時所在的詞法做用域並非函數聲明所在的域,依舊訪問到了想要的變量,產生了閉包。
四、url->頁面生成過程
分爲兩大塊來答
一個是網絡http層面
首先輸入url,進行DNS解析
DNS解析首先會進行緩存查找,分爲如下5部
1. 瀏覽器先會在瀏覽器緩存中去查找該域名是否有對應的IP信息
2. 接着搜索操做系統中有沒有相應的緩存信息,好比hosts文件中
3. 接着在路由器緩存中進行查找
4. 接着從網絡服務商那邊的緩存信息中進行查找
5. 以上方法若是找到就直接返回IP信息,若是都沒有找到就會發起一個迭代DNS解析請求
1> 向根域名服務器發送請求,該服務器沒有域名的所有信息,可是能夠返回頂級域名服務器地址,如com域的頂級域名服務器地址
2> 接着向頂級域名服務器發送請求最終獲取IP地址
6. 本地域名服務器(也就是網絡服務商)將IP返回給操做系統同時把該IP緩存起來
7. 操做系統把IP返給瀏覽器,同時把IP緩存起來
8. 最後瀏覽器獲得了IP開始創建鏈接
創建鏈接 => 3次握手
客戶端發送帶有SYN標誌的數據包給服務端(請求鏈接)
服務端發送帶有SYN/ACK標誌的數據包給客戶端(贊成創建鏈接)
客戶端發送帶有ACK標誌的數據包給服務端(開始鏈接)
PS:若是三次握手中某次某一方沒接到信號,TCP協議會要求從新發送信號
創建鏈接後會開始數據的傳遞,好比相應的js文件,css文件,圖片文件等。同時瀏覽器開始渲染頁面
當數據傳遞所有完成,開始四次揮手,斷開鏈接。
其中瀏覽器渲染頁面過程以下:
解析HTML,遍歷文檔節點生成DOM樹;
解析CSS文件生成 CSSOM規則樹;
而後DOM樹和和CSSOM樹結合生成渲染Render樹;
渲染樹開始佈局,計算每一個節點的位置大小等信息;
而後繪製每一個節點到屏幕上
PS:在生成DOM樹的過程當中,若是遇到script標籤,會產生阻塞;
若是JS腳本中還對CSSOM進行了操做,瀏覽器會延遲腳本的執行和DOM的渲染,先生成CSSOM樹;
渲染樹進行繪製的時候,涉及到了重繪和迴流
迴流:當元素的大小布局等發生改變的時候就會觸發迴流,迴流必然引起重繪;
重繪:當元素的顏色背景之類發生改變的時候觸發重繪
每一個頁面必然發生一次迴流,因此必然有一次重繪,也就是頁面加載渲染的時候;
由於迴流會致使渲染樹從新構建,花銷較大,因此應該儘量的避免迴流;
好比減小DOM操做等。
五、JS的數據類型,基本類型哪些,和引用類型的區別
數據類型: null, undefined, Number, String, Object, Boolean, Symbol
基本類型: undefined, null, boolean, number, String, 指的是保存在棧內存中的簡單數據
引用類型: Array, Object等對象類型,指的是保存在堆內存中的對象,變量實際保存的是一個指針,指針指向內存中的一個位置,該位置保存對象
六、節流和防抖
節流和防抖都是用來預防某些操做重複觸發函數,好比重複請求,形成性能卡頓問題。
防抖原理:不管如何觸發事件,在n秒後我才執行這個函數,若是n秒內再次觸發該函數就以新觸發的爲準,n秒後再執行
1 function debounce(fuc, delay = 300) { 2 let timer = null; 3 return function fn() { 4 const self = this; // 保存調用函數的this,防止setTimeout改變this指向
5 const args = arguments; // 保存函數調用的傳參
6 if (timer) clearTimeout(timer); // 清除定時器
7 timer = setTimeout(() => { 8 fuc.apply(self, args); // 函數指定this,同時把參數傳入
9 }, delay); 10 }; 11 } 12
13 // 好比給一個div綁上一個mousemove事件,
14 function move(event) { 15 console.log(1, event); 16 } 17 div.onmousemove = debounce(move);
能夠直接試一下。
節流原理:一段時間內某個函數只能觸發一次,好比3秒內fn函數只能觸發一次,便會在0秒是觸發一次該函數直至3秒都不會再次觸發
目前主流採用時間戳或者定時器來實現
時間戳方法會讓函數馬上執行一次,可是在中止觸發的時候並不會再執行了,以下
1 function throttle(fuc, delay = 300) { 2 let timer = 0; 3 return function fn() { 4 const self = this; 5 const now = +new Date(); 6 const args = Array.prototype.slice.apply(arguments); 7 if (now - timer > delay) { 8 fuc.apply(self, args); 9 timer = now; 10 } 11 }; 12 }
七、手寫bind實現
這個問題真的很是常見,可是知識點還蠻多的。寫了一年業務代碼不多用到 bind, call, apply這些函數,也對這些理解得不咋深,結果面試問到就露餡,回過頭來多理解一下,多寫寫也搞懂了一些。
首先講一下bind的用法,bind通常用來改變一個函數中this的指向,該方法會返回改變this後的函數,如下是一個範例
1 var obj = { 2 name: '阿狸', 3 age: '18', 4 }; 5 function fuc(sex, from) { 6 console.log(`我叫${this.name}`); 7 console.log(`我是${sex}孩子,來自${from}`); 8 console.log(`今年${this.age}歲啦`); 9 return '哈哈哈哈哈'; 10 } 11
12 fuc.bind(obj, '女')('瓦羅蘭大陸');
輸出確定是沒啥問題的,能夠直接貼f12試下,用法試了一回那麼怎麼手寫一個實現怎麼作呢?
1 Function.prototype.bindHandler = function (ctx) { 2 const self = this; 3 // ctx也就是this要指向的對象
4 return function() { 5 self.apply(ctx); 6 } 7 } 8 // 試驗一下
9 fuc.bindHandler(obj, '女', '瓦羅蘭大陸'); 10 // 發現結果有點不對,參數沒有拿到,並且還有一個點沒考慮到,fuc函數若是返回一個值,能拿到嗎?明顯不能,改進一下函數
11 Function.prototype.bindHandler = function (ctx) { 12 // ctx也就是this要指向的對象
13 const self = this; 14 // 保存一下除了ctx之外傳進來的參數,
15 const args = Array.prototype.slice.apply(arguments, [1]); 16 return function() { 17 const argsOther = Array.prototype.slice.apply(arguments); 18 const result = self.apply(ctx, args.concat(argsOther)); 19 return result; 20 } 21 } 22 // 再試一下, 徹底objk。
23 fuc.bindHandler(obj, '女', '瓦羅蘭大陸'); 24 fuc.bindHandler(obj, '女')('瓦羅蘭大陸');
搞定!
八、手寫trim實現
其實就是去掉首尾空格,一個簡單的正則搞定
1 String.prototype.trimHandler = function() { 2 return this.replace(/(^\s*)|(\s*$)/g, ''); 3 }
先寫到這裏,下次再來更新!
/* ----------------------------------------------- 2019/3/4 更新------------------------ */
又回來啦,最近這段時間一直在面試。運氣不錯,拿到offer了。
躺了一天不能懶了,回來記錄下面試過程當中印象比較深的一些題吧。
續上
九、如何理解Nginx代理解決跨域
跨域問題在前端算是個必考點,其實不想說這個的,一個基礎的前端開發是必然要了解跨域相關的知識。
不過面試次次問,那就說一下個人理解,但願給面到這個問題的同窗一點幫助。
其實在這以前還得理解一下什麼是 "同源策略",網上其實有不少解釋了,不過我但願同窗們記住一點,這是瀏覽器的一個功能!(劃重點)
也就是說解決跨域,就爲了繞過瀏覽器的這個限制。
在這裏貼一個連接,裏面有講到同源策略和一些廣泛的跨域方法,我就不贅述了(http://www.javashuo.com/article/p-gnbvzzoy-bh.html)。
我想說的主要是如今咱們開發中很經常使用的配置nginx代理去解決跨域。
原理其實不復雜,就是繞過了瀏覽器的同源策略去解決。
正常來講咱們寫代碼發送請求給目標地址,會產生跨域,那麼咱們能夠作箇中轉站服務器,讓這個代理服務器能夠咱們的請求,而後這個代理服務器再轉發給目標服務器。
服務器和服務器之間是能夠直接請求的,以前說過同源策略是瀏覽器的功能嘛。這樣就繞過了服務器的限制,解決了跨域,其實就這麼簡單。
不過通常來講正式項目上線,服務器會給配置一個網關,這個網關去過濾一部分請求,才能到目標服務器。否則服務器的安全性就過低了,任意請求被攻擊怎麼辦?
這樣咱們在開發的時候就能夠解決跨域問題,快樂的開發。至於上線,也能夠配置Nginx代理服務器的,而後在服務器端改一下配置容許對應代理的請求便可,這部分通常是運維的工做。
十、JS實現事件綁定的幾種方法
這個問題簡單,可是常常讓你手寫一個兼容綁定事件的方法,能夠瞟一眼。
假設有個函數
// 假設有個函數
function fn() { alert('Hello World !'); } // 直線元素綁定 // html代碼
<div id='btn' @onclick='fn()'>按鈕</div>
// 或者經過js綁定 // 這是dom0級事件綁定,此方法只能綁定一個事件,綁定多個會被覆蓋
document.getElementById('btn').onclick = fn; // dom2級事件綁定 // 若是綁定多個函數,會根據綁定的順序來執行
document.getElementById('btn').addEventListener('click', fn, false); // 其中這個布爾值,true指的是在事件捕獲階段調用方法,false就是在冒泡階段,默認爲false
不過addEventListener有兼容問題,在IE8及如下版本要用attachEvent這個方法,那麼最後貼一個兼容方法。
function addEventHandler(obj, type, fn) { // obj是元素對象,type是要綁定的事件類型,fn是函數
// 能力判斷
if(element.addEventListener) { // Chrome,Firefox等瀏覽器,IE9及以上
element.addEventListener(type, fn, false); } else if (element.attachEvent) { // IE8及如下
element.attachEvent("on" + type, fn); } else { element["on" + type] = fn; } }
那麼若是是移除事件呢?也有兼容性方法的,本身想一想怎麼寫吧!基本相似。
十一、說說對var, let, const的理解
每次面對這種XXX的理解的問題就很頭大,只能盡力去把本身知道的表達出來。
我當時的回答:
var能夠重複聲明,存在聲明提高,綁定全局做用域,例:
// 通常來講直接
console.log(test); // 是會報錯的,由於test沒有聲明。這你們都理解
// 那麼下面這段代碼缺會輸出undefined
console.log(test); var test = 1; // 由於上面這段代碼等同於
var test; console.log(test); test = 1; // var的變量提高會把聲明變量提高到"當前做用域"的頂部 // 同時var還會綁定全局做用域,直接用代碼理解,前提是var在全局做用域中聲明使用
var test = 'test'; console.log(test); // test
console.log(window.test); // test
可是const和let不存在聲明提高,也不會綁定全局做用域,那麼let和const之間也有區別
const用於聲明常量,值被設定之後不能修改,不然報錯,
不過const能夠理解爲不可修改綁定,能夠修改其屬性值,看代碼
const num = 1; num = 2; // 報錯
const obj = { name: '張三' }; obj.name = '李四'; obj.sex = '男'; console.log(obj); // { name: '李四', sex: '男' };
並且let和const還有一個特性,臨時死區(Temporal Dead Zone),簡寫爲 TDZ (劃重點!!!)。
以前在瞭解到let和const不會提高的時候,我就想過一個問題
var name = '張三'; function fn() { console.log(name); } fn(); // 這裏會輸出張三,由於js的做用域鏈,在本身的做用域找不到該變量時會去到上一級做用域找該變量
// 那麼若是是如下狀況
const name = '張三'; function fn() { console.log(name); // 此時fn的做用域未聲明name
const name = '李四'; // 不會提高變量
console.log(name); } fn();
當初個人想法是,第一句console未聲明name,那麼會去上一級做用域找name,輸出「張三」。
第二句console由於上一句代碼聲明瞭name,輸出李四。
結果以上想法是錯的,程序直接報錯,就是由於臨時死區的存在纔會報錯。
在fn中只要const或者let聲明瞭一個變量,雖然不會提高到頂部,但會把變量放在DTZ中。
這樣在聲明以前訪問變量就會報錯,執行過變量聲明語句以後纔會從DTZ中移出來,我把這理解爲一種另類的提高。
十二、瀏覽器緩存禁用
實際開發中有這樣一個場景,由於http的緩存機制,項目打包上線之後,可能頁面加載的js是瀏覽器中的緩存,並非最新的js文件,總不能告訴用戶去手動清緩存吧?那有什麼辦法解決呢。
其實我對http緩存這一塊瞭解不深,可是這個問題我以爲很實用,也是我項目中確實碰到的問題,而後查了下解決方案,有兩種比較經常使用:
(一) html裏面經過meta標籤去禁用緩存
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="Cache-Contro" content="no-cache, must-revalidate">
<meta http-equiv="expires" content="0">
我瞭解不深怕解釋不清,就不詳說,這幾個屬性值你們能夠本身查查瞭解,這樣印象也會深一些。
(二)
css和js帶參數(形如.css?time=與.js?time=) ,加一個隨機數或者時間戳,這樣請求資源的路徑變了,就不會加載緩存的資源。
主要詳寫的是我容易忘或者比較特別的問題,固然面試還有不少問題,網上有很是多的解釋也沒有什麼特別好說的,
我在此列舉一些,看這篇帖子的同窗們也能夠去了解一下。
雖然已經準備入職新公司,可是日常會看看有什麼好玩的題,會分享在這裏,或者另外寫,下次再來更新!
有什麼問題均可以給我留言哈~
/* ----------------------------------------------- 2019/3/17 更新------------------------ */