webform 控件上面能写 前端事件吗_详细的网易前端秘籍—如何准备面试
前言
開門見山,這篇文章,適合「初級前端」,如果你還在校招的話,或者還在求職的話,可以看看本文,找一找靈感,希望對你們有幫助呀。
先說一下最近個人情況:2020年8月底已經拿到網易有道offer, 這算是我的第一份web前端工作吧,一直以來都是自學前端的,走過很多的彎路,之前的技術棧是Vue.js,目前往react方向走。
這是一次萬恒的三輪網易面經
我的感受就是,自己一邊梳理知識點,一邊總結歸納,收獲可能更大,所以打算把我梳理的部分分享出來,篇幅有點長,大家見諒呀。
覆蓋的點不是很全,分享給你們,希望你們秋招一切順利,offer收割機??????
HTML系列
你是如何理解 HTML 語義化的?
讓頁面內容結構化,它有如下優點
1、易于用戶閱讀,樣式丟失的時候能讓頁面呈現清晰的結構。2、有利于SEO,搜索引擎根據標簽來確定上下文和各個關鍵字的權重。
3、方便其他設備解析,如盲人閱讀器根據語義渲染網頁
4、有利于開發和維護,語義化更具可讀性,代碼更好維護,與CSS3關系更和諧
如:
代表頭部代表超鏈接區域定義文檔主要內容可以表示文章、博客等內容通常表示側邊欄或嵌入內容代表尾部可以跟面試官講的更具體一點?
第一個是荒野階段,那時候前端的代碼主要是后臺來寫的,所以那個時候寫的代碼主要是用table來布局的。
第二階段---美工階段,這個時候就有專門的人來前端代碼了,這個階段的布局主要是DIV+CSS布局,但是呢有一個問題,就是不夠語義化。
第三個階段-->> 前端階段,也就是利用具有語義的標簽,比如p,h1,h2,article,header,nav,main,aside,footer這些標簽,使用這些正確的標簽,可以表達正確的內容,也利于開發和維護。
meta viewport 是做什么用的,怎么寫?
通常viewport是指視窗、視口。瀏覽器上(也可能是一個app中的webview)用來顯示網頁的那部分區域。在移動端和pc端視口是不同的,pc端的視口是瀏覽器窗口區域,而在移動端有三個不同的視口概念:布局視口、視覺視口、理想視口
meta有兩個屬性name 和 http-equiv
name
keywords(關鍵字) ? 告訴搜索引擎,你網頁的關鍵字
description(網站內容描述) ? 用于告訴搜索引擎,你網站的主要內容。
viewport(移動端的窗口) ?后面介紹
robots(定義搜索引擎爬蟲的索引方式) robots用來告訴爬蟲哪些頁面需要索引,哪些頁面不需要索引
author(作者)
generator(網頁制作軟件)
copyright(版權)
http-equiv
?http-equiv顧名思義,相當于http的文件頭作用。
?有以下參數:
content-Type 設定網頁字符集
//舊的HTML,不推薦 //HTML5設定網頁字符集的方式,推薦使用UTF-8X-UA-Compatible(瀏覽器采用哪種版本來渲染頁面)
//指定IE和Chrome使用最新版本渲染當前頁面cache-control(請求和響應遵循的緩存機制)
expires(網頁到期時間)
你用過哪些 HTML 5 標簽?
有、、、、「canvas畫布」
const?ctx?=?canvas.getContext('2d');??//?獲取它的2d上下文ctx.fillStyle?=?'green';????//?設置筆刷的填充色
ctx.fillRect(10,?10,?100,?100);??//??利用畫筆范圍,矩形,比如圓
video
autoplay 布爾屬性;視頻馬上自動開始播放,不會停下來等著數據載入結束。
「controls」 ? 提供用戶控制,允許用戶控制視頻的播放,包括音量,跨幀,暫停/恢復播放。
「loop」 布爾屬性;指定后,會在視頻結尾的地方,自動返回視頻開始的地方。
「track」標簽表示的是字幕
「poster」 表示的是封面
"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頁面)?,F在的移動端混合開發軟件,如果對于交互渲染要求不是特別高的項目,基本都是這么玩的。
「WebView作用」
- 顯示和渲染Web頁面
- 直接使用html文件(網絡上或本地assets中)作布局
- 可和JavaScript交互調用
HTML5新特性:
增加拖放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對象。
"outer">"inner">Click?me!
因此上面的例子在事件冒泡的概念下發生click事件的順序應該是
「p -> div -> body -> html -> document」
「事件捕獲」
與事件冒泡相反,事件會從最外層開始發生,直到最具體的元素。
上面的例子在事件捕獲的概念下發生click事件的順序應該是
「document -> html -> body -> div -> p」
所以從上面的圖片來看?1-5是捕獲過程,5-6是目標階段,6-10是冒泡階段
addEventListener
addEventListener方法用來為一個特定的元素綁定一個事件處理函數,是JavaScript中的常用方法。
?element.addEventListener(event,?function,?useCapture)重點來看看第三個參數useCapture
- true - 事件句柄在捕獲階段執行(即在事件捕獲階段調用處理函數)
- false- false- 默認。事件句柄在冒泡階段執行(即表示在事件冒泡的階段調用事件處理函數)
所以我們通常來說,默認第三個參數不寫的話,是按照事件句柄在冒泡執行的。
attachEvent
兼容IE的寫法,默認是事件冒泡階段調用處理函數,寫事件名時候要加上"on"前綴("onload"、"onclick"等)。
object.attachEvent(event,?function)事件代理
利用事件流的特性,我們可以使用一種叫做事件代理的方法,其實利用的就是事件冒泡的機制。
"xxx">下面的內容是子元素1li內容>>>??這是span內容123????????下面的內容是子元素2li內容>>>??這是span內容123
????????下面的內容是子元素3li內容>>>??這是span內容123
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
"xxx">分割線-—---
var?dragging?=?falsevar?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」
"wrapper">"rigth">"left">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實現
"wrapper">"left">"main">"rigth">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部分」
"wrapper">"main">"inner">"left">"right">「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;
- baseline: 項目的第一行文字的基線對齊。
- 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 縮小比例 設置元素的收縮比例 ? 多出盒子的部分,按照比例的大小砍掉相應的大小,即比例越大,被砍的越大,默認值是1
- flex-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就是頁面上的一個隔離的獨立容器,容器里面的子元素不會影響到外面的元素。反之也如此。
下列方式會創建「塊格式化上下文」:
需要背的條件?
「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)
- 盒稱為浮動盒(floating boxes);
- 它位于當前行的開頭或末尾;
- 這「導致常規流環繞在它的周邊」,除非設置 clear 屬性;
絕對定位(Absolute positioning)
- 絕對定位方案,「盒從常規流中被移除」,不影響常規流的布局;
- 它的定位相對于它的包含塊,相關CSS屬性:top,bottom,left及right;
- 如果元素的屬性position為absolute或fixed,它是絕對定位元素;
- 對于position: absolute,元素定位將相對于最近的一個relative、fixed或absolute的父元素,如果沒有則相對于body;
獲取DOM
"css-cell">"heart">"sun">????
???let?oDiv?=?document.getElementById('css-cell')
????????let?oDiv1?=?document.getElementsByTagName('div')???//集合?根據標簽
????????let?oDiv2?=?document.querySelectorAll('div')?????//?集合??標簽???????
????????let?oDiv3?=?document.getElementsByClassName('heart')?????//?className????
Attribute與Property
attribute:是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 : 新的網址,必須與前頁面處在同一個域。瀏覽器的地址欄將顯示這個網址。
replaceState
??history.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滿足以下幾個特性,才使得其可以實現前端路由:
如此一來,我們就可以在hashchange事件里,根據hash值來更新對應的視圖,但不會去重新請求頁面,同時呢,也在history里增加了一條訪問記錄,用戶也仍然可以通過前進后退鍵實現UI的切換。
觸發hash值的變化有2種方法?
- 一種是通過a標簽,設置href屬性,當標簽點擊之后,地址欄會改變,同時會觸發hashchange事件
- 另一種是通過js直接賦值給location.hash,也會改變url,觸發hashchange事件。
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)????//?trueNumber.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('21N')?true
//?isNaN(NaN)??true
//?isNaN(123)?false
我們來看看Number.isNaN
Number.isNaN('1232N')??//?falseNumber.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.prototype
- Demo.__proto__
- Object.getPrototypeOf(Demo)
獲取對象屬性的方法
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]);
}
有哪些缺陷呢?
- 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語法遍歷的是數組元素的值
- for in 遍歷的是索引
- 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)
????????????}
????????}
手寫AJAX
function?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)
????}
}
實現call
Function.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?????????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指向
- new對象,被實例調用,指向的就是實例對象
- 箭頭函數:this指向的是上級作用域中的this
- class方法:該this指向的就是實例
ECMAScript6 怎么寫 class,為什么會出現 class 這種東西?
?在我看來 ES6 新添加的 class 只是為了補充 js 中缺少的一些面向對象語言的特性,但本質上來說它只是一種語法糖,不是一個新的東西,其背后還是原型繼承的思想。通過加入 class 可以有利于我們更好的組織代碼。在 class 中添加的方法,其實是添加在類的原型上的。
?哪些操作會造成內存泄漏?
相關知識點:
- 1.意外的全局變量
- 2.被遺忘的計時器或回調函數
- 3.脫離 DOM 的引用
- 4.閉包
第二種情況是我們設置了?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[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?????????????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
瀏覽器相關
這個瀏覽器專題的話,我之前也總結過啦,所以這里就貼出地址,有興趣的可以去補一補基礎知識,大部分的知識點下面也提及到了,就不單獨拿出來梳理啦?
往期文章總結
「查缺補漏」送你18道瀏覽器面試題(780+?)
「瀏覽器工作原理」寫給女友的秘籍-瀏覽器組成&網絡請求篇(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 攻擊
- 基于 DOM 的 XSS 攻擊
存儲型 XSS 攻擊
存儲型 XSS 攻擊大致步驟如下:
比如常見的場景:
在評論區提交一份腳本代碼,假設前后端沒有做好轉義工作,那內容上傳到服務器,在頁面渲染的時候就會直接執行,相當于執行一段未知的JS代碼。這就是存儲型 XSS 攻擊。
反射型 XSS 攻擊
反射型 XSS 攻擊指的就是惡意腳本作為**「網絡請求的一部分」**,隨后網站又把惡意的JavaScript腳本返回給用戶,當惡意 JavaScript 腳本在用戶頁面中被執行時,黑客就可以利用該腳本做一些惡意操作。
舉個例子:
http://TianTianUp.com?query=復制代碼
如上,服務器拿到后解析參數query,最后將內容返回給瀏覽器,瀏覽器將這些內容作為HTML的一部分解析,發現是Javascript腳本,直接執行,這樣子被XSS攻擊了。
這也就是反射型名字的由來,將惡意腳本作為參數,通過網絡請求,最后經過服務器,在反射到HTML文檔中,執行解析。
主要注意的就是,「「服務器不會存儲這些惡意的腳本,這也算是和存儲型XSS攻擊的區別吧」」。
基于 DOM 的 XSS 攻擊
基于 DOM 的 XSS 攻擊是不牽涉到頁面 Web 服務器的。具體來講,黑客通過各種手段將惡意腳本注入用戶的頁面中,在數據傳輸的時候劫持網絡數據包
常見的劫持手段有:
- WIFI路由器劫持
- 本地惡意軟件
阻止 XSS 攻擊的策略
以上講述的XSS攻擊原理,都有一個共同點:讓惡意腳本直接在瀏覽器執行。
針對三種不同形式的XSS攻擊,有以下三種解決辦法
「對輸入腳本進行過濾或轉碼」
對用戶輸入的信息過濾或者是轉碼
舉個例子?
轉碼后?
>script#39;你受到XSS攻擊了')>/script<這樣的代碼在 html 解析的過程中是無法執行的。
當然了對于、、等關鍵字標簽也是可以過來的,效果如下?
最后什么都沒有剩下了
「利用 CSP」
該安全策略的實現基于一個稱作 Content-Security-Policy的 HTTP 首部。
可以移步MDN,有更加規范的解釋。我在這里就是梳理一下吧。
CSP,即瀏覽器中的內容安全策略,它的核心思想大概就是服務器決定瀏覽器加載哪些資源,具體來說有幾個功能?
- 限制加載其他域下的資源文件,這樣即使黑客插入了一個 JavaScript 文件,這個 JavaScript 文件也是無法被加載的;
- 禁止向第三方域提交數據,這樣用戶數據也不會外泄;
- 提供上報機制,能幫助我們及時發現 XSS 攻擊。
- 禁止執行內聯腳本和未授權的腳本;
「利用 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 攻擊是指瀏覽器中執行惡意腳本, 然后拿到用戶的信息進行操作。主要分為存儲型、反射型和文檔型。防范的措施包括:
- 對輸入內容過濾或者轉碼,尤其是類似于、、標簽
- 利用CSP
- 利用Cookie的HttpOnly屬性
除了以上策略之外,我們還可以通過添加驗證碼防止腳本冒充用戶提交危險操作。而對于一些不受信任的輸入,還可以限制其輸入長度,這樣可以增大 XSS 攻擊的難度。
什么是 CSRF?如何預防?
CSRF 英文全稱是 Cross-site request forgery,所以又稱為“跨站請求偽造”,是指黑客引誘用戶打開黑客的網站,在黑客的網站中,利用用戶的登錄狀態發起的跨站請求。簡單來講,「CSRF 攻擊就是黑客利用了用戶的登錄狀態,并通過第三方的站點來做一些壞事?!?/strong>
一般的情況下,點開一個誘導你的鏈接,黑客會在你不知情的時候做哪些事情呢
1. 自動發起 Get 請求
黑客網頁里面可能有一段這樣的代碼?
在受害者訪問含有這個img的頁面后,瀏覽器會自動向http://bank.example/withdraw?account=xiaoming&amount=10000&for=hacker發出一次HTTP請求。
bank.example就會收到包含受害者登錄信息的一次跨域請求。
2. 自動發起 POST 請求
黑客網頁中有一個表單,自動提交的表單?
?"http://bank.example/withdraw"?method=POST>訪問該頁面后,表單會自動提交,相當于模擬用戶完成了一次POST操作。
同樣也會攜帶相應的用戶 cookie 信息,讓服務器誤以為是一個正常的用戶在操作,讓各種惡意的操作變為可能。
3. 引誘用戶點擊鏈接
這種需要誘導用戶去點擊鏈接才會觸發,這類的情況比如在論壇中發布照片,照片中嵌入了惡意鏈接,或者是以廣告的形式去誘導,比如:
重磅消息!!!
點擊后,自動發送 get 請求,接下來和自動發 GET 請求部分同理。
以上三種情況,就是CSRF攻擊原理,跟XSS對比的話,CSRF攻擊并不需要將惡意代碼注入HTML中,而是跳轉新的頁面,利用「服務器的驗證漏洞」和「用戶之前的登錄狀態」來模擬用戶進行操作
「防護策略」
其實我們可以想到,黑客只能借助受害者的**cookie**騙取服務器的信任,但是黑客并不能憑借拿到「cookie」,也看不到 「cookie」的內容。另外,對于服務器返回的結果,由于瀏覽器「同源策略」的限制,黑客也無法進行解析。
?這就告訴我們,我們要保護的對象是那些可以直接產生數據改變的服務,而對于讀取數據的服務,則不需要進行**CSRF**的保護。而保護的關鍵,是 「在請求中放入黑客所不能偽造的信息」
?「用戶操作限制——驗證碼機制」
方法:添加驗證碼來識別是不是用戶主動去發起這個請求,由于一定強度的驗證碼機器無法識別,因此危險網站不能偽造一個完整的請求。
「1. 驗證來源站點」
在服務器端驗證請求來源的站點,由于大量的CSRF攻擊來自第三方站點,因此服務器跨域禁止來自第三方站點的請求,主要通過HTTP請求頭中的兩個Header
- Origin Header
- Referer Header
這兩個Header在瀏覽器發起請求時,大多數情況會自動帶上,并且不能由前端自定義內容。
服務器可以通過解析這兩個Header中的域名,確定請求的來源域。
其中,「Origin」只包含域名信息,而「Referer」包含了具體的 URL 路徑。
在某些情況下,這兩者都是可以偽造的,通過AJax中自定義請求頭即可,安全性略差。
「2. 利用Cookie的SameSite屬性」
可以看看MDN對此的解釋
SameSite可以設置為三個值,Strict、Lax和None。
「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 的最后加上:這樣,就把Token以參數的形式加入請求了。
?「第三步:服務器驗證Token是否正確」
?當用戶從客戶端得到了Token,再次提交給服務器的時候,服務器需要判斷Token的有效性,驗證過程是先解密Token,對比加密字符串以及時間戳,如果加密字符串一致且時間未過期,那么這個Token就是有效的。
?非常感興趣的,可以仔細去閱讀一下相關的文章,Token是如何加密的,又是如何保證不被攻擊者獲取道。
總結
CSRF(Cross-site request forgery), 即跨站請求偽造,本質是沖著瀏覽器分不清發起請求是不是真正的用戶本人,所以防范的關鍵在于在請求中放入黑客所不能偽造的信息。從而防止黑客偽造一個完整的請求欺騙服務器。
「防范措施」:驗證碼機制,驗證來源站點,利用Cookie的SameSite屬性,CSRF Token
JS 垃圾回收機制
這部分的知識點,基本上看別人寫的翻譯,然后按照別人的思路去完成的,所以這里就推薦一篇我看的文章吧,個人覺得寫的還是挺好的,所以有興趣的可以了解一下,下面的文章?
簡單了解JavaScript垃圾回收機制
計算機網絡部分
這個專題也十分的重要,面試大廠的話,這個你得會,不問就不要緊,但是問到你的話,必須的會,我之前梳理過一篇文章,效果還不錯,這里分享給大家?
往期文章
- 「查缺補漏」鞏固你的HTTP知識體系(930+?)
HTTP 狀態碼知道哪些?分別什么意思?
狀態碼:由3位數字組成,第一個數字定義了響應的類別
「1xx:指示信息,表示請求已接收,繼續處理」
「2xx:成功,表示請求已被成功接受,處理?!?/strong>
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:服務器端錯誤,服務器未能實現合法的請求?!?/strong>
500 Internal Server Error:服務器發生不可預期的錯誤。503 Server Unavailable:服務器當前不能處理客戶端的請求,一段時間后可能恢復正常
長輪詢和短輪詢
「短輪詢」
短輪詢(Polling)的實現思路就是瀏覽器端「每隔幾秒鐘向」服務器端**發送http請求,服務端在收到請求后,不論是否有數據更新,都直接進行響應。**在服務端響應完成,就會關閉這個Tcp連接。
- 優點:就是兼容性比較好,只要支持http協議就可以實現該方式。
- 缺點:很明顯消耗資源,因為下一次的建立Tcp是非常消耗資源的,服務器端響應后就會關閉這個Tcp連接。
????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請求,一種不需要發送?!埂?/strong>
首先檢查強緩存,這個階段**不需要發送HTTP請求。**通過查找不同的字段來進行,不同的HTTP版本所以不同。
- HTTP1.0版本,使用的是Expires,HTTP1.1使用的是Cache-Control
Expires
Expires即過期時間,時間是相對于服務器的時間而言的,存在于服務端返回的響應頭中,在這個過期時間之前可以直接從緩存里面獲取數據,無需再次請求。比如下面這樣:
Expires:Mon,?29?Jun?2020?11:10:23?GMT復制代碼
表示該資源在2020年7月29日11:10:23過期,過期時就會重新向服務器發起請求。
這個方式有一個問題:「「服務器的時間和瀏覽器的時間可能并不一致」」,所以HTTP1.1提出新的字段代替它。
Cache-Control
HTTP1.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請求響應的流程一樣。
- 否則返回304,告訴瀏覽器直接使用緩存。
ETag
ETag是服務器根據當前文件的內容,對文件生成唯一的標識,比如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
- Memory Cache
- Disk Cache
- Push Cache
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字段檢查資源是否更新
- 資源更新,返回資源和200狀態碼。
- 否則,返回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
這個面試也是會經??嫉囊徊糠至?#xff0c;所以掌握它還是很有必要的,我是從0到1配過它的,所以這里我就沒有梳理筆記了,嗯,下面就推薦兩個文章,希望看完可以對你們有幫助。
實打實的從0到1配置webpack?
「一勞永逸」由淺入深配置webpack4
針對面試的?
關于webpack的面試題總結
算法與數據結構
這個專題,我目前總結了三個板塊,速度有點慢,不過面試初級前端的話,應該是沒有問題的,需要了解的小伙伴可以看看我梳理的三篇?
往期文章
- 「算法與數據結構」鏈表的9個基本操作(180+?)
- 「算法與數據結構」梳理6大排序算法(210+?)
- 「算法與數據結構」DFS和BFS算法之美(210+?)
如果你跟我一樣,對算法也有所熱愛的話,我們可以互相討論下算法,或者關注我噠,我會一直更新算法噠。
模塊化
將一個復雜的程序依據特定的規則(規范)封裝成幾個文件,然后將其組合在一起,這些只是向外暴露一些接口,或者方法,與其他模塊進行通信,這樣子叫做是模塊化的過程。
「為什么要模塊化」,目的在于減少復雜性,減少它們相互之間的功能關系。使每個模塊功能單一。
「模塊化好處」
- 避免了命名沖突(減少了命名空間污染)
- 更好的分離,按需加載
- 更高復用性
- 高維護性
CommomJS
CommonJS定義了兩個主要概念:
require函數,用于導入模塊
module.exports變量,用于導出模塊
require
導入,代碼很簡單,let {count,addCount}=require("./utils")就可以了。
require的第一步是解析路徑獲取到模塊內容:
- 如果是核心模塊,比如fs,就直接返回模塊
- 如果是帶有路徑的如/,./等等,則拼接出一個絕對路徑,然后先讀取緩存require.cache再讀取文件。如果沒有加后綴,則自動加后綴然后一一識別。
- .js 解析為JavaScript 文本文件
- .json解析JSON對象
- .node解析為二進制插件模塊
- 首次加載后的模塊會緩存在require.cache之中,所以多次加載require,得到的對象是同一個。
- 在執行模塊代碼的時候,會將模塊包裝成如下模式,以便于作用域在模塊范圍之內。
module
let?count=0function?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 只實現一些簡單的功能,所以并沒有模塊的概念,但隨著程序越來越復雜,代碼的模塊化開發變得越來越重要。
由于函數具有獨立作用域的特點,最原始的寫法是使用函數來作為模塊,幾個函數作為一個模塊,但是這種方式容易造成全局變量的污染,并且模塊間沒有聯系。
后面提出了對象寫法,通過將函數作為一個對象的方法來實現,這樣解決了直接使用函數作為模塊的一些缺點,但是這種辦法會暴露所有的所有的模塊成員,外部代碼可以修改內部屬性的值。
現在最常用的是立即執行函數的寫法,通過利用閉包來實現模塊私有作用域的建立,同時不會對全局作用域造成污染。
?【JS面試題】數組去重(6種方法)震驚面試官
?【超高頻面試題】這兩段代碼的返回結果你知道是什么嗎?
?【css面試題】用css畫0.5px的線條
?【css面試題】css實現氣泡框效果
?【css炫酷動畫】讓面試官眼前一亮的故障風格文字動畫
END
?支持三連
1.看到這里了就點個在看支持下吧,你的「在看」是我創作的動力。
2.關注公眾號前端印象,「一起交流進步」!
3.關注公眾號回復【加群】,拉你進技術交流群一起玩轉前端。
總結
以上是生活随笔為你收集整理的webform 控件上面能写 前端事件吗_详细的网易前端秘籍—如何准备面试的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: e3 1231 v3 达芬奇_黑苹果E3
- 下一篇: python处理表格很厉害么_Pytho