開門見山,這篇文章,適合「初級前端」,如果你還在校招的話,或者還在求職的話,可以看看本文,找一找靈感,希望對你們有幫助呀。
先說一下最近個人情況:2020年8月底已經拿到網易有道offer, 這算是我的第一份web前端工作吧,一直以來都是自學前端的,走過很多的彎路,之前的技術棧是Vue.js,目前往react方向走。
這是一次萬恆的三輪網易面經
我的感受就是,自己一邊梳理知識點,一邊總結歸納,收穫可能更大,所以打算把我梳理的部分分享出來,篇幅有點長,大家見諒呀。
覆蓋的點不是很全,分享給你們,希望你們秋招一切順利,offer收割機❤️❤️❤️
HTML系列你是如何理解 HTML 語義化的?讓頁面內容結構化,它有如下優點
1、易於用戶閱讀,樣式丟失的時候能讓頁面呈現清晰的結構。
2、有利於SEO,搜尋引擎根據標籤來確定上下文和各個關鍵字的權重。
3、方便其他設備解析,如盲人閱讀器根據語義渲染網頁
4、有利於開發和維護,語義化更具可讀性,代碼更好維護,與CSS3關係更和諧如:
<header>代表頭部
<nav>代表超連結區域
<main>定義文檔主要內容
<article>可以表示文章、博客等內容
<aside>通常表示側邊欄或嵌入內容
<footer>代表尾部可以跟面試官講的更具體一點👇
第一個是荒野階段,那時候前端的代碼主要是後臺來寫的,所以那個時候寫的代碼主要是用table來布局的。
第二階段---美工階段,這個時候就有專門的人來前端代碼了,這個階段的布局主要是DIV+CSS布局,但是呢有一個問題,就是不夠語義化。
第三個階段-->> 前端階段,也就是利用具有語義的標籤,比如p,h1,h2,article,header,nav,main,aside,footer這些標籤,使用這些正確的標籤,可以表達正確的內容,也利於開發和維護。
meta viewport 是做什麼用的,怎麼寫?通常viewport是指視窗、視口。瀏覽器上(也可能是一個app中的webview)用來顯示網頁的那部分區域。在移動端和pc端視口是不同的,pc端的視口是瀏覽器窗口區域,而在移動端有三個不同的視口概念:布局視口、視覺視口、理想視口
meta有兩個屬性name 和 http-equiv
namekeywords(關鍵字) 告訴搜尋引擎,你網頁的關鍵字
description(網站內容描述) 用於告訴搜尋引擎,你網站的主要內容。
robots(定義搜尋引擎爬蟲的索引方式) robots用來告訴爬蟲哪些頁面需要索引,哪些頁面不需要索引
http-equiv❝http-equiv顧名思義,相當於http的文件頭作用。
❞有以下參數:
content-Type 設定網頁字符集
//舊的HTML,不推薦 //HTML5設定網頁字符集的方式,推薦使用UTF-8X-UA-Compatible(瀏覽器採用哪種版本來渲染頁面)
//指定IE和Chrome使用最新版本渲染當前頁面cache-control(請求和響應遵循的緩存機制)
你用過哪些 HTML 5 標籤?有<header>、<footer>、<aside>、<nav>、<video>、<audio>、<canvas>等...「canvas畫布」
const ctx = canvas.getContext('2d'); // 獲取它的2d上下文
ctx.fillStyle = 'green'; // 設置筆刷的填充色
ctx.fillRect(10, 10, 100, 100); // 利用畫筆範圍,矩形,比如圓
videoautoplay 布爾屬性;視頻馬上自動開始播放,不會停下來等著數據載入結束。
「controls」 提供用戶控制,允許用戶控制視頻的播放,包括音量,跨幀,暫停/恢復播放。
「loop」 布爾屬性;指定後,會在視頻結尾的地方,自動返回視頻開始的地方。
「track」標籤表示的是字幕
「poster」 表示的是封面
<track kind="subtitles" src="foo.en.vtt" srclang="en" label="English">
H5 是什麼?-->>移動端頁面h5一般指的是開一個WebView來加載頁面吧,
「WebView是一種控制項,它基於webkit引擎,因此具備渲染Web頁面的功能。」
基於Webview的混合開發,就是在 Anddroid (安卓)/(蘋果)原生APP裡,通過WebView控制項嵌入Web頁面。
很多APP都是外邊套原生APP的殼,內容是H5頁面(基於html+css+js的Web頁面)。現在的移動端混合開發軟體,如果對於交互渲染要求不是特別高的項目,基本都是這麼玩的。
「WebView作用」
直接使用html文件(網絡上或本地assets中)作布局HTML5新特性:
設備兼容特性 HTML5提供了前所未有的數據與應用接入開放接口網頁多媒體特性 支持Audio Video SVG Canvas WebGL CSS3增加拖放API、地理定位、SVG繪圖、canvas繪圖、Web Worker、WebSocket
區分普通顯示屏和高清屏當devicePixelRatio值等於1時(也就是最小值),那麼它普通顯示屏。
當devicePixelRatio值大於1(通常是1.5、2.0),那麼它就是高清顯示屏。
不同像素的圖利用媒體查詢結合 devicePixelRatio 可以區分普通顯示屏和高清顯示屏
並給出了如下CSS設計方案:
.css{/* 普通顯示屏(設備像素比例小於等於1.3)使用1倍的圖 */
background-image: url(img_1x.png);
}
@media only screen and (-webkit-min-device-pixel-ratio:1.5){
.css{/* 高清顯示屏(設備像素比例大於等於1.5)使用2倍圖 */
background-image: url(img_2x.png);
}
}
「服務端用nginx對圖片進行處理」想要什麼樣尺寸的圖片自己裁切,我們提供了按比例縮放和自定尺寸的裁切方法,地址後拼接字符串就行。
「使用更小更快更強,新一代圖片格式 WebP」在實測中,webp 格式比 jpg 格式減小約 20%。這對優化用戶體驗,減少CDN帶寬消耗有很好的效果。
「如何判斷呢」
我想到一個解決的方案,就是通過User-Agent信息,可以拿到你的瀏覽器信息,通過對你的瀏覽器分類,支持webp放在白名單裡,不支持的則為黑名單。判斷為白名單,則直接調用,返回webp格式圖片;反之,則顯示原圖。
DOM事件冒泡事件會從最內層的元素開始發生,一直向上傳播,直到document對象。
<div id="outer">
<p id="inner">Click me!</p>
</div>因此上面的例子在事件冒泡的概念下發生click事件的順序應該是
「p -> div -> body -> html -> document」
「事件捕獲」與事件冒泡相反,事件會從最外層開始發生,直到最具體的元素。
上面的例子在事件捕獲的概念下發生click事件的順序應該是
「document -> html -> body -> div -> p」
所以從上面的圖片來看👉1-5是捕獲過程,5-6是目標階段,6-10是冒泡階段
addEventListeneraddEventListener方法用來為一個特定的元素綁定一個事件處理函數,是JavaScript中的常用方法。
element.addEventListener(event, function, useCapture)重點來看看第三個參數useCapture
true - 事件句柄在捕獲階段執行(即在事件捕獲階段調用處理函數)false- false- 默認。事件句柄在冒泡階段執行(即表示在事件冒泡的階段調用事件處理函數)所以我們通常來說,默認第三個參數不寫的話,是按照事件句柄在冒泡執行的。
attachEvent兼容IE的寫法,默認是事件冒泡階段調用處理函數,寫事件名時候要加上"on"前綴("onload"、"onclick"等)。
object.attachEvent(event, function)
事件代理利用事件流的特性,我們可以使用一種叫做事件代理的方法,其實利用的就是事件冒泡的機制。
<ul id="xxx">下面的內容是子元素1
<li>li內容>>> <span> 這是span內容123</span></li>
下面的內容是子元素2
<li>li內容>>> <span> 這是span內容123</span></li>
下面的內容是子元素3
<li>li內容>>> <span> 這是span內容123</span></li>
</ul>js代碼
xxx.addEventListener('click', function (e) {
console.log(e,e.target)
if (e.target.tagName.toLowerCase() === 'li') {
console.log('列印')
}
})更加規範的寫法👇
function delegate(element, eventType, selector, fn) {
element.addEventListener(eventType, e => {
let el = e.target
while (!el.matches(selector)) {
if (element === el) {
el = null
break
}
el = el.parentNode
}
el && fn.call(el, e, el)
},true)
return element
}
阻止事件冒泡和默認事件event.preventDefault() // 阻止默認事件
event.stopPropagation() //阻止冒泡
實現一個可以拖拽的DIV<div id="xxx"></div>分割線-—---
var dragging = false
var position = null
xxx.addEventListener('mousedown',function(e){
dragging = true
position = [e.clientX, e.clientY]
})
document.addEventListener('mousemove', function(e){
if(dragging === false) return null
console.log('hi')
const x = e.clientX
const y = e.clientY
const deltaX = x - position[0]
const deltaY = y - position[1]
const left = parseInt(xxx.style.left || 0)
const top = parseInt(xxx.style.top || 0)
xxx.style.left = left + deltaX + 'px'
xxx.style.top = top + deltaY + 'px'
position = [x, y]
})
document.addEventListener('mouseup', function(e){
dragging = false
})
CSS系列兩種盒模型分別說一下也就是標準盒模型寫起來更方便,也更規範吧。
盒模型分為標準盒模型和怪異盒模型(IE模型)
box-sizing:content-box //標準盒模型
box-sizing:border-box //怪異盒模型「content-box」
❝默認值,標準盒子模型。width 與 height 只包括內容的寬和高, 不包括邊框(border),內邊距(padding),外邊距(margin)。注意: 內邊距、邊框和外邊距都在這個盒子的外部。比如說,.box {width: 350px; border: 10px solid black;} 在瀏覽器中的渲染的實際寬度將是 370px。
❞尺寸計算公式:
width = 內容的寬度
height = 內容的高度
寬度和高度的計算值都不包含內容的邊框(border)和內邊距(padding)。
「border-box」
❝width 和 height 屬性包括內容,內邊距和邊框,但不包括外邊距。這是當文檔處於 Quirks模式 時Internet Explorer使用的盒模型。注意,填充和邊框將在盒子內 , 例如, .box {width: 350px; border: 10px solid black;} 導致在瀏覽器中呈現的寬度為350px的盒子。內容框不能為負,並且被分配到0,使得不可能使用border-box使元素消失。
❞尺寸計算公式:
width = border + padding + 內容的寬度
height = border + padding + 內容的高度
注意:如果你在設計頁面中,發現內容區被撐爆了,那麼就先檢查一下border-sizing是什麼,最好在引用reset.css的時候,就對border-sizing進行統一設置,方便管理
如何垂直居中?16種方法實現水平居中垂直居中
水平局中「內聯元素」,「寬度默認就是內容的寬度」,只需要給父級添加text-align
.wrapper{text-align: center;}「塊級元素」,將它的margin-left和margin-right設置為auto,並且塊級元素一定要設置寬度,否則元素默認為100%寬度,不需要居中。
.inner{
display: block;
width: 150px;
margin: 0 auto;
}
// 一定要設置寬度,不然就不需要局中了兩個以上的水平局中,可以將其設置為display:inline-block,在設置父級text-align
垂直局中「內聯元素」,第一種實用的是flex布局,這裡局中的值得是相對於父盒子
.wrapper{
display: flex;
align-items: center;
}第二種,這裡面指的局中是相對於自身而言的
.inner{
height:100px;
line-height:100px;
}「塊級元素」
寬高確定情況下,實用 「position absolute + 負margin」
寬高不確定的情況下,實用「position absolute + transform」
垂直水平局中子元素寬高確定的情況下,使用「position absolute + 負margin」
子元素寬高不確定的,使用「position absolute + transform」
.inner{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
background: blue;
}當然了flex布局也是可以解決問題的,下面就介紹👇
兩列布局左列定寬,右列自適應「float+margin」
.left{
float: left;
width: 100px;
height: 100%;
background: rebeccapurple;
}
.rigth{
height: 100%;
margin-left: 100px; /*大於等於#left的寬度*/
background: blue;
}
左列自適應,右列定寬「float+overflow」
<div class="wrapper">
<div class="rigth"></div>
<div class="left"></div>
</div>css代碼👇
.rigth {
margin-left: 10px;
/*margin需要定義在#right中*/
float: right;
width: 100px;
height: 100%;
background-color: #0f0;
}
.left {
overflow: hidden;
/*觸發bfc*/
height: 100%;
background-color: blue;
}
三列布局兩列定寬,一列自適應使用float+margin實現<div class="wrapper">
<div class="left"></div>
<div class="main"></div>
<div class="rigth"></div>
</div>css代碼
.wrapper {
height: 400px;
background: red;
min-width: 500px;
}
.left {
margin-left: 10px;
float: left;
width: 100px;
height: 100%;
background-color: #0f0;
}
.main{
float: left;
width: 100px;
height: 100%;
margin-left: 20px;
background: brown;
}
.rigth {
margin-left: 230px; /*等於#left和#center的寬度之和加上間隔,多出來的就是#right和#center的間隔*/
height: 100%;
background-color: blue;
}「間列自適應寬度,旁邊兩側固定寬度」
「雙飛翼布局」
實現步驟
三個部分都設定為左浮動,然後設置center的寬度為100%,此時,left和right部分會跳到下一行;通過設置margin-left為負值讓left和right部分回到與center部分同一行;center部分增加一個內層div,並設margin: 0 200px;「html部分」
<div class="wrapper">
<div class="main">
<div class="inner"></div>
</div>
<div class="left"></div>
<div class="right"></div>
</div>「css部分」
.wrapper {
/* //確保中間內容可以顯示出來,兩倍left寬+right寬 */
min-width: 600px;
}
.left {
float: left;
width: 200px;
height: 400px;
background: red;
margin-left: -100%;
}
.main {
float: left;
width: 100%;
height: 500px;
background: yellow;
}
.main .inner {
/* margin水平方向要是左右兩者的寬度 */
margin: 0 200px;
height: 100%;
border: 2px solid brown;
}
.right {
float: left;
width: 200px;
height: 400px;
background: blue;
margin-left: -200px;
}
flex 怎麼用,常用屬性有哪些?flex 的核心的概念就是 「容器」 和 「軸」。
父容器「justify-content 項目在主軸上的對齊方式」
flex-start | flex-end | center | space-between | space-around
space-between 子容器沿主軸均勻分布,位於首尾兩端的子容器與父容器相切。
space-around 子容器沿主軸均勻分布,位於首尾兩端的子容器到父容器的距離是子容器間距的一半。
「align-items」 「定義項目在側軸上如何對齊」
flex-start | flex-end | center | baseline | stretch;stretch(默認值):如果項目未設置高度或設為auto,將佔滿整個容器的高度。子容器「align-self 單個項目對齊方式」
align-self: auto | flex-start | flex-end | center | baseline | stretch;「flex:前面三個屬性的簡寫 是flex-grow flex-shrink flex-basis的簡寫」
flex-grow 放大比例 根據所設置的比例分配盒子所剩餘的空間flex-shrink 縮小比例 設置元素的收縮比例 多出盒子的部分,按照比例的大小砍掉相應的大小,即比例越大,被砍的越大,默認值是1flex-basis 伸縮基準值 項目佔據主軸的空間flex-basis 該屬性設置元素的寬度或高度,當然width也可以用來設置元素寬度,如果元素上同時出現了width 和flex-basis那麼flex-basis會覆蓋width的值flex: 0 1 auto;默認主軸是row,那麼不會去放大比例,如果所有的子元素寬度和大於父元素寬度時,就會按照比例的大小去砍掉相應的大小。
軸「flex-direction 決定主軸的方向 即項目的排列方向」
row | row-reverse | column | column-reverse
BFC 是什麼?深入理解BFC和外邊距合併(Margin Collapse)
BFC全稱是Block Formatting Context,即塊格式化上下文。
BFC就是頁面上的一個隔離的獨立容器,容器裡面的子元素不會影響到外面的元素。反之也如此。
下列方式會創建「塊格式化上下文」:
display為inline-block, table-cell, table-caption, flex, inline-flex需要背的條件👇
絕對定位元素(元素的 position 為 absolute 或 fixed)overflow 值不為 visible 的塊元素彈性元素(display為 flex 或 inline-flex元素的直接子元素)「BFC布局規則」
Box垂直方向的距離由margin決定。屬於同一個BFC的兩個相鄰Box的margin會發生重疊每個元素的margin box的左邊, 與包含塊border box的左邊相接觸(對於從左往右的格式化,否則相反)。即使存在浮動也是如此。BFC就是頁面上的一個隔離的獨立容器,容器裡面的子元素不會影響到外面的元素。反之也如此。選擇器優先級「css常用選擇器」
通配符:*
ID選擇器:#ID
類選擇器:.class
元素選擇器:p、a 等
後代選擇器:p span、div a 等
偽類選擇器:a:hover 等
屬性選擇器:input[type="text"] 等「css選擇器權重」
!important -> 行內樣式 -> #id -> .class -> 元素和偽元素 -> * -> 繼承 -> 默認
CSS新特性transition:過渡
transform:旋轉、縮放、移動或者傾斜
animation:動畫
gradient:漸變
shadow:陰影
border-radius:圓角「transition」
transition: property duration timing-function delay;
// css屬性名稱 過渡時間 過渡時間曲線 過渡延遲時間「transform」
transform:rotate(30deg) 旋轉
transform:translate(100px,20px) 移動
transform:scale(2,1.5); 縮放
transform:skew(30deg,10deg); 扭曲「animation」
animation: move 1s linear forwards;
// 定義動畫的時間 duration
// 動畫的名稱
// 動畫的貝塞爾曲線
// animation-fill-mode 屬性規定動畫在播放之前或之後,其動畫效果是否可見。
// forwards 當動畫完成後,保持最後一個屬性值
清除浮動說一下第一種用偽元素
.clearfix:after{
content: "";
display: block;
clear: both;
}
.clearfix{
zoom: 1; /* IE 兼容*/
}第二種給父容器添加 overflow:hidden 或者 auto 樣式
overflow:hidden;
三種地位方案在定位的時候,瀏覽器就會根據元素的盒類型和上下文對這些元素進行定位,可以說盒就是定位的基本單位。定位時,有三種定位方案,分別是常規流,浮動已經絕對定位。
常規流(Normal flow)當position為static或relative,並且float為none時會觸發常規流;對於「靜態定位」(static positioning),position: static,「盒的位置是常規流布局裡的位置」;對於「相對定位」(relative positioning),position: relative,盒偏移位置由這些屬性定義top,bottom,leftandright。「即使有偏移,仍然保留原有的位置」,其它常規流不能佔用這個位置。浮動(Floats)這「導致常規流環繞在它的周邊」,除非設置 clear 屬性;絕對定位(Absolute positioning)絕對定位方案,「盒從常規流中被移除」,不影響常規流的布局;它的定位相對於它的包含塊,相關CSS屬性:top,bottom,left及right;如果元素的屬性position為absolute或fixed,它是絕對定位元素;對於position: absolute,元素定位將相對於最近的一個relative、fixed或absolute的父元素,如果沒有則相對於body;獲取DOM<div id="css-cell">
<div class="heart"></div>
<div class="sun"></div>
</div>
let oDiv = document.getElementById('css-cell')
let oDiv1 = document.getElementsByTagName('div') //集合 根據標籤
let oDiv2 = document.querySelectorAll('div') // 集合 標籤
let oDiv3 = document.getElementsByClassName('heart') // className
Attribute與Propertyattribute:是HTML標籤上的某個屬性,如id、class、value等以及自定義屬性
property:是js獲取的DOM對象上的屬性值,比如a,你可以將它看作為一個基本的js對象。
let demo11 = oDiv.getAttribute('class');
let demo2 = oDiv.setAttribute('data-name','new-value')
路由規則可以在不刷新頁面的前提下動態改變瀏覽器地址欄中的URL地址,動態修改頁面上所顯示資源。
「window.history的方法和屬性」back() forward() go()
HTML5 新方法:添加和替換歷史記錄的條目
「pushState()」
history.pushState(state, title, url); 添加一條歷史記錄,不刷新頁面
state : 一個於指定網址相關的狀態對象,popstate事件觸發時,該對象會傳入回調函數中。如果不需要這個對象,此處可以填null。title : 新頁面的標題,但是所有瀏覽器目前都忽略這個值,因此這裡可以填null。url : 新的網址,必須與前頁面處在同一個域。瀏覽器的地址欄將顯示這個網址。replaceStatehistory.replaceState(state, title, url); 替換當前的歷史記錄,不刷新頁面這兩個API的相同之處是都會操作瀏覽器的歷史記錄,而不會引起頁面的刷新。
不同之處在於,pushState會增加一條新的歷史記錄,replaceState則會替換當前的歷史記錄。
這兩個api,加上state改變觸發的popstate事件,提供了單頁應該的另一種路由方式。
popstate 事件:歷史記錄發生改變時觸發基於hash(location.hash+hashchange事件)我們知道location.hash的值就是url中#後面的內容,如http://www.163.com#something。
此網址中,location.hash='#something'。
hash滿足以下幾個特性,才使得其可以實現前端路由:
url中hash值的變化並不會重新加載頁面,因為hash是用來指導瀏覽器行為的,對服務端是無用的,所以不會包括在http請求中。hash值的改變,都會在瀏覽器的訪問歷史中增加一個記錄,也就是能通過瀏覽器的回退、前進按鈕控制hash的切換我們可以通過hashchange事件,監聽到hash值的變化,從而響應不同路徑的邏輯處理。window.addEventListener("hashchange", funcRef, false)如此一來,我們就可以在hashchange事件裡,根據hash值來更新對應的視圖,但不會去重新請求頁面,同時呢,也在history裡增加了一條訪問記錄,用戶也仍然可以通過前進後退鍵實現UI的切換。
觸發hash值的變化有2種方法👇
一種是通過a標籤,設置href屬性,當標籤點擊之後,地址欄會改變,同時會觸發hashchange事件<a href="#TianTianUp">to somewhere</a>
另一種是通過js直接賦值給location.hash,也會改變url,觸發hashchange事件。location.hash="#somewhere"
JS系列JS基礎是最重要的一個環節,所以這個專題,我也是梳理總結了很多,畢竟這個是靈魂嘛,那接下來我把我梳理的文章也總結一遍,然後我複習的部分內容也梳理出來了。
往期文章總結「數組方法」從詳細操作js數組到淺析v8中array.js(230+👍)[查缺補漏]再來100道JS輸出題酸爽繼續(共1.8W字)(240+👍)「查缺補漏」送你 54 道 JavaScript 面試題(650+👍)「一勞永逸」送你21道高頻JavaScript手寫面試題(620+👍)介紹一下js數據類型基本數據類型,Number、String、Boolean、Undefined、Null、Symbol ,BigInt。
比如Symbol提出是為了解決什麼問題?可以往全局變量衝突講。
比如BigInt,解決的問題是大數問題,超過了安全數,怎麼辦?
引用數據類型,數組,對象,函數。
可以試著往它們存儲問題上面答,基本數據類型的值直接保存在棧中,而複雜數據類型的值保存在堆中,通過使用在棧中保存對應的指針來獲取堆中的值。
Number.isFinite & isFinite區別某種程度上,都是檢測「有限性」的值。兩者區別在於,isFinite函數強制將一個非數值的參數轉換成數值,如果能轉換成數值,然後再去判斷是否是「有限的」。
Number.isFinite()檢測有窮性的值,這個方法不會強制將一個非數值的參數轉換成數值,這就意味著,只有數值類型的值,且是有窮的(finite),才返回 true。
Number.isFinite(0) // true
Number.isFinite('0') // false
Number.isFinite(Infinity) false
isFinite('0') // true
isFinite('0') // true
isFinite(Infinity) // false
isNaN 和 Number.isNaN 函數的區別?isNaN首先會接受一個參數,參數講這個轉換成數字,任何不能被轉換成數值的都返回true,所以對於非數字的參數,也是true,會影響NaN判斷Number.isNaN首先判斷是不是數字,是數字在去判斷是不是NaN,這種方法更準確。// isNaN('sdasd') true
// isNaN('21N') true
// isNaN(NaN) true
// isNaN(123) false我們來看看Number.isNaN
Number.isNaN('1232N') // false
Number.isNaN('1232') // false
Number.isNaN(21312) // false
Number.isNaN('sadas') // false
Number.isNaN(NaN) // true
什麼是可迭代對象要成為可迭代對象, 一個對象必須實現 @@iterator 方法。這意味著對象(或者它原型鏈上的某個對象)必須有一個鍵為 @@iterator 的屬性,可通過常量 Symbol.iterator 訪問該屬性:如何判斷一個類型是不是可迭代對象
let someString = "hi";
typeof someString[Symbol.iterator]; // "function"「結論」
常見的可迭代對象,有Array,Map, Set, String,TypeArray, arguments可以通過判斷Symbol.iterator判斷當前變量是否是可迭代對象arguments對象了解嗎這個arguments有個易錯點,容易忽略的點。
首先我們看下它的定義:arguments對象是所有(非箭頭)函數中都可用的「局部變量」。此對象包含傳遞給函數的每個參數,第一個參數在索引0處。
arguments對象不是一個 Array 。它類似於Array,但除了length屬性和索引元素之外沒有任何Array屬性。
轉換成數組👇
let args = Array.prototype.slice.call(arguments)
let args1 = Array.from(arguments)
let args2 = [...arguments]易錯點👇
當非嚴格模式中的函數「沒有」包含剩餘參數、默認參數和解構賦值,那麼arguments對象中的值「會」跟蹤參數的值(反之亦然),看幾個題目懂了
function func(a) {
arguments[0] = 99; // 更新了arguments[0] 同樣更新了a
console.log(a);
}
func(10); // 99這裡arguments就會跟蹤a變量👇
function func(a) {
a = 99; // 更新了a 同樣更新了arguments[0]
console.log(arguments[0]);
}
func(10); // 99當非嚴格模式中的函數「有」包含剩餘參數、默認參數和解構賦值,那麼arguments對象中的值「不會」跟蹤參數的值(反之亦然)。相反, arguments反映了調用時提供的參數:
function func(a = 55) {
arguments[0] = 99; // updating arguments[0] does not also update a
console.log(a);
}
func(10); // 10並且
function func(a = 55) {
a = 99; // updating a does not also update arguments[0]
console.log(arguments[0]);
}
func(10); // 10並且
function func(a = 55) {
console.log(arguments[0]);
}
func(); // undefined
原型在js中,我們通常會使用構造函數來創建一個對象,每一個構造函數的內部都有一個prototype屬性,這個屬性對應的值是一個對象,這個對象它包含了可以由該構造函數的所有實例都共享的屬性和方法,我們把它稱為原型。
原型分為「顯示原型」和「隱式原型」,一般稱prototype為顯示原型,__proto__稱為隱式原型。
一般而言,__proto__這個指針我們應該獲取這個值,但是瀏覽器中都實現了 __proto__屬性來讓我們訪問這個屬性,但是我們最好不要使用這個屬性,因為它不是規範中規定的。
ES5 中新增了一個 Object.getPrototypeOf() 方法,我們可以通過這個方法來獲取對象的原型。
舉個例子👇
為什麼我們新建的對象可以使用toString()方法,這是因為我們訪問一個對象的屬性時,首先會在這個對象身上找,如果沒有的話,我們會通過這個對象的__proto__找到該對象的原型,然後在這個原型對象中找,這個原型對象又沒有的話,就這樣子通過一直找下去,這也就是「原型鏈概念」。直到找到原型鏈的盡頭也就是Object.prototype。
js 獲取原型的方法?假設Demo是一個對象,那麼有三種方式👇
Demo.constructor.prototypeObject.getPrototypeOf(Demo)獲取對象屬性的方法Object.keys(testObj) 返回的參數就是一個數組,數組內包括對象內可枚舉屬性和方法名for in 遍歷的也可以,不過對於非繼承的屬性名稱也會獲取到,通過hasOwnproperty判斷Object.getOwnPropertyNames(obj) 返回的參數就是一個數組,數組內包括自身擁有的枚舉或不可枚舉屬性名稱字符串,如果是數組的話,還有可能獲取到length屬性for of 和 for in區別「for in」
我們直接從一段代碼來看
Array.prototype.method=function(){
console.log(this.length);
}
var myArray=[1,2,4,5,6,7]
myArray.name="數組"
for (var index in myArray) {
console.log(myArray[index]);
}有哪些缺陷呢👇
使用for in 會遍歷數組所有可枚舉屬性,包括原型。「例如上面的method和name都會遍歷」for in 更適合遍歷對象,不要使用for in去遍歷數組「for of」
Array.prototype.method=function(){
console.log(this.length);
}
var myArray=[1,2,4,5,6,7]
myArray.name="數組";
for (var value of myArray) {
console.log(value);
}
for of遍歷的只是數組內的元素,而不包括數組的原型屬性method和索引name「小結」
for..of適用遍歷數/數組對象/字符串/map/set等擁有迭代器對象的集合,不能遍歷對象,因為沒有迭代對象,與forEach()不同的是,它可以正確響應break、continue和return語句。for in 可以遍歷一個普通的對象,這樣也是它的本質工作,for in會遍歷原型以及可枚舉屬性,最好的情況下,使用hasOwnProperty判斷是不是實例屬性。作用域鏈「作用域」 規定了如何查找變量,也就是確定當前執行代碼對變量的訪問權限。當查找變量的時候,會先從當前上下文的變量對象中查找,如果沒有找到,就會從父級(詞法層面上的父級)執行上下文的變量對象中查找,一直找到全局上下文的變量對象,也就是全局對象。這樣由多個執行上下文的變量對象構成的鍊表就叫做 「作用域鏈」。
**函數的作用域在函數創建時就已經確定了。**當函數創建時,會有一個名為 [[scope]] 的內部屬性保存所有父變量對象到其中。當函數執行時,會創建一個執行環境,然後通過複製函數的 [[scope]] 屬性中的對象構建起執行環境的作用域鏈,然後,變量對象 VO 被激活生成 AO 並添加到作用域鏈的前端,完整作用域鏈創建完成:
Scope = [AO].concat([[Scope]]);所以閉包,可以說是作用域鏈的另外一種表示形式。
閉包的應用閉包的應用比較典型是定義模塊,我們將操作函數暴露給外部,而細節隱藏在模塊內部
閉包的第一個用途是使我們在函數外部能夠訪問到函數內部的變量。
通過使用閉包,我們可以通過在外部調用閉包函數,從而在外部訪問到函數內部的變量,可以使用這種方法來創建私有變量。
函數的另一個用途是使已經運行結束的函數上下文中的變量對象繼續留在內存中,因為閉包函數保留了這個變量對象的引用,所以這個變量對象不會被回收。
ES6 語法知道哪些,分別怎麼用?let const 塊級作用域 箭頭函數 詞法this Class 解構,剩餘運算符,Promise等,往這些方面展開。
手寫函數防抖和函數節流「節流throttle」
規定在一個單位時間內,只能觸發一次函數。如果這個單位時間內觸發多次函數,只有一次生效。
function throttle(fn, delay) {
let flag = true,
timer = null
return function(...args) {
let context = this
if(!flag) return
flag = false
clearTimeout(timer)
timer = setTimeout(function() {
fn.apply(context,args)
flag = true
},delay)
}
}「防抖」
在事件被觸發n秒後再執行回調,如果在這n秒內又被觸發,則重新計時。
function debounce(fn, delay) {
let timer = null
return function(...args) {
let context = this
if(timer) clearTimeout(timer)
timer = setTimeout(function(){
fn.apply(context,args)
},delay)
}
}
手寫AJAXfunction ajax(url, method) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open(url, method, true)
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(xhr.responseText)
} else if (xhr.status === 404) {
reject(new Error('404'))
}
} else {
reject('請求數據失敗')
}
}
xhr.send(null)
})
}
數組去重function unique_3(array) {
var obj = {};
return array.filter(function (item, index, array) {
return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
})
}
手寫bind函數Function.prototype.mybind = function(context, ...args) {
return (...newArgs) => {
return this.call(context, ...args, ...newArgs)
}
}
實現callFunction.prototype.mycall = function (context, ...args) {
context = Object(context) || window
let fn = Symbol(1)
context[fn] = this
let result = context[fn](...args)
delete context[fn]
return result
}
實現一個快排function quickSort(arr){
if (arr.length <= 1) return arr;
let index = Math.floor(arr.length / 2)
let pivot = arr.splice(index, 1)[0],
left = [],
right = [];
for(let i = 0; i < arr.length; i++){
if(pivot > arr[i]){
left.push(arr[i])
}else{
right.push(arr[i])
}
}
return quickSort(left).concat([pivot],quickSort(right))
}
數組的扁平化function flatDeep(arr) {
return arr.reduce((res, cur) => {
if(Array.isArray(cur)){
return [...res, ...flatDeep(cur)]
}else{
return [...res, cur]
}
},[])
}
深拷貝function deepClone(obj, hash = new WeakMap()) {
if (obj instanceof RegExp) return new RegExp(obj)
if (obj instanceof Date) return new Date(obj)
if (obj === null || typeof obj !== 'object') return obj
if (hash.has(obj)) return obj
let res = new obj.constructor();
hash.set(obj, res)
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
res[key] = deepClone(obj[key],hash)
}
}
return res
}
實現高階函數柯裡化function currying(fn, ...args) {
if (fn.length > args.length) {
return (...newArgs) => currying(fn, ...args, ...newArgs)
} else {
return fn(...args)
}
}
寄生組合式繼承function inherit(Son, Father) {
// 創建對象,創建父類原型的一個副本
let prototype = Object.create(Father.prototype)
// 增強對象,彌補因重寫原型而失去的默認的constructor 屬性
prototype.construct = Son
// 指定對象,將新創建的對象賦值給子類的原型
Son.prototype = prototype
}
this「this 永遠指向最後調用它的那個對象」
主要有下面幾個規則
默認指向,作為普通函數調用,指向window,嚴格模式下指向undefined使用call/apply/bind 顯示改變this指向ECMAScript6 怎麼寫 class,為什麼會出現 class 這種東西?❝在我看來 ES6 新添加的 class 只是為了補充 js 中缺少的一些面向對象語言的特性,但本質上來說它只是一種語法糖,不是一個新的東西,其背後還是原型繼承的思想。通過加入 class 可以有利於我們更好的組織代碼。在 class 中添加的方法,其實是添加在類的原型上的。
❞哪些操作會造成內存洩漏?相關知識點:
第一種情況是我們由於使用未聲明的變量,而意外的創建了一個全局變量,而使這個變量一直留在內存中無法被回收。
第二種情況是我們設置了 setInterval 定時器,而忘記取消它,如果循環函數有對外部變量的引用的話,那麼這個變量會被一直留
在內存中,而無法被回收。
第三種情況是我們獲取一個 DOM 元素的引用,而後面這個元素被刪除,由於我們一直保留了對這個元素的引用,所以它也無法被回
收。
第四種情況是不合理的使用閉包,從而導致某些變量一直被留在內存當中。
Object.is()使用過嗎?跟 === 和 == 區別三等號判等(判斷嚴格),比較時不進行隱式類型轉換,(類型不同則會返回false)使用 Object.is 來進行相等判斷時,一般情況下和三等號的判斷相同,它處理了一些特殊的情況,比如 -0 和 +0 不再相等,兩個 NaN 認定為是相等的。JS事件循環機制了解嗎因為 js 是單線程運行的,在代碼執行的時候,通過將不同函數的執行上下文壓入執行棧中來保證代碼的有序執行。在執行同步代碼的時候,如果遇到了異步事件,js 引擎並不會一直等待其返回結果,而是會將這個事件掛起,繼續執行執行棧中的其他任務。當異步事件執行完畢後,再將異步事件對應的回調加入到與當前執行棧中不同的另一個任務隊列中等待執行。任務隊列可以分為宏任務對列和微任務對列,當前執行棧中的事件執行完畢後,js 引擎首先會判斷微任務對列中是否有任務可以執行,如果有就將微任務隊首的事件壓入棧中執行。當微任務對列中的任務都執行完成後再去判斷宏任務對列中的任務。微任務包括了 promise 的回調、node 中的 process.nextTick 、對 Dom 變化監聽的 MutationObserver。
宏任務包括了 script 腳本的執行、setTimeout ,setInterval ,setImmediate 一類的定時事件,還有如 I/O 操作、UI 渲染等。
立即執行函數是什麼?聲明一個函數,並馬上調用這個匿名函數就叫做立即執行函數;也可以說立即執行函數是一種語法,讓你的函數在定義以後立即執行;
寫法👇
(function () {alert("我是匿名函數")}()) //用括號把整個表達式包起來
(function () {alert("我是匿名函數")})() //用括號把函數包起來
!function () {alert("我是匿名函數")}() //求反,我們不在意值是多少,只想通過語法檢查
+function () {alert("我是匿名函數")}()
-function () {alert("我是匿名函數")}()
~function () {alert("我是匿名函數")}()
void function () {alert("我是匿名函數")}()
new function () {alert("我是匿名函數")}()作用:
立即執行函數內部形成了一個單獨的作用域,可以封裝一些外部無法讀取的私有變量什麼是 JSONP,什麼是 CORS,什麼是跨域?這個我有篇文章已經總結啦,所以這裡就直接跳到對應文章吧,傳送門
發布訂閱者模式class EventEmitter {
constructor(){
this.list = {}
}
on(key,fn){
if(!this.list[key]){
this.list[key] = []
}
this.list[key].push(fn)
return this
}
once(key,fn) {
if(!this.list[key]){
this.list[key] = []
}
this.list[key].push(fn)
this.list[key].flag = this.list[key].length;
return this
}
emit(key, args){
let that = this;
let fns = this.list[key]
if(!fns || fns.length === 0) return false
for(let i = 0; i < fns.length; i++) {
fns[i].apply(this, args)
if(fns.flag === i){
that.off(key,fns[i-1])
}
}
}
off(key,fn) {
let fns = this.list[key];
let len = fns.length,
k = -1;
for(let i = 0; i < len; i++) {
if(fns[i].name === fn.name){ // 刪除
k = i;
break;
}
}
if(k !== -1) {
this.list[key].splice(k,1)
}
}
allOff(key) {
if(key === undefined){
this.list = {}
}else{
this.list[key] = []
}
}
}下面是測試數據
var emitter = new EventEmitter();
function handleOne(a, b, c) {
console.log('第一個監聽函數', a, b, c)
}
function handleSecond(a, b, c) {
console.log('第二個監聽函數', a, b, c)
}
function handleThird(a, b, c) {
console.log('第三個監聽函數', a, b, c)
}
emitter.on("demo", handleOne)
.once("demo", handleSecond)
.on("demo", handleThird);
emitter.emit('demo', [1, 2, 3]);
// => 第一個監聽函數 1 2 3
// => 第二個監聽函數 1 2 3
// => 第三個監聽函數 1 2 3
emitter.off('demo', handleThird);
emitter.emit('demo', [1, 2, 3]);
// => 第一個監聽函數 1 2 3
emitter.allOff();
emitter.emit('demo', [1, 2, 3]);
// nothing
瀏覽器相關這個瀏覽器專題的話,我之前也總結過啦,所以這裡就貼出地址,有興趣的可以去補一補基礎知識,大部分的知識點下面也提及到了,就不單獨拿出來梳理啦👇
往期文章總結「瀏覽器工作原理」寫給女友的秘籍-瀏覽器組成&網絡請求篇(1.2W字)(270+👍)
「瀏覽器工作原理」寫給女友的秘籍-渲染流程篇(1.1W字)(280+👍)
Cookie V.S. LocalStorage V.S. SessionStorage V.S. Session其中的一個相同點,就是它們保存在瀏覽器端,且同源的。
那麼不同點是哪些呢👇
異同點分類生命周期存儲容量存儲位置cookie默認保存在內存中,隨瀏覽器關閉失效(如果設置過期時間,在到過期時間後失效)4KB保存在客戶端,每次請求時都會帶上localStorage理論上永久有效的,除非主動清除。4.98MB(不同瀏覽器情況不同,safari 2.49M)保存在客戶端,不與服務端交互。節省網絡流量sessionStorage僅在當前網頁會話下有效,關閉頁面或瀏覽器後會被清除。4.98MB(部分瀏覽器沒有限制)同上操作方式接下來我們來具體看看如何來操作localStorage和sessionStorage
let obj = { name: "TianTianUp", age: 18 };
localStorage.setItem("name", "TianTianUp");
localStorage.setItem("info", JSON.stringify(obj));
複製代碼接著進入相同的域名時就能拿到相應的值👇
let name = localStorage.getItem("name");
let info = JSON.parse(localStorage.getItem("info"));
複製代碼從這裡可以看出,localStorage其實存儲的都是字符串,如果是存儲對象需要調用JSON的stringify方法,並且用JSON.parse來解析成對象。
應用場景localStorage 適合持久化緩存數據,比如頁面的默認偏好配置,如官網的logo,存儲Base64格式的圖片資源等;sessionStorage 適合一次性臨時數據保存,存儲本次瀏覽信息記錄,這樣子頁面關閉的話,就不需要這些記錄了,還有對表單信息進行維護,這樣子頁面刷新的話,也不會讓表單信息丟失。什麼是 XSS?如何預防?XSS 全稱是 Cross Site Scripting ,為了與CSS區分開來,故簡稱 XSS,翻譯過來就是「跨站腳本」。
XSS是指黑客往 HTML 文件中或者 DOM 中注入惡意腳本,從而在用戶瀏覽頁面時利用注入的惡意腳本對用戶實施攻擊的一種手段。
最開始的時候,這種攻擊是通過跨域來實現的,所以叫「跨域腳本」。發展到現在,往HTML文件中中插入惡意代碼方式越來越多,所以是否跨域注入腳本已經不是唯一的注入手段了,但是 XSS 這個名字卻一直保留至今。
注入惡意腳本可以完成這些事情:
監聽用戶行為,比如輸入帳號密碼後之間發給黑客伺服器一般的情況下,XSS攻擊有三種實現方式
存儲型 XSS 攻擊存儲型 XSS 攻擊大致步驟如下:
首先黑客利用站點漏洞將一段惡意 JavaScript 代碼提交到網站的資料庫中;然後用戶向網站請求包含了惡意 JavaScript 腳本的頁面;當用戶瀏覽該頁面的時候,惡意腳本就會將用戶的 Cookie 信息等數據上傳到伺服器。比如常見的場景:
在評論區提交一份腳本代碼,假設前後端沒有做好轉義工作,那內容上傳到伺服器,在頁面渲染的時候就會直接執行,相當於執行一段未知的JS代碼。這就是存儲型 XSS 攻擊。
反射型 XSS 攻擊反射型 XSS 攻擊指的就是惡意腳本作為**「網絡請求的一部分」**,隨後網站又把惡意的JavaScript腳本返回給用戶,當惡意 JavaScript 腳本在用戶頁面中被執行時,黑客就可以利用該腳本做一些惡意操作。
舉個例子:
http://TianTianUp.com?query=<script>alert("你受到了XSS攻擊")</script>
複製代碼如上,伺服器拿到後解析參數query,最後將內容返回給瀏覽器,瀏覽器將這些內容作為HTML的一部分解析,發現是Javascript腳本,直接執行,這樣子被XSS攻擊了。
這也就是反射型名字的由來,將惡意腳本作為參數,通過網絡請求,最後經過伺服器,在反射到HTML文檔中,執行解析。
主要注意的就是,「「伺服器不會存儲這些惡意的腳本,這也算是和存儲型XSS攻擊的區別吧」」。
基於 DOM 的 XSS 攻擊基於 DOM 的 XSS 攻擊是不牽涉到頁面 Web 伺服器的。具體來講,黑客通過各種手段將惡意腳本注入用戶的頁面中,在數據傳輸的時候劫持網絡數據包
常見的劫持手段有:
阻止 XSS 攻擊的策略以上講述的XSS攻擊原理,都有一個共同點:讓惡意腳本直接在瀏覽器執行。
針對三種不同形式的XSS攻擊,有以下三種解決辦法
「對輸入腳本進行過濾或轉碼」
對用戶輸入的信息過濾或者是轉碼
舉個例子👇
轉碼後👇
<script>alert('你受到XSS攻擊了')</script>這樣的代碼在 html 解析的過程中是無法執行的。
當然了對於<script>、<img>、<a>等關鍵字標籤也是可以過來的,效果如下👇
最後什麼都沒有剩下了
「利用 CSP」
該安全策略的實現基於一個稱作 Content-Security-Policy的 HTTP 首部。
可以移步MDN,有更加規範的解釋。我在這裡就是梳理一下吧。
CSP,即瀏覽器中的內容安全策略,它的核心思想大概就是伺服器決定瀏覽器加載哪些資源,具體來說有幾個功能👇
限制加載其他域下的資源文件,這樣即使黑客插入了一個 JavaScript 文件,這個 JavaScript 文件也是無法被加載的;「利用 HttpOnly」
由於很多 XSS 攻擊都是來盜用 Cookie 的,因此還可以通過使用 HttpOnly 屬性來保護我們 Cookie 的安全。這樣子的話,JavaScript 便無法讀取 Cookie 的值。這樣也能很好的防範 XSS 攻擊。
通常伺服器可以將某些 Cookie 設置為 HttpOnly 標誌,HttpOnly 是伺服器通過 HTTP 響應頭來設置的,下面是打開 Google 時,HTTP 響應頭中的一段:
set-cookie: NID=189=M8l6-z41asXtm2uEwcOC5oh9djkffOMhWqQrlnCtOI; expires=Sat, 18-Apr-2020 06:52:22 GMT; path=/; domain=.google.com; HttpOnly
總結XSS 攻擊是指瀏覽器中執行惡意腳本, 然後拿到用戶的信息進行操作。主要分為存儲型、反射型和文檔型。防範的措施包括:
對輸入內容過濾或者轉碼,尤其是類似於<script>、<img>、<a>標籤除了以上策略之外,我們還可以通過添加驗證碼防止腳本冒充用戶提交危險操作。而對於一些不受信任的輸入,還可以限制其輸入長度,這樣可以增大 XSS 攻擊的難度。
什麼是 CSRF?如何預防?CSRF 英文全稱是 Cross-site request forgery,所以又稱為「跨站請求偽造」,是指黑客引誘用戶打開黑客的網站,在黑客的網站中,利用用戶的登錄狀態發起的跨站請求。簡單來講,「CSRF 攻擊就是黑客利用了用戶的登錄狀態,並通過第三方的站點來做一些壞事。」
一般的情況下,點開一個誘導你的連結,黑客會在你不知情的時候做哪些事情呢
1. 自動發起 Get 請求黑客網頁裡面可能有一段這樣的代碼👇
<img src="http://bank.example/withdraw?amount=10000&for=hacker" >在受害者訪問含有這個img的頁面後,瀏覽器會自動向http://bank.example/withdraw?account=xiaoming&amount=10000&for=hacker發出一次HTTP請求。
bank.example就會收到包含受害者登錄信息的一次跨域請求。
2. 自動發起 POST 請求黑客網頁中有一個表單,自動提交的表單👇
<form action="http://bank.example/withdraw" method=POST>
<input type="hidden" name="account" value="xiaoming" />
<input type="hidden" name="amount" value="10000" />
<input type="hidden" name="for" value="hacker" />
</form>
<script> document.forms[0].submit(); </script>訪問該頁面後,表單會自動提交,相當於模擬用戶完成了一次POST操作。
同樣也會攜帶相應的用戶 cookie 信息,讓伺服器誤以為是一個正常的用戶在操作,讓各種惡意的操作變為可能。
3. 引誘用戶點擊連結這種需要誘導用戶去點擊連結才會觸發,這類的情況比如在論壇中發布照片,照片中嵌入了惡意連結,或者是以廣告的形式去誘導,比如:
<a href="http://test.com/csrf/withdraw.php?amount=1000&for=hacker" taget="_blank">
重磅消息!!!
<a/>點擊後,自動發送 get 請求,接下來和自動發 GET 請求部分同理。
以上三種情況,就是CSRF攻擊原理,跟XSS對比的話,CSRF攻擊並不需要將惡意代碼注入HTML中,而是跳轉新的頁面,利用「伺服器的驗證漏洞」和「用戶之前的登錄狀態」來模擬用戶進行操作
「防護策略」其實我們可以想到,黑客只能藉助受害者的**cookie**騙取伺服器的信任,但是黑客並不能憑藉拿到「cookie」,也看不到 「cookie」的內容。另外,對於伺服器返回的結果,由於瀏覽器「同源策略」的限制,黑客也無法進行解析。
❝這就告訴我們,我們要保護的對象是那些可以直接產生數據改變的服務,而對於讀取數據的服務,則不需要進行**CSRF**的保護。而保護的關鍵,是 「在請求中放入黑客所不能偽造的信息」
❞「用戶操作限制——驗證碼機制」
方法:添加驗證碼來識別是不是用戶主動去發起這個請求,由於一定強度的驗證碼機器無法識別,因此危險網站不能偽造一個完整的請求。
「1. 驗證來源站點」
在伺服器端驗證請求來源的站點,由於大量的CSRF攻擊來自第三方站點,因此伺服器跨域禁止來自第三方站點的請求,主要通過HTTP請求頭中的兩個Header
這兩個Header在瀏覽器發起請求時,大多數情況會自動帶上,並且不能由前端自定義內容。
伺服器可以通過解析這兩個Header中的域名,確定請求的來源域。
其中,「Origin」只包含域名信息,而「Referer」包含了具體的 URL 路徑。
在某些情況下,這兩者都是可以偽造的,通過AJax中自定義請求頭即可,安全性略差。
「2. 利用Cookie的SameSite屬性」
可以看看MDN對此的解釋
SameSite可以設置為三個值,Strict、Lax和None。
在Strict模式下,瀏覽器完全禁止第三方請求攜帶Cookie。比如請求sanyuan.com網站只能在sanyuan.com域名當中請求才能攜帶 Cookie,在其他網站請求都不能。在Lax模式,就寬鬆一點了,但是只能在 get 方法提交表單況或者a 標籤發送 get 請求的情況下可以攜帶 Cookie,其他情況均不能。在None模式下,Cookie將在所有上下文中發送,即允許跨域發送。「3. CSRF Token」
前面講到CSRF的另一個特徵是,攻擊者無法直接竊取到用戶的信息(Cookie,Header,網站內容等),僅僅是冒用Cookie中的信息。
那麼我們可以使用Token,在不涉及XSS的前提下,一般黑客很難拿到Token。
可以看看這篇文章,將了Token是怎麼操作的👉徹底理解cookie,session,token
Token(令牌)做為Web領域驗證身份是一個不錯的選擇,當然了,JWT有興趣的也可以去了解一下。
Token步驟如下:
「第一步:將CSRF Token輸出到頁面中」
❝首先,用戶打開頁面的時候,伺服器需要給這個用戶生成一個Token,該Token通過加密算法對數據進行加密,一般Token都包括隨機字符串和時間戳的組合,顯然在提交時Token不能再放在Cookie中了(XSS可能會獲取Cookie),否則又會被攻擊者冒用。因此,為了安全起見Token最好還是存在伺服器的Session中,之後在每次頁面加載時,使用JS遍歷整個DOM樹,對於DOM中所有的a和form標籤後加入Token。這樣可以解決大部分的請求,但是對於在頁面加載之後動態生成的HTML代碼,這種方法就沒有作用,還需要程式設計師在編碼時手動添加Token。
❞「第二步:頁面提交的請求攜帶這個Token」
❝對於GET請求,Token將附在請求地址之後,這樣URL 就變成 http://url?csrftoken=tokenvalue。而對於 POST 請求來說,要在 form 的最後加上:<input type=」hidden」 name=」csrftoken」 value=」tokenvalue」/>這樣,就把Token以參數的形式加入請求了。
❞「第三步:伺服器驗證Token是否正確」
❝當用戶從客戶端得到了Token,再次提交給伺服器的時候,伺服器需要判斷Token的有效性,驗證過程是先解密Token,對比加密字符串以及時間戳,如果加密字符串一致且時間未過期,那麼這個Token就是有效的。
❞非常感興趣的,可以仔細去閱讀一下相關的文章,Token是如何加密的,又是如何保證不被攻擊者獲取道。
總結CSRF(Cross-site request forgery), 即跨站請求偽造,本質是衝著瀏覽器分不清發起請求是不是真正的用戶本人,所以防範的關鍵在於在請求中放入黑客所不能偽造的信息。從而防止黑客偽造一個完整的請求欺騙伺服器。
「防範措施」:驗證碼機制,驗證來源站點,利用Cookie的SameSite屬性,CSRF Token
JS 垃圾回收機制這部分的知識點,基本上看別人寫的翻譯,然後按照別人的思路去完成的,所以這裡就推薦一篇我看的文章吧,個人覺得寫的還是挺好的,所以有興趣的可以了解一下,下面的文章👇
簡單了解JavaScript垃圾回收機制
計算機網絡部分這個專題也十分的重要,面試大廠的話,這個你得會,不問就不要緊,但是問到你的話,必須的會,我之前梳理過一篇文章,效果還不錯,這裡分享給大家👇
往期文章「查缺補漏」鞏固你的HTTP知識體系(930+👍)HTTP 狀態碼知道哪些?分別什麼意思?狀態碼:由3位數字組成,第一個數字定義了響應的類別
「1xx:指示信息,表示請求已接收,繼續處理」
「2xx:成功,表示請求已被成功接受,處理。」
200 OK:客戶端請求成功
204 No Content:無內容。伺服器成功處理,但未返回內容。一般用在只是客戶端向伺服器發送信息,而伺服器不用向客戶端返回什麼信息的情況。不會刷新頁面。
206 Partial Content:伺服器已經完成了部分GET請求(客戶端進行了範圍請求)。響應報文中包含Content-Range指定範圍的實體內容「3xx:重定向」
301 Moved Permanently:永久重定向,表示請求的資源已經永久的搬到了其他位置。
302 Found:臨時重定向,表示請求的資源臨時搬到了其他位置
303 See Other:臨時重定向,應使用GET定向獲取請求資源。303功能與302一樣,區別只是303明確客戶端應該使用GET訪問
307 Temporary Redirect:臨時重定向,和302有著相同含義。POST不會變成GET
304 Not Modified:表示客戶端發送附帶條件的請求(GET方法請求報文中的IF…)時,條件不滿足。返回304時,不包含任何響應主體。雖然304被劃分在3XX,但和重定向一毛錢關係都沒有「4xx:客戶端錯誤」
400 Bad Request:客戶端請求有語法錯誤,伺服器無法理解。
401 Unauthorized:請求未經授權,這個狀態代碼必須和WWW-Authenticate報頭域一起使用。
403 Forbidden:伺服器收到請求,但是拒絕提供服務
404 Not Found:請求資源不存在。比如,輸入了錯誤的url
415 Unsupported media type:不支持的媒體類型「5xx:伺服器端錯誤,伺服器未能實現合法的請求。」
500 Internal Server Error:伺服器發生不可預期的錯誤。
503 Server Unavailable:伺服器當前不能處理客戶端的請求,一段時間後可能恢復正常
長輪詢和短輪詢「短輪詢」
短輪詢(Polling)的實現思路就是瀏覽器端「每隔幾秒鐘向」伺服器端**發送http請求,服務端在收到請求後,不論是否有數據更新,都直接進行響應。**在服務端響應完成,就會關閉這個Tcp連接。
優點:就是兼容性比較好,只要支持http協議就可以實現該方式。缺點:很明顯消耗資源,因為下一次的建立Tcp是非常消耗資源的,伺服器端響應後就會關閉這個Tcp連接。function LongAjax() {
fetch(url).then(data => {
// 數據正確拿到後,dosometing
}).catch(err => {
// 發現錯誤,比如返回的數據為空等。
console.log(err);
});
}
setInterval(LongAjax, 5000);「長輪詢」
客戶端發送請求後伺服器端「不會立即」返回數據,伺服器端會「阻塞請求」連接不會「立即斷開」,直到伺服器端「有數據更新或者是連接超時」才返回,客戶端才再次發出請求新建連接、如此反覆從而獲取最新數據。
function LongAjax() {
fetch(url).then(data => {
// 數據正確拿到後,
LongPolling();
}).catch(err => {
// 出錯或者就是超時間
LongPolling();
});
}
LongAjax()
優點:長輪詢與短輪詢相比,明顯減少了很多不必要的http請求,節約資源。節點:連接掛起也會導致資源的浪費,停留在伺服器端。HTTP 緩存有哪幾種?瀏覽器緩存是性能優化的一個重要手段,對於理解緩存機制而言也是很重要的,我們來梳理一下吧👇
強緩存強緩存兩個相關欄位,「「Expires」」,「「Cache-Control」」。
「「強緩存分為兩種情況,一種是發送HTTP請求,一種不需要發送。」」
首先檢查強緩存,這個階段**不需要發送HTTP請求。**通過查找不同的欄位來進行,不同的HTTP版本所以不同。
HTTP1.0版本,使用的是Expires,HTTP1.1使用的是Cache-ControlExpiresExpires即過期時間,時間是相對於伺服器的時間而言的,存在於服務端返回的響應頭中,在這個過期時間之前可以直接從緩存裡面獲取數據,無需再次請求。比如下面這樣:
Expires:Mon, 29 Jun 2020 11:10:23 GMT
複製代碼表示該資源在2020年7月29日11:10:23過期,過期時就會重新向伺服器發起請求。
這個方式有一個問題:「「伺服器的時間和瀏覽器的時間可能並不一致」」,所以HTTP1.1提出新的欄位代替它。
Cache-ControlHTTP1.1版本中,使用的就是該欄位,這個欄位採用的時間是過期時長,對應的是max-age。
Cache-Control:max-age=6000
複製代碼上面代表該資源返回後6000秒,可以直接使用緩存。
當然了,它還有其他很多關鍵的指令,梳理了幾個重要的👇
注意點:
當Expires和Cache-Control同時存在時,優先考慮Cache-Control。當然了,當緩存資源失效了,也就是沒有命中強緩存,接下來就進入協商緩存👇協商緩存強緩存失效後,瀏覽器在請求頭中攜帶響應的緩存Tag來向伺服器發送請求,伺服器根據對應的tag,來決定是否使用緩存。
緩存分為兩種,「「Last-Modified」」 和 「「ETag」」。兩者各有優勢,並不存在誰對誰有絕對的優勢,與上面所講的強緩存兩個Tag所不同。
Last-Modified這個欄位表示的是**「最後修改時間」**。在瀏覽器第一次給伺服器發送請求後,伺服器會在響應頭中加上這個欄位。
瀏覽器接收到後,「「如果再次請求」」,會在請求頭中攜帶If-Modified-Since欄位,這個欄位的值也就是伺服器傳來的最後修改時間。
伺服器拿到請求頭中的If-Modified-Since的欄位後,其實會和這個伺服器中該資源的最後修改時間對比:
如果請求頭中的這個值小於最後修改時間,說明是時候更新了。返回新的資源,跟常規的HTTP請求響應的流程一樣。ETagETag是伺服器根據當前文件的內容,對文件生成唯一的標識,比如MD5算法,只要裡面的內容有改動,這個值就會修改,伺服器通過把響應頭把該欄位給瀏覽器。
瀏覽器接受到ETag值,會在下次請求的時候,將這個值作為**「If-None-Match」**這個欄位的內容,發給伺服器。
伺服器接收到**「If-None-Match」「後,會跟伺服器上該資源的」「ETag」**進行比對👇
如果兩者一樣的話,直接返回304,告訴瀏覽器直接使用緩存如果不一樣的話,說明內容更新了,返回新的資源,跟常規的HTTP請求響應的流程一樣兩者對比性能上,Last-Modified優於ETag,Last-Modified記錄的是時間點,而Etag需要根據文件的MD5算法生成對應的hash值。精度上,ETag``Last-Modified``ETag``Last-Modified編輯了資源文件,但是文件內容並沒有更改,這樣也會造成緩存失效。Last-Modified 能夠感知的單位時間是秒,如果文件在 1 秒內改變了多次,那麼這時候的 Last-Modified 並沒有體現出修改了。最後,「「如果兩種方式都支持的話,伺服器會優先考慮ETag」」。
緩存位置接下來我們考慮使用緩存的話,緩存的位置在哪裡呢?
瀏覽器緩存的位置的話,可以分為四種,優先級從高到低排列分別👇
Service Worker這個應用場景比如PWA,它借鑑了Web Worker思路,由於它脫離了瀏覽器的窗體,因此無法直接訪問DOM。它能完成的功能比如:離線緩存、消息推送和網絡代理,其中離線緩存就是**「Service Worker Cache」**。
Memory Cache指的是內存緩存,從效率上講它是最快的,從存活時間來講又是最短的,當渲染進程結束後,內存緩存也就不存在了。
Disk Cache存儲在磁碟中的緩存,從存取效率上講是比內存緩存慢的,優勢在於存儲容量和存儲時長。
Disk Cache VS Memory Cache兩者對比,主要的策略👇
內容使用率高的話,文件優先進入磁碟
比較大的JS,CSS文件會直接放入磁碟,反之放入內存。
Push Cache推送緩存,這算是瀏覽器中最後一道防線吧,它是HTTP/2的內容。具體我也不是很清楚,有興趣的可以去了解。
總結首先檢查Cache-Control, 嘗鮮,看強緩存是否可用否則進入協商緩存,發送HTTP請求,伺服器通過請求頭中的If-Modified-Since或者If-None-Match欄位檢查資源是否更新否則,返回304,直接告訴瀏覽器直接從緩存中去資源。GET 和 POST 的區別首先,我們的知道區別只是語義上有區別而已,但是面試的時候,肯定不能這麼回答的。
GET在瀏覽器回退時是無害的,而POST會再次提交請求。GET請求會被瀏覽器主動cache,而POST不會,除非手動設置。GET請求只能進行url編碼,而POST支持多種編碼方式。GET請求參數會被完整保留在瀏覽器歷史記錄裡,而POST中的參數不會被保留。GET請求大小一般是(1024位元組),http協議並沒有限制,而與伺服器,作業系統有關,POST理論上來說沒有大小限制,http協議規範也沒有進行大小限制,但實際上post所能傳遞的數據量根據取決於伺服器的設置和內存大小。對參數的數據類型,GET只接受ASCII字符,而POST沒有限制。GET比POST更不安全,因為參數直接暴露在URL上,所以不能用來傳遞敏感信息。Webpack這個面試也是會經常考的一部分了,所以掌握它還是很有必要的,我是從0到1配過它的,所以這裡我就沒有梳理筆記了,嗯,下面就推薦兩個文章,希望看完可以對你們有幫助。
實打實的從0到1配置webpack👇
「一勞永逸」由淺入深配置webpack4
針對面試的👇
關於webpack的面試題總結
算法與數據結構這個專題,我目前總結了三個板塊,速度有點慢,不過面試初級前端的話,應該是沒有問題的,需要了解的小夥伴可以看看我梳理的三篇👇
往期文章「算法與數據結構」鍊表的9個基本操作(180+👍)「算法與數據結構」DFS和BFS算法之美(210+👍)如果你跟我一樣,對算法也有所熱愛的話,我們可以互相討論下算法,或者關注我噠,我會一直更新算法噠。
模塊化將一個複雜的程序依據特定的規則(規範)封裝成幾個文件,然後將其組合在一起,這些只是向外暴露一些接口,或者方法,與其他模塊進行通信,這樣子叫做是模塊化的過程。
「為什麼要模塊化」,目的在於減少複雜性,減少它們相互之間的功能關係。使每個模塊功能單一。
「模塊化好處」
CommomJSCommonJS定義了兩個主要概念:
require函數,用於導入模塊
module.exports變量,用於導出模塊
require導入,代碼很簡單,let {count,addCount}=require("./utils")就可以了。
require的第一步是解析路徑獲取到模塊內容:
如果是帶有路徑的如/,./等等,則拼接出一個絕對路徑,然後先讀取緩存require.cache再讀取文件。如果沒有加後綴,則自動加後綴然後一一識別。首次加載後的模塊會緩存在require.cache之中,所以多次加載require,得到的對象是同一個。在執行模塊代碼的時候,會將模塊包裝成如下模式,以便於作用域在模塊範圍之內。modulelet count=0
function addCount(){
count++
}
module.exports={count,addCount}然後根據require執行代碼時需要加上的,那麼實際上我們的代碼長成這樣:
(function(exports, require, module, __filename, __dirname) {
let count=0
function addCount(){
count++
}
module.exports={count,addCount}
});
ES6模塊與CommonJS的區別CommonJS 模塊輸出的是一個值的拷貝,ES6 模塊輸出的是值的引用。CommonJS 模塊輸出的是值的拷貝,也就是說,一旦輸出一個值,模塊內部的變化就影響不到這個值。
ES6 模塊的運行機制與 CommonJS 不一樣。JS 引擎對腳本靜態分析的時候,遇到模塊加載命令import,就會生成一個只讀引用。等到腳本真正執行時,再根據這個只讀引用,到被加載的那個模塊裡面去取值。換句話說,ES6 的import有點像 Unix 系統的「符號連接」,原始值變了,import加載的值也會跟著變。因此,ES6 模塊是動態引用,並且不會緩存值,模塊裡面的變量綁定其所在的模塊。
CommonJS 模塊是運行時加載,ES6 模塊是編譯時輸出接口。運行時加載: CommonJS 模塊就是對象;即在輸入時是先加載整個模塊,生成一個對象,然後再從這個對象上面讀取方法,這種加載稱為「運行時加載」。
編譯時加載: ES6 模塊不是對象,而是通過 export 命令顯式指定輸出的代碼,import時採用靜態命令的形式。即在import時可以指定加載某個輸出值,而不是加載整個模塊,這種加載稱為「編譯時加載」。
模塊化開發怎麼做?我對模塊的理解是,一個模塊是實現一個特定功能的一組方法。在最開始的時候,js 只實現一些簡單的功能,所以並沒有模塊的概念,但隨著程序越來越複雜,代碼的模塊化開發變得越來越重要。
由於函數具有獨立作用域的特點,最原始的寫法是使用函數來作為模塊,幾個函數作為一個模塊,但是這種方式容易造成全局變量的汙染,並且模塊間沒有聯繫。
後面提出了對象寫法,通過將函數作為一個對象的方法來實現,這樣解決了直接使用函數作為模塊的一些缺點,但是這種辦法會暴露所有的所有的模塊成員,外部代碼可以修改內部屬性的值。
現在最常用的是立即執行函數的寫法,通過利用閉包來實現模塊私有作用域的建立,同時不會對全局作用域造成汙染。