前言
文章涉及的內容可能不全面,但量很多,需要慢慢看。我花了很長的時間整理,用心分享心得,希望對大家有所幫助。但是難免會有打字的錯誤或理解的錯誤點,希望發現的可以郵箱告訴我ghui_master@163.com,我會及時的進行修改,只希望對你有所幫助,謝謝。
JS中的資料型別都有哪些?它們之間有什麼區別?該如何檢測?
基本資料型別:
number、
string、
boolean、
null、
undefined 引用資料型別: object(普通物件、陣列物件、正則物件、日期物件、Math、例項、 prototype等)
function(普通函式和類) 特殊資料型別:Symbol
基本資料型別按值操作,引用資料型別按照堆記憶體的引用地址來操作
資料型別檢測四種方式: typeof
instanceof
constructor
Object.prototype.toString.call()
-
常用瀏覽器的核心都有哪些? 3. 陣列中常用的迭代方法有哪些?都是什麼意思?(至少四種)
webkit、
Gecko、
Trident、
Presto等
-
說一下你對閉包的理解,以及工作中什麼地方你用到了閉包?
函式執行會形成一個私有的作用域(私有棧記憶體),這就是閉包,在真實專案 中主要應用閉包的兩大作用 保護
儲存 之前研究過像JQUERY等類庫的原始碼,為了防止全域性變數汙染,基本上所有程式碼 都是放到閉包中保護起來的 專案中遇到迴圈事件繫結,也會基於閉包儲存的作用,去儲存對應的索引 之前研究過JS高階程式設計技巧:柯理化函式程式設計思想,這個就是閉包的應用,重 寫過debounce、throttle函式節流和防抖的方法 ...... 因為閉包會產生不釋放的棧記憶體,所以儘可能在專案中少使用
什麼是物件導向? 談談你的理解
物件導向是一種程式設計思想,js本身就是基於物件導向構建出來的(例如:js中有很多內建類,像Promise就是ES6中新增的一個內建類,我們可以基於new Promise來建立一個例項,來管理非同步程式設計,我們在專案中Promise也經常用,自己也研究過他的原始碼。。。)我們平時用的VUE/REACT/JQUERY也是基於物件導向構建出來的,他們都是類,平時開發的時候都是建立他們的額例項來操作的;當然我自己在真實專案中,也封裝過一些元件外掛,(例如:DIALOG、拖拽、、、)也是基於物件導向開發的,這樣可以創造不同的例項,來管理私有的屬性和公有的方法,很方便。。。。
JS中的面向對物件,和其他程式語言還略有不同,JS中類和例項都是基於原型和原型鏈機制來處理的; 而且js中關於類的過載、重寫、繼承也和其他語言不太一樣
-
window.onload VS $(document).ready()
// 這個題我知道,我之前看過部分JQ原始碼
// 1.$(document).ready() 採用的是DOM2事件繫結,監聽的是DOMContentLoaded這個事件,所以只要DOM結構載入完成就會被觸發執行,而且同一個頁面中可以使用多次(繫結不同的方法,因為基於DOM2事件池繫結機制完成的) // 2.window.onload必須等待所有資源都載入完成才會被觸發執行,採用DOM0事件繫結,同一個頁面只能繫結一次(一個方法),想繫結多個也需要改為window.addEventListener('load', function () {})DOM2繫結方式
-
闡述一下let/var/const三者之間的區別?
let 和 var 的區別
- 不存在變數提升
- 不允許重複宣告
- 在全域性作用域下設定變數不會給window設定屬性
- 存在塊級作用域
- 解決了一些暫時性死區問題
let 和 const 的區別 const 建立的是常量,儲存的值不能被修改(準確說是不能修改變數的指 向)
-
闡述一下call/apply/bind三者之間的區別,以及應用場景?
call 和 apply 的區別 給方法傳參的時候,call是按順序,一個個傳遞;apply是把傳遞的引數放 到一個陣列中傳遞;
在傳遞引數較多的情況下, call的效能要高於apply; call 和 bind 的區別 call在改變函式this指向的時候,會把函式立即執行,而bind不會把函式立 即執行,只是預先處理了this和實參資訊;
真實專案中,我們需要改變this指向的時候,會應用這三個方法,例如: 給元素進行事件繫結,我們需要把事件觸發,所執行的函式中this和引數進 行預先處理,此時可以使用bind進行處理;
我們可以基於call方法,讓類陣列借用陣列原型上的方法,例如:把類陣列 轉換為陣列
可以基於apply傳參是一個陣列,借用Math.max獲取陣列中的最大值 ......
陣列中常用的迭代方法有哪些?(至少四種)
forEach、
map、
find、
some、
filter、
reduce、
every、
sort等
有A和B兩個開發者,他們同時開發一款產品(最後的程式碼需要合併),為了防止相互之前產生變數汙染,他們決定採用高階單例模式來進行模組化開發,請編寫相應的程式碼示例!
// 開發者A let AModule = (function () { let n = 10; let query = function () { //... }; let fn = function () { //... //調取開發者B編寫的QUERY方法 BModule.query(); }; return { query: query, init: function () { query(); fn(); } } })(); // 開發者B let BModule = (function () { let n = 20; let query = function () { //... }; let sum = function () { //... //調取開發者A編寫的QUERY方法 AModule.query(); }; return { query: query, init: function () { query(); sum(); } } })(); AModule.init(); BModule.init(); 複製程式碼
ES6中的新語法規範
- let / const
- class 建立類
- import / export :ES6 Module 模組的匯入匯出規範(JS中的模組化規範 AMD -> CMD -> CommonJS -> ES6 Module)
- Arrow Function 箭頭函式
- 模板字串
- 解構賦值
- “...” 擴充、展開、剩餘運算子
- Promise / async / await
- for of迴圈
- Set / Map
- Array / Object ... 提供的新方法
- ......
var r2 = typeof typeof typeof sum; // "string"
執行順序:先執行最後一個 typeof sum -> "function" -> typeof typeof "function" -> typeof "string" -> "string"
只要是兩個及以上的typeof 最後返回結果就是 "string"
TCP傳輸的詳細過程是怎樣的?
進行三次握手,建立TCP連線。
-
第一次握手:建立連線。客戶端傳送連線請求報文段,將SYN位置為1,Sequence Number為x;然後,客戶端進入SYN_SEND狀態,等待伺服器的確認;
-
第二次握手:伺服器收到SYN報文段。伺服器收到客戶端的SYN報文段,需要對這個SYN報文段進行確認,設定Acknowledgment Number為x+1(Sequence Number+1);同時,自己自己還要傳送SYN請求資訊,將SYN位置為1,Sequence Number為y;伺服器端將上述所有資訊放到一個報文段(即SYN+ACK報文段)中,一併傳送給客戶端,此時伺服器進入SYN_RECV狀態;
-
第三次握手:客戶端收到伺服器的SYN+ACK報文段。然後將Acknowledgment Number設定為y+1,向伺服器傳送ACK報文段,這個報文段傳送完畢以後,客戶端和伺服器端都進入ESTABLISHED狀態,完成TCP三次握手。
-
完成了三次握手,客戶端和伺服器端就可以開始傳送資料。
-
ACK:此標誌表示應答域有效,就是說前面所說的TCP應答號將會包含在TCP資料包中;有兩個取值:0和1,為1的時候表示應答域有效,反之為0。
-
TCP協議規定,只有ACK=1時有效,也規定連線建立後所有傳送的報文的ACK必須為1。
-
SYN(SYNchronization) : 在連線建立時用來同步序號。當SYN=1而ACK=0時,表明這是一個連線請求報文。對方若同意建立連線,則應在響應報文中使SYN=1和ACK=1. 因此, SYN置1就表示這是一個連線請求或連線接受報文。 FIN (finis)即完,終結的意思, 用來釋放一個連線。當 FIN = 1 時,表明此報文段的傳送方的資料已經傳送完畢,並要求釋放連線。
-
傳送HTTP請求,伺服器處理請求,返回響應結果 TCP連線建立後,瀏覽器就可以利用HTTP/HTTPS協議向伺服器傳送請求了。伺服器接受到請求,就解析請求頭,如果頭部有快取相關資訊如if-none-match與if-modified-since,則驗證快取是否有效,若有效則返回狀態碼為304,若無效則重新返回資源,狀態碼為200. 關閉TCP連線
-
第一次分手:主機1(可以使客戶端,也可以是伺服器端),設定Sequence Number和Acknowledgment Number,向主機2傳送一個FIN報文段;此時,主機1進入FIN_WAIT_1狀態;這表示主機1沒有資料要傳送給主機2了;
-
第二次分手:主機2收到了主機1傳送的FIN報文段,向主機1回一個ACK報文段,Acknowledgment Number為Sequence Number加1;主機1進入FIN_WAIT_2狀態;主機2告訴主機1,我“同意”你的關閉請求;
-
第三次分手:主機2向主機1傳送FIN報文段,請求關閉連線,同時主機2進入LAST_ACK狀態;
-
第四次分手:主機1收到主機2傳送的FIN報文段,向主機2傳送ACK報文段,然後主機1進入TIME_WAIT狀態;主機2收到主機1的ACK報文段以後,就關閉連線;此時,主機1等待2MSL後依然沒有收到回覆,則證明Server端已正常關閉,那好,主機1也可以關閉連線了。
瞭解跨域嗎,一般什麼情況下會導致跨域
小提示: 如果平常自身有使用場景可結合使用場景進行講解,比如我在這裡使用過的場景是CORS和Nginx反向代理。
跨域行為
- 同源策略限制、安全性考慮
- 協議、IP和埠不一致都是跨域行為
JSONP
小提示:如果你提到JSONP,面試官肯定會問你整個詳細的實現過程,所以一定要搞懂JSONP的實現原理,如果不是很理解可以自己起一個Express服務實踐一下。
Web前端事先定義一個用於獲取跨域響應資料的回撥函式,並通過沒有同源策略限制的script標籤發起一個請求(將回撥函式的名稱放到這個請求的query引數裡),然後服務端返回這個回撥函式的執行,並將需要響應的資料放到回撥函式的引數裡,前端的script標籤請求到這個執行的回撥函式後會立馬執行,於是就拿到了執行的響應資料。
缺點: JSONP只能發起GET請求
如何實現一個JSONP
這裡給出幾個連結:
segmentfault.com/a/119000001… zhangguixu.github.io/2016/12/02/… www.cnblogs.com/iovec/p/531…
JSONP安全性問題
CSRF攻擊
前端構造一個惡意頁面,請求JSONP介面,收集服務端的敏感資訊。如果JSONP介面還涉及一些敏感操作或資訊(比如登入、刪除等操作),那就更不安全了。
解決方法:驗證JSONP的呼叫來源(Referer),服務端判斷Referer是否是白名單,或者部署隨機Token來防禦。
XSS漏洞
不嚴謹的 content-type導致的 XSS 漏洞,想象一下 JSONP 就是你請求 http://youdomain.com?callback=douniwan
, 然後返回 douniwan({ data })
,那假如請求 http://youdomain.com?callback=<script>alert(1)</script>
不就返回 <script>alert(1)</script>({ data })
了嗎,如果沒有嚴格定義好 Content-Type( Content-Type: application/json ),再加上沒有過濾 callback
引數,直接當 html 解析了,就是一個赤裸裸的 XSS 了。
解決方法:嚴格定義 Content-Type: application/json,然後嚴格過濾 callback
後的引數並且限制長度(進行字元轉義,例如<換成<,>換成>)等,這樣返回的指令碼內容會變成文字格式,指令碼將不會執行。
伺服器被黑,返回一串惡意執行的程式碼
可以將執行的程式碼轉發到服務端進行校驗JSONP內容校驗,再返回校驗結果。
CORS(跨域資款共享)
小提示:如果你回答跨域解決方案CORS,那麼面試官一定會問你實現CORS的響應頭資訊Access-Control-Allow-Origin。
什麼是CORS
CORS(跨域資源共享 Cross-origin resource sharing)允許瀏覽器向跨域伺服器發出XMLHttpRequest請求,從而克服跨域問題,它需要瀏覽器和伺服器的同時支援。
- 瀏覽器端會自動向請求頭新增origin欄位,表明當前請求來源。
- 伺服器端需要設定響應頭的Access-Control-Allow-Methods,Access-Control-Allow-Headers,Access-Control-Allow-Origin等欄位,指定允許的方法,頭部,源等資訊。
- 請求分為簡單請求和非簡單請求,非簡單請求會先進行一次OPTION方法進行預檢,看是否允許當前跨域請求。
簡單請求
請求方法是以下三種方法之一:
- HEAD
- GET
- POST
HTTP的請求頭資訊不超出以下幾種欄位:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限於三個值application/x-www-form-urlencoded、multipart/form-data、text/plain
後端的響應頭資訊:
- Access-Control-Allow-Origin:該欄位是必須的。它的值要麼是請求時Origin欄位的值,要麼是一個*,表示接受任意域名的請求。
- Access-Control-Allow-Credentials:該欄位可選。它的值是一個布林值,表示是否允許傳送Cookie。
- Access-Control-Expose-Headers:該欄位可選。CORS請求時,XMLHttpRequest物件的getResponseHeader()方法只能拿到6個基本欄位:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他欄位,就必須在Access-Control-Expose-Headers裡面指定。
非簡單請求
非簡單請求是那種對伺服器有特殊要求的請求,比如請求方法是PUT或DELETE,或者Content-Type欄位的型別是application/json。非簡單請求的CORS請求,會在正式通訊之前,增加一次HTTP查詢請求,稱為"預檢"請求(preflight)。
- Access-Control-Request-Method:該欄位是必須的,用來列出瀏覽器的CORS請求會用到哪些HTTP方法,上例是PUT。
- Access-Control-Request-Headers:該欄位是一個逗號分隔的字串,指定瀏覽器CORS請求會額外傳送的頭資訊欄位,上例是X-Custom-Header。
如果瀏覽器否定了"預檢"請求,會返回一個正常的HTTP回應,但是沒有任何CORS相關的頭資訊欄位。這時,瀏覽器就會認定,伺服器不同意預檢請求,因此觸發一個錯誤,被XMLHttpRequest物件的onerror回撥函式捕獲。
JSONP和CORS的對比
- JSONP只支援GET請求,CORS支援所有型別的HTTP請求
- JSONP的優勢在於支援老式瀏覽器,以及可以向不支援CORS的網站請求資料
其他跨域解決方案
-
Nginx反向代理
-
postMessage
-
document.domain
基於原生實現內建類方法重寫
重寫內建 new
function Dog(name) { this.name = name; } Dog.prototype={ bark(){ console.log('wangwang'); }, sayName(){ console.log('my name is ' + this.name); } }; function _new() { //=>完成你的程式碼 } let sanmao = _new(Dog, '三毛'); sanmao.bark(); //=>"wangwang" sanmao.sayName(); //=>"my name is 三毛" console.log(sanmao instanceof Dog); //=>true ================================================ //——> 重寫 _new function _new(Fn, ...arg) { let obj = {}; obj.__proto__ = Fn.prototype; Fn.call(obj, ...arg); return obj; } 複製程式碼
實現一個$attr(name,value)遍歷
/* 實現一個$attr(name,value)遍歷 * 屬性為name * 值為value的元素集合 * 例如下面示例: */ let ary = $attr('class','box'); //=>獲取頁面中所有class為box的元素 ===================================== function $attr(property, value) { let elements = document.getElementsByTagName('*'), arr = []; elements = Array.from(elements); elements.forEach(item => { let itemValue = item.getAttribute(property); if (property==='class') { new RegExp("\\b" + value + "\\b").test(itemValue)?arr.push(item):null; return; } if (itemValue === value) { arr.push(item); } }); return arr; } console.log($attr('class', 'box')); 複製程式碼
bind重寫
~function(){ //=>bind方法在IE6~8中不相容,接下來我們自己基於原生JS實現這個方法 function bind(){ }; Function.prototype.bind=bind; }(); var obj = {name:'zhufeng'}; function func(){ console.log(this,arguments); //=>當點選BODY的時候,執行func方法,輸出:obj [100,200,MouseEvent事件物件] } document.body.onclick = func.bind(obj,100,200); ============================================= ~function(){ //=>bind方法在IE6~8中不相容,接下來我們自己基於原生JS實現這個方法 function bind(context){ context=context||window; var _this = this, outerArg=[].slice.call(arguments,1); return function anonymous() { var innerArg=[].slice.call(arguments,0); _this.apply(context, outerArg.concat(innerArg)); } }; Function.prototype.bind=bind; }(); 複製程式碼
class重構
function Modal(x,y){ this.x=x; this.y=y; } Modal.prototype.z=10; Modal.prototype.getX=function(){ console.log(this.x); } Modal.prototype.getY=function(){ console.log(this.y); } Modal.n=200; Modal.setNumber=function(n){ this.n=n; }; let m = new Model(10,20); =============================================== class Modal{ constructor(x,y){ this.x=x; this.y=y; } getX(){ console.log(this.x); } getY(){ console.log(this.y); } static setNumber(n){ this.n=n; } } Modal.n=200; Modal.prototype.z=10; 複製程式碼
call重寫
~function(){ function changeThis(context){ context=context||window; //let arg=[].slice.call(arguments,1); let arg=[], _this=this, result=null; for(let i=1;i<arguments.length;i++){ arg.push(arguments[i]); } context.$fn=_this; result=context.$fn(...arg); delete context.$fn; return result; }; Function.prototype.changeThis=changeThis; }(); let obj = {name:'Alibaba',$fn:100}; function fn(x,y){ this.total=x+y; return this; } let res = fn.changeThis(obj,100,200); //res => {name:'Alibaba',total:300} 複製程式碼
apply重寫
~function(){ /*生成隨機函式名:時間戳的方式*/ function queryRandomName(){ let time=new Date().getTime(); return '$zhufeng'+time; } /*模擬CALL方法改變函式中的THIS*/ function changeThis(context=window,arg=[]){ let _this=this, result=null, ran=queryRandomName(); context[ran]=_this; result=context[ran](...arg); delete context[ran]; return result; }; Function.prototype.changeThis=changeThis; }(); let res = fn.changeThis(obj,[100,200]); 複製程式碼
怎麼讓一個 div 水平垂直居中?(不少於三種解決方案)
/* 已知寬高 */
.box {
position: absolute;
top: 50%;
left: 50%;
margin-top: -50px;
margin-left: -50px;
width: 100px;
height: 100px;
}
========================
/* 未知寬高 */
.box {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
==========================
.box {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
}
===============================
.container {
display: flex;
justify-content: center;
align-items: center;
}
.container .box {}
複製程式碼
編寫一個方法“flatten”,將陣列扁平化 (至少兩種辦法)
/*
* 1.編寫一個方法“flatten”,將陣列扁平化 (至少兩種辦法)
* 2.然後實現“unique”陣列去重方法,把陣列進行去重 (至少兩種辦法)
*/
let arr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9,[11, 12, [12,
13, [14]]]], 10];
ary.flatten().unique().sort((a,b)=>a-b); //=>[1, 2, 3, 4, 5,
6, 7, 8, 9....]
============================================
~function(){
function flatten(){
/*第一種*/
return JSON.stringify(this).replace(/(\
[|\])/g,'').split(',').map(item=>{
return Number(item);
});
/*第二種*/
return this.flat(Infinity);
/*第三種*/
return this.toString().split(',').map(item=>{
return Number(item);
});
/*第四種*/
let arr=this.slice(0);
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
/*第五種*/
let result = [];
let fn = function (ary) {
for (let i = 0; i < ary.length; i++) {
let item = ary[i];
if (Array.isArray(ary[i])) {
fn(item);
} else {
result.push(item);
}
}
}
fn(this);
return result;
}
function unique(){
/*第一種*/
return Array.from(new Set(this));
/*第二種*/
return [...new Set(this)];
/*第三種*/
let obj={};
for(let i=0;i<this.length;i++){
let item=this[i];
if(typeof obj[item]!=='undefined'){
this[i]=this[this.length-1];
this.length--;
i--;
continue;
}
obj[item]=item;
}
return this;
}
Array.prototype.flatten=flatten;
Array.prototype.unique=unique;
}();
let arr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9,[11, 12, [12,
13, [14]]]], 10];
ary.flatten().unique().sort((a,b)=>a-b); //=>[1, 2, 3, 4, 5,
6, 7, 8, 9....]
複製程式碼