評論區可以糾錯完善,也可以留言面試題目
css部分
rem原理
- rem佈局的本質是等比縮放,一般是基於寬度,假設將螢幕寬度分為100份,每份寬度是1rem,1rem的寬度是螢幕寬度/100,,然後子元素設定rem單位的屬性, 通過改變html元素的字型大小,就可以設定子元素的實際大小。
- rem佈局載入閃爍的問題
- 解決方案,媒體查詢設定根元素字型大小,比如設計稿是750px;對應的開發方式是1rem=100px,那375px的font-size 大小就是50px(具體方法可以百度一下)
- 比rem更好的方案(缺點相容不好)
- vw(1vw是視口寬度的1%,100vw就是視口寬度),vh(100vh就是視口高度)
實現三欄佈局
(兩側定寬,中間自適應)
- 採用了 absolute,導致父元素脫離了文件流,那所有的子元素也需要脫離文件流。如果頁面複雜,那開發的難度可想而知
- 利用浮動 當中間內容高於兩側時,兩側高度不會隨中間內容變高而變高
- 彈性盒子佈局
- 利用負邊距和浮動,實現起來比較複雜
- 利用網格佈局
.container {
display: grid;
grid-template-columns: 100px auto 200px;
}
複製程式碼
BFC(塊級格式化上下文)
- BFC 的原理
其實也就是 BFC 的渲染規則(能說出以下四點就夠了)。包括:
- BFC 內部的子元素,在垂直方向,邊距會發生重疊。
- BFC在頁面中是獨立的容器,外面的元素不會影響裡面的元素,反之亦然。
- BFC區域不與旁邊的float box區域重疊。(可以用來清除浮動帶來的影響)。
- 計算BFC的高度時,浮動的子元素也參與計算。
- 如何生成BFC
- 方法1:overflow: 不為visible,可以讓屬性是 hidden、auto。【最常用】
- 方法2:浮動中:float的屬性值不為none。意思是,只要設定了浮動,當前元素就建立了BFC。
- 方法3:定位中:只要posiiton的值不是 static或者是relative即可,可以是absolute或fixed,也就生成了一個BFC。
- 方法4:display為inline-block, table-cell, table-caption, flex, inline-flex
- BFC應用
- 阻止margin重疊
- 可以包含浮動元素 —— 清除內部浮動(清除浮動的原理是兩個div都位於同一個 BFC 區域之中)
- 自適應兩欄佈局
- 可以阻止元素被浮動元素覆蓋
flex(面試常問,略)
js部分
call, apply, bind區別? 怎麼實現call,apply方法
Function.prototype.myBind = function(content) {
if(type of this !='function'){
throw Error('not a function')
}
let _this = this;
let args = [...arguments].slice(1)
let resFn=function(){
return _this.apply(this instanceof resFn?this:content,content.concat(...arguments))
}
return resFn
};
/**
* 每個函式都可以呼叫call方法,來改變當前這個函式執行的this關鍵字,並且支援傳入引數
*/
Function.prototype.myCall=function(context=window){
context.fn = this;//此處this是指呼叫myCall的function
let args=[...arguments].slice(1);
let result=content.fn(...args)
//將this指向銷燬
delete context.fn;
return result;
}
/**
* apply函式傳入的是this指向和引數陣列
*/
Function.prototype.myApply = function(context=window) {
context.fn = this;
let result;
if(arguments[1]){
result=context.fn(...arguments[1])
}else{
result=context.fn()
}
//將this指向銷燬
delete context.fn;
return result;
}
複製程式碼
函式柯里化
js繼承,建構函式,原型鏈,建構函式、原型鏈組合式繼承,寄生式組合繼承,Object.create polyfill;
陣列去重
[...new Set(arr]
複製程式碼
var arr = [1,2,1,2,3,5,4,5,3,4,4,4,4],
init=[]
var result = arr.sort().reduce((init, current)=>{
console.log(init,current)
if(init.length===0 || init[init.length-1]!==current){
init.push(current);
}
return init;
}, []);
console.log(result);//1,2,3,4,5
複製程式碼
防抖節流
var deBounce=function(fn,wait=300){
let timer
return function(){
if(timer){
clearTimeOut(timer)
}
timer=setTimeOut(()=>{
fn.apply(this,arguments)
},wait)
}
}
var throttle=function(fn,wait=300){
let prev=+new Date();
return function(){
const args=argument,
now=+new Date();
if(now>last+wait){
last=now;
fn.apply(this,args)
}
}
}
複製程式碼
實現Promise思路
//0 pending , 1 resolve,2 reject
function Promise(fn) {
...
this._state = 0 // 狀態標記
doResolve(fn, this)
}
function doResolve(fn, self) {
var done = false // 保證只執行一個監聽
try {
fn(function(value) {
if (done) return
done = true
resolve(self, value)
},
function(reason) {
if (done) return;
done = true
reject(self, value)
})
} catch(err) {
if (done) return
done = true
reject(self, err)
}
}
function resolve(self, newValue) {
try {
self._state = 1;
...
}
catch(err) {
reject(self, err)
}
}
function reject(self, newValue) {
self._state = 2;
...
if (!self._handled) {
Promise._unhandledRejectionFn(self._value);
}
}
複製程式碼
實現深拷貝
funtion deepCopy(obj){
let result;
if(typeofObj=='object'){
//複雜資料型別
result=obj.constructor==Array?[]:{}
for (let i in obj){
result[i]=typeof obj[i]=='object'?deepCopy(obj[i]):obj[i]
}
}else{
//簡單資料型別
result=obj
}
return result
}
複製程式碼
正則實現千位分隔符
function commafy(num) {
return num && num
.toString()
.replace(/(\d)(?=(\d{3})+\.)/g, function($0, $1) {
return $1 + ",";
});
}
console.log(commafy(1312567.903000))
複製程式碼
js事件迴圈
javascript是單執行緒語言,任務設計成了兩類,同步任務和非同步任務 同步和非同步任務分別進入不同的執行“場所”,同步進入主執行緒,非同步進入Event Table並註冊函式。當指定的事情完成時,Event Table會將這個函式移入Event Queue。主執行緒內的任務執行完畢為空,回去了Event Queue讀取對應的函式,進入主執行緒。 上述過程會不斷重複,也就是常說的Event Loop(事件迴圈)。 但是,JS非同步還有一個機制,就是遇到巨集任務,先執行巨集任務,將巨集任務放入event queue,然後再執行微任務,將微任務放入eventqueue,但是,這兩個queue不是一個queue。當你往外拿的時候先從微任務裡拿這個回撥函式,然後再從巨集任務的queue拿巨集任務的回撥函式 巨集任務一般包括:整體程式碼script,setTimeout,setInterval。 微任務:Promise,process.nextTick
事件流機制,事件委託(略) event.targe和event.currentTarget的區別
- 事件捕獲,
- 處於目標階段,
- 事件冒泡階段
- event.target返回觸發事件的元素
- event.currentTarget返回繫結事件的元素
new的過程以及實現new
//方法1
function create(){
//1.建立一個空物件
let obj={}
//2.獲取建構函式
let Con=[].shift.call(arguments)
//3.設定空物件的原型
obj._proto_=Con.prototype
//4.繫結this並執行建構函式,給新物件新增屬性和方法
let result=Con.apply(obj,arguments)
//5.確保返回值為物件
return result instanceof Object?result:obj
}
//方法2
//通過分析原生的new方法可以看出,在new一個函式的時候,
// 會返回一個func同時在這個func裡面會返回一個物件Object,
// 這個物件包含父類func的屬性以及隱藏的__proto__
function New(f) {
//返回一個func
return function () {
var o = {"__proto__": f.prototype};
f.apply(o, arguments);//繼承父類的屬性
return o; //返回一個Object
}
}
複製程式碼
封裝ajax
/* 封裝ajax函式
* @param {string}opt.type http連線的方式,包括POST和GET兩種方式
* @param {string}opt.url 傳送請求的url
* @param {boolean}opt.async 是否為非同步請求,true為非同步的,false為同步的
* @param {object}opt.data 傳送的引數,格式為物件型別
* @param {function}opt.success ajax傳送並接收成功呼叫的回撥函式
*/
function myAjax(opt){
opt = opt || {};
opt.method = opt.method.toUpperCase() || 'POST';
opt.url = opt.url || '';
opt.async = opt.async || true;
opt.data = opt.data || null;
opt.success = opt.success || function () {}
let xmlHttp = null;
if (XMLHttpRequest) {
xmlHttp = new XMLHttpRequest();
}else{
xmlHttp =new ActiveXObject('Microsoft.XMLHTTP')
}
let params;
for (var key in opt.data){
params.push(key + '=' + opt.data[key]);
}
let postData = params.join('&');
if (opt.method.toUpperCase() === 'POST') {
xmlHttp.open(opt.method, opt.url, opt.async);
xmlHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=utf-8');
xmlHttp.send(postData);
}else if (opt.method.toUpperCase() === 'GET') {
xmlHttp.open(opt.method, opt.url + '?' + postData, opt.async);
xmlHttp.send(null);
}
xmlHttp.onreadystatechange= function () {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
opt.success(xmlHttp.responseText);//如果是json資料可以在這使用opt.success(JSON.parse( xmlHttp.responseText))
}
};
}
複製程式碼
url拿引數
var url = "http://www.taobao.com/index.php?key0=0&key1=1&key2=2";
function parseQueryString(url){
var str = url.split("?")[1], //通過?得到一個陣列,取?後面的引數
items = str.split("&"); //分割成陣列
var arr,name,value;
for(var i=0; i<items.length; i++){
arr = items[i].split("="); //["key0", "0"]
name = arr[0];
value = arr[1];
this[name] = value;
}
}
var obj = new parseQueryString(url);
alert(obj.key2)
複製程式碼
HTTP部分
http協議
HTTP協議(超文字傳輸協議)
- 1.0 協議缺陷:
- 無法複用連結,完成即斷開,重新慢啟動和 TCP 3次握手
- head of line blocking: 線頭阻塞,導致請求之間互相影響 1.1 改進:
- 長連線(預設 keep-alive),複用
- host 欄位指定對應的虛擬站點
- 新增功能:
- 斷點續傳
- 身份認證
- 狀態管理
- cache 快取
- Cache-Control
- Expires
- Last-Modified
- Etag
- 2.0:
- 多路複用
- 二進位制分幀層: 應用層和傳輸層之間
- 首部壓縮
- 服務端推送
HTTP之請求訊息Request
- 請求行(request line)、請求頭部(header)、空行和請求資料四個部分組成。
- 請求行,用來說明請求型別,要訪問的資源以及所使用的HTTP版本.
- 請求頭部,緊接著請求行(即第一行)之後的部分,用來說明伺服器要使用的附加資訊
- 空行,請求頭部後面的空行是必須的
- 請求資料也叫主體,可以新增任意的其他資料。
HTTP之響應訊息Response
HTTP響應也由四個部分組成,分別是:狀態行、訊息報頭、空行和響應正文。
- 狀態行,由HTTP協議版本號, 狀態碼, 狀態訊息 三部分組成。
- 訊息報頭,用來說明客戶端要使用的一些附加資訊
- 第三部分:空行,訊息報頭後面的空行是必須的
- 第四部分:響應正文,伺服器返回給客戶端的文字資訊。
在瀏覽器位址列鍵入URL,按下回車之後會經歷以下流程:
- 瀏覽器向 DNS 伺服器請求解析該 URL 中的域名所對應的 IP 地址;
- 建立TCP連線(三次握手);
- 瀏覽器發出讀取檔案(URL 中域名後面部分對應的檔案)的HTTP 請求,該請求報文作為 TCP 三次握手的第三個報文的資料傳送給伺服器;
- 伺服器對瀏覽器請求作出響應,並把對應的 html 文字傳送給瀏覽器;
- 釋放 TCP連線(四次揮手);
- 瀏覽器將該 html 文字並顯示內容;
三次握手
SYN (同步序列編號)ACK(確認字元)
- 第一次握手: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之間可以開始傳輸資料了。
四次揮手
- 第一次揮手: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狀態,完成四次揮手。
為什麼建立連線是三次握手,而關閉連線卻是四次揮手呢?
這是因為服務端在LISTEN狀態下,收到建立連線請求的SYN報文後,把ACK和SYN放在一個報文裡傳送給客戶端。而關閉連線時,當收到對方的FIN報文時,僅僅表示對方不再傳送資料了但是還能接收資料,己方也未必全部資料都傳送給對方了,所以己方可以立即close,也可以傳送一些資料給對方後,再傳送FIN報文給對方來表示同意現在關閉連線,因此,己方ACK和FIN一般都會分開傳送。
網頁生成的過程,大致可以分為五步:
- html程式碼轉化為dom
- css程式碼轉化為cssom
- 結合dom和cssom,生成一顆渲染樹
- 生成佈局layout,即將所有的渲染樹的節點進行平面合成
- 將佈局繪製paint在螢幕上(可以擴充講一下減少瀏覽器渲染的重排和重繪)
瀏覽器快取
當瀏覽器再次訪問一個已經訪問過的資源時,它會這樣做:
- 看看是否命中強快取,如果命中,就直接使用快取了。
- 如果沒有命中強快取,就發請求到伺服器檢查是否命中協商快取。
- 如果命中協商快取,伺服器會返回 304 告訴瀏覽器使用本地快取。
- 否則,返回最新的資源。
vue&react
Virtual DOM
其實 VNode 是對真實 DOM 的一種抽象描述,它的核心定義無非就幾個關鍵屬性,標籤名、資料、子節點、鍵值等,其它屬性都是都是用來擴充套件 VNode 的靈活性以及實現一些特殊 feature 的。由於 VNode 只是用來對映到真實 DOM 的渲染,不需要包含操作 DOM 的方法,因此它是非常輕量和簡單的。 Virtual DOM 除了它的資料結構的定義,對映到真實的 DOM 實際上要經歷 VNode 的 create、diff、patch 等過程。
diff演算法
- diff演算法比較新舊節點的時候,比較只會在同層級比較,不會跨層級比較
- 當資料發生變化的時候會生成一個新的VNode,然後新VNode和oldNode做對比,發現不一樣的地方直接修改在真實的dom上,比較新舊節點,一邊比較一邊給真是的dom打補丁
- 節點設定key可以好笑的利用dom(key最好不要設定成index索引)
vue的響應式原理
Object.defineProperty(obj, prop, descriptor)
- obj 是要在其上定義屬性的物件;prop 是要定義或修改的屬性的名稱;descriptor 是將被定義或修改的屬性描述符。
比較核心的是 descriptor,它有很多可選鍵值,具體的可以去參閱它的文件。這裡我們最關心的是 get 和 set,get 是一個給屬性提供的 getter 方法,當我們訪問了該屬性的時候會觸發 getter 方法;set 是一個給屬性提供的 setter 方法,當我們對該屬性做修改的時候會觸發 setter 方法。一旦物件擁有了 getter 和 setter,我們可以簡單地把這個物件稱為響應式物件
- 物件遞迴呼叫
- 陣列變異方法的解決方法:代理原型/例項方法
observe
- observe 方法的作用就是給非 VNode 的物件型別資料新增一個 Observer,如果已經新增過則直接返回,否則在滿足一定條件下去例項化一個 Observer 物件例項。
- observe 的功能就是用來監測資料的變化.
- Observer 是一個類,它的作用是給物件的屬性新增 getter 和 setter,用於依賴收集和派發更新:
- 依賴收集和派發更新
- 收集依賴的目的是為了當這些響應式資料發生變化,觸發它們的 setter 的時候,能知道應該通知哪些訂閱者去做相應的邏輯處理,我們把這個過程叫派發更新,其實 Watcher 和 Dep 就是一個非常經典的觀察者設計模式的實現
- 派發更新就是資料發生變化的時候,觸發 setter 邏輯,把在依賴過程中訂閱的的所有觀察者,也就是 watcher,都觸發它們的 update 過程,這個過程又利用了佇列做了進一步優化,在 nextTick 後執行所有 watcher 的 run,最後執行它們的回撥函式
- vue編譯Compile的過程主要分以下幾步 parse(生成AST)=> optimize(優化靜態節點) => generate(生成render function)
// 解析模板字串生成 AST
const ast = parse(template.trim(), options)
//優化語法樹
optimize(ast, options)
//生成程式碼
const code = generate(ast, options)
複製程式碼
對vuex的理解,單向資料流
vuex
- state: 狀態中心
- mutations: 更改狀態
- actions: 非同步更改狀態
- getters: 獲取狀態
- modules: 將state分成多個modules,便於管理
前端路由的兩種實現原理
- Hash模式
- window物件提供了onhashchange事件來監聽hash值的改變,一旦url中的hash值發生改變,便會觸發該事件。
- History 模式
- popstate監聽歷史棧資訊變化,變化時重新渲染
- 使用pushState方法實現新增功能
- 使用replaceState實現替換功能
前端安全
XSS和CSRF
- XSS:跨站指令碼攻擊,是一種網站應用程式的安全漏洞攻擊,是程式碼注入的一種。常見方式是將惡意程式碼注入合法程式碼裡隱藏起來,再誘發惡意程式碼,從而進行各種各樣的非法活動。
預防:
-
使用XSS Filter
- 輸入過濾,對使用者提交的資料進行有效性驗證,僅接受指定長度範圍內並符合我們期望格式的的內容提交,阻止或者忽略除此外的其他任何資料。
- 輸出轉義,當需要將一個字串輸出到Web網頁時,同時又不確定這個字串中是否包括XSS特殊字元,為了確保輸出內容的完整性和正確性,輸出HTML屬性時可以使用HTML轉義編碼(HTMLEncode)進行處理,輸出到
<script>
中,可以進行JS編碼。
-
使用 HttpOnly Cookie 將重要的cookie標記為httponly,這樣的話當瀏覽器向Web伺服器發起請求的時就會帶上cookie欄位,但是在js指令碼中卻不能訪問這個cookie,這樣就避免了XSS攻擊利用JavaScript的document.cookie獲取cookie。
-
CSRF:跨站請求偽造,也稱 XSRF,是一種挾制使用者在當前已登入的Web應用程式上執行非本意的操作的攻擊方法。與 XSS 相比,XSS利用的是使用者對指定網站的信任,CSRF利用的是網站對使用者網頁瀏覽器的信任。
- 預防:使用者操作限制——驗證碼機制
- 方法:新增驗證碼來識別是不是使用者主動去發起這個請求,由於一定強度的驗證碼機器無法識別,因此危險網站不能偽造一個完整的請求。
- 優點:簡單粗暴,低成本,可靠,能防範99.99%的攻擊者。
- 缺點:對使用者不友好。
- 請求來源限制——驗證 HTTP Referer 欄位
- 方法:在HTTP請求頭中有一個欄位叫Referer,它記錄了請求的來源地址。 伺服器需要做的是驗證這個來源地址是否合法,如果是來自一些不受信任的網站,則拒絕響應。
- 優點:零成本,簡單易實現。
- 缺點:由於這個方法嚴重依賴瀏覽器自身,因此安全性全看瀏覽器。
- 額外驗證機制——token的使用
- 方法:使用token來代替驗證碼驗證。由於黑客並不能拿到和看到cookie裡的內容,所以無法偽造一個完整的請求。基本思路如下:
- 伺服器隨機產生token(比如把cookie hash化生成),存在session中,放在cookie中或者以ajax的形式交給前端。
- 前端發請求的時候,解析cookie中的token,放到請求url裡或者請求頭中。
- 伺服器驗證token,由於黑客無法得到或者偽造token,所以能防範csrf