言
金九銀十跳槽季,offer快到碗里來,前端面試考點眾多,而網上各個知識點的博客文章又太多,看的眼花繚亂...所以我整理了一下常見知識點的精華文章,每個知識點的文章控制在3篇以內,盡量覆蓋該知識點的下容易被面試到的所有內容,文章都是之前自己讀過的,確定是精華干貨。文章會一直更新,也歡迎大家推薦精華文章,大家共同學習進步呀!
display:flex; 在父元素設置,子元素受彈性盒影響,默認排成一行,如果超出一行,按比例壓縮 flex:1; 子元素設置,設置子元素如何分配父元素的空間,flex:1,子元素寬度占滿整個父元素align-items:center 定義子元素在父容器中的對齊方式,center 垂直居中justify-content:center 設置子元素在父元素中居中,前提是子元素沒有把父元素占滿,讓子元素水平居中。
transtion transition-property 規定設置過渡效果的 CSS 屬性的名稱。
transition-duration 規定完成過渡效果需要多少秒或毫秒。
transition-timing-function 規定速度效果的速度曲線。
transition-delay 定義過渡效果何時開始。
animation屬性可以像Flash制作動畫一樣,通過控制關鍵幀來控制動畫的每一步,實現更為復雜的動畫效果。
ainimation實現動畫效果主要由兩部分組成:
通過類似Flash動畫中的幀來聲明一個動畫;
在animation屬性中調用關鍵幀聲明的動畫。
translate 3D建模效果
圖片中的 alt屬性是在圖片不能正常顯示時出現的文本提示。alt有利于SEO優化
圖片中的 title屬性是在鼠標在移動到元素上的文本提示。
復制代碼 <style>
div {
width: 0;
height: 0;
border-top: 40px solid transparent;
border-left: 40px solid transparent;
border-right: 40px solid transparent;
border-bottom: 40px solid #ff0000;
}
</style>
</head>
<body>
<div></div>
</body>
標準盒子模型:寬度=內容的寬度(content)+ border + padding
低版本IE盒子模型:寬度=內容寬度(content+border+padding)
已知寬度,block元素 ,添加添加margin:0 auto屬性。
已知寬度,絕對定位的居中 ,上下左右都為0,margin:auto
復制代碼div {
position: relative / fixed; /* 相對定位或絕對定位均可 */
width:500px;
height:300px;
top: 50%;
left: 50%;
margin-top:-150px;
margin-left:-250px;
外邊距為自身寬高的一半 */
background-color: pink; /* 方便看效果 */
}
.container {
display: flex;
align-items: center; /* 垂直居中 */
justify-content: center; /* 水平居中 */
}
.container div {
width: 100px; /* 可省 */
height: 100px; /* 可省 */
background-color: pink; /* 方便看效果 */
}
clear清除浮動(添加空div法)在浮動元素下方添加空div,并給該元素寫css樣式 {clear:both;height:0;overflow:hidden;}
給浮動元素父級設置高度
父級同時浮動(需要給父級同級元素添加浮動)
父級設置成inline-block,其margin: 0 auto居中方式失效
給父級添加overflow:hidden 清除浮動方法
萬能清除法 after偽類 清浮動(現在主流方法,推薦使用)
復制代碼float_div:after{
content:".";
clear:both;
display:block;
height:0;
overflow:hidden;
visibility:hidden;
}
.float_div{
zoom:1
}
圣杯布局/雙飛翼布局
復制代碼 <style>
* {
margin: 0;
padding: 0;
}
.middle,
.left,
.right {
position: relative;
float: left;
min-height: 130px;
}
.container {
padding: 0 220px 0 200px;
overflow: hidden;
}
.left {
margin-left: -100%;
left: -200px;
width: 200px;
background: red;
}
.right {
margin-left: -220px;
right: -220px;
width: 220px;
background: green;
}
.middle {
width: 100%;
background: blue;
word-break: break-all;
}
</style>
</head>
<body>
<div class='container'>
<div class='middle'></div>
<div class='left'></div>
<div class='right'></div>
</div>
</body>
display:none 隱藏對應的元素,在文檔布局中不再給它分配空間,它各邊的元素會合攏,就當他從來不存在。
visibility:hidden 隱藏對應的元素,但是在文檔布局中仍保留原來的空間。
link屬于HTML標簽,而@import是CSS提供的頁面被加載的時,link會同時被加載,而@import引用的CSS會等到頁面被加載完再加載
import只在IE5以上才能識別,而link是HTML標簽,無兼容問題
link方式的樣式的權重 高于@import的權重.
共同點: 改變行內元素的呈現方式,display被置為block 讓元素脫離普通流,不占據空間 默認會覆蓋到非定位元素上
不同點: absolute的”根元素“是可以設置的 fixed的”根元素“固定為瀏覽器窗口。當你滾動網頁,fixed元素與瀏覽器窗口之間的距離是不變的。
Animation和transition大部分屬性是相同的,他們都是隨時間改變元素的屬性值,他們的主要區別是transition需要觸發一個事件才能改變屬性, 而animation不需要觸發任何事件的情況下才會隨時間改變屬性值,并且transition為2幀,從from .... to,而animation可以一幀一幀的。
transition 規定動畫的名字 規定完成過渡效果需要多少秒或毫秒 規定速度效果 定義過渡效果何時開始 animation 指定要綁定到選擇器的關鍵幀的名稱
復制代碼不同級別:總結排序:!important > 行內樣式>ID選擇器 > 類選擇器 > 標簽 > 通配符 > 繼承 > 瀏覽器默認屬性
1.屬性后面加!import 會覆蓋頁面內任何位置定義的元素樣式
2.作為style屬性寫在元素內的樣式
3.id選擇器
4.類選擇器
5.標簽選擇器
6.通配符選擇器(*)
7.瀏覽器自定義或繼承
**同一級別:后寫的會覆蓋先寫的**
css選擇器的解析原則:選擇器定位DOM元素是從右往左的方向,這樣可以盡早的過濾掉一些不必要的樣式規則和元素
復制代碼 多個圖片集成在一個圖片中的圖
使用雪碧圖可以減少網絡請求的次數,加快允許的速度
通過background-position,去定位圖片在屏幕的哪個位置
相同點: 都常用來判斷一個變量是否為空,或者是什么類型的。
不同點: typeof 返回值是一個字符串,用來說明變量的數據類型 instanceof 用于判斷一個變量是否屬于某個對象的實例.
復制代碼visibility:hidden、display:none、z-index=-1、opacity:0
1.opacity:0,該元素隱藏起來了,但不會改變頁面布局,并且,如果該元素已經綁定了一些事件,如click事件也能觸發
2.visibility:hidden,該元素隱藏起來了,但不會改變頁面布局,但是不會觸發該元素已經綁定的事件
3.display:node, 把元素隱藏起來,并且會改變頁面布局,可以理解成在頁面中把該元素刪掉
淺克隆: 只是拷貝了基本類型的數據,而引用類型數據,復制后也是會發生引用,我們把這種拷貝叫做“(淺復制)淺拷貝”,換句話說,淺復制僅僅是指向被復制的內存地址,如果原地址中對象被改變了,那么淺復制出來的對象也會相應改變。
深克隆: 創建一個新對象,屬性中引用的其他對象也會被克隆,不再指向原有對象地址。 JSON.parse、JSON.stringify()
let定義塊級作用域變量 沒有變量的提升,必須先聲明后使用 let聲明的變量,不能與前面的let,var,conset聲明的變量重名
const 定義只讀變量 const聲明變量的同時必須賦值,const聲明的變量必須初始化,一旦初始化完畢就不允許修改 const聲明變量也是一個塊級作用域變量 const聲明的變量沒有“變量的提升”,必須先聲明后使用 const聲明的變量不能與前面的let, var , const聲明的變量重 const定義的對象\數組中的屬性值可以修改,基礎數據類型不可以
ES6可以給形參函數設置默認值
在數組之前加上三個點(...)展開運算符
數組的解構賦值、對象的解構賦值
箭頭函數的特點 箭頭函數相當于匿名函數,是不能作為構造函數的,不能被new 箭頭函數沒有arguments實參集合,取而代之用...剩余運算符解決 箭頭函數沒有自己的this。他的this是繼承當前上下文中的this 箭頭函數沒有函數原型 箭頭函數不能當做Generator函數,不能使用yield關鍵字 不能使用call、apply、bind改變箭頭函數中this指向 Set數據結構,數組去重
=賦值
==返回一個布爾值;相等返回true,不相等返回false; 允許不同數據類型之間的比較; 如果是不同類型的數據進行,會默認進行數據類型之間的轉換; 如果是對象數據類型的比較,比較的是空間地址
===只要數據類型不一樣,就返回false;
復制代碼1、js工廠模式
2、js構造函數模式
3、js原型模式
4、構造函數+原型的js混合模式
5、構造函數+原型的動態原型模式
6、觀察者模式
7、發布訂閱模式
call() 和apply()的第一個參數相同,就是指定的對象。這個對象就是該函數的執行上下文。
call()和apply()的區別就在于,兩者之間的參數。
call()在第一個參數之后的 后續所有參數就是傳入該函數的值。
apply() 只有兩個參數,第一個是對象,第二個是數組,這個數組就是該函數的參數。 bind() 方法和前兩者不同在于: bind() 方法會返回執行上下文被改變的函數而不會立即執行,而前兩者是 直接執行該函數。他的參數和call()相同。
原型鏈繼承 核心: 將父類的實例作為子類的原型
構造繼承 核心:使用父類的構造函數來增強子類實例,等于是復制父類的實例屬性給子類
實例繼承 核心:為父類實例添加新特性,作為子類實例返回
拷貝繼承
組合繼承 核心:通過調用父類構造,繼承父類的屬性并保留傳參的優點,然后通過將父類實例作為子類原型,實現 函數復用
寄生組合繼承 核心:通過寄生方式,砍掉父類的實例屬性,這樣,在調用兩次父類的構造的時候,就不會初始化兩次實 例方法/屬性,避免的組合繼承的缺點
個人感覺,簡單來說閉包就是在函數里面聲明函數,本質上說就是在函數內部和函數外部搭建起一座橋梁,使得子函數可以訪問父函數中所有的局部變量,但是反之不可以,這只是閉包的作用之一,另一個作用,則是保護變量不受外界污染,使其一直存在內存中,在工作中我們還是少使用閉包的好,因為閉包太消耗內存,不到萬不得已的時候盡量不使用。
把所有的對象共用的屬性全部放在堆內存的一個對象(共用屬性組成的對象),然后讓每一個對象的 __proto__存儲這個「共用屬性組成的對象」的地址。而這個共用屬性就是原型,原型出現的目的就是為了減少不必要的內存消耗。而原型鏈就是對象通過__proto__向當前實例所屬類的原型上查找屬性或方法的機制,如果找到Object的原型上還是沒有找到想要的屬性或者是方法則查找結束,最終會返回undefined
將html代碼按照深度優先遍歷來生成DOM樹。 css文件下載完后也會進行渲染,生成相應的CSSOM。 當所有的css文件下載完且所有的CSSOM構建結束后,就會和DOM一起生成Render Tree。 接下來,瀏覽器就會進入Layout環節,將所有的節點位置計算出來。 最后,通過Painting環節將所有的節點內容呈現到屏幕上。
復制代碼1、瀏覽器的地址欄輸入URL并按下回車。
2、瀏覽器查找當前URL是否存在緩存,并比較緩存是否過期。3、DNS解析URL對應的IP。
4、根據IP建立TCP連接(三次握手)。
5、HTTP發起請求。
6、服務器處理請求,瀏覽器接收HTTP響應。
7、渲染頁面,構建DOM樹。
8、關閉TCP連接(四次揮手)
相同點 都是保存在瀏覽器端,且同源的。
不同點
同源策略(協議+端口號+域名要相同)
1、jsonp跨域(只能解決get) 原理:動態創建一個script標簽。利用script標簽的src屬性不受同源策略限制,因為所有的src屬性和href屬性都不受同源策略的限制,可以請求第三方服務器資源內容
步驟: 1).去創建一個script標簽 2).script的src屬性設置接口地址 3).接口參數,必須要帶一個自定義函數名,要不然后臺無法返回數據 4).通過定義函數名去接受返回的數據
2、document.domain 基礎域名相同 子域名不同
3、window.name 利用在一個瀏覽器窗口內,載入所有的域名都是共享一個window.name
4、服務器設置對CORS的支持 原理:服務器設置Access-Control-Allow-Origin HTTP響應頭之后,瀏覽器將會允許跨域請求
5、利用h5新特性window.postMessage()
1.創建ajax實例
2.執行open 確定要訪問的鏈接 以及同步異步
3.監聽請求狀態
4.發送請求
ES6的set對象 先將原數組排序,在與相鄰的進行比較,如果不同則存入新數組
復制代碼function unique(arr){
var arr2=arr.sort();
var res=[arr2[0]];
for(var i=1;i<arr2.length;i++){
if(arr2[i] !==res[res.length-1]){
res.push(arr2[i]);
}
}
return res;
}
利用下標查詢
function unique(arr){
var newArr=[arr[0]];
for(var i=1;i<arr.length;i++){
if(newArr.indexOf(arr[i])==-1){
newArr.push(arr[i]);
}
}
return newArr;
}
2開頭
3開頭
以4開頭的都是客戶端的問題;
以5開頭都是服務端的問題
同步:在同一時間內做一件事情
異步:在同一時間內做多個事情 JS是單線程的,每次只能做一件事情,JS運行在瀏覽器中,瀏覽器是多線程的,可以在同一時間執行多個任務。
定時器、ajax、事件綁定、回調函數、async await、promise
三次握手
四次揮手
建立連接的時候, 服務器在LISTEN狀態下,收到建立連接請求的SYN報文后,把ACK和SYN放在一個報文里發送給客戶端。 而關閉連接時,服務器收到對方的FIN報文時,僅僅表示對方不再發送數據了但是還能接收數據,而自己也未必全部數據都發送給對方了,所以己方可以立即關閉,也可以發送一些數據給對方后,再發送FIN報文給對方來表示同意現在關閉連接,因此,己方ACK和FIN一般都會分開發送,從而導致多了一次。
如果元素類型發生變化,直接替換 如果是文本,則比較文本里面的內容,是否有差異,如果是元素就需要比較當前元素的屬性是否相等,會先比較key, 在比較類型 為什么 react中循環 建議不要使用索引 ,如果純為了展示 那可以使用索引
全局作用域
私有作用域
塊級作用域
上級作用域
他是ES6中新增加的一個類(new Promise),目的是為了管理JS中的異步編程的,所以把他稱為“Promise設計模式” new Promise 經歷三個狀態:padding(準備狀態:初始化成功、開始執行異步的任務)、fullfilled(成功狀態)、rejected(失敗狀態)==Promise本身是同步編程的,他可以管理異步操作的(重點),new Promise的時候,會把傳遞的函數立即執行 Promise函數天生有兩個參數,resolve(當異步操作執行成功,執行resolve方法),rejected(當異步操作失敗,執行reject方法) then()方法中有兩個函數,第一個傳遞的函數是resolve,第二個傳遞的函數是reject ajax中false代表同步,true代表異步,如果使用異步,不等ajax徹底完成
相同點
不同點
注意:forEach對于空數組是不會調用回調函數的。
async/await函數是異步代碼的新方式
async/await是基于promise實現的
async/await使異步代碼更像同步代碼
await 只能在async函數中使用,不能再普通函數中使用,要成對出現
默認返回一個promise實例,不能被改變
await下面的代碼是異步,后面的代碼是同步的
全局作用域下的this指向window 如果給元素的事件行為綁定函數,那么函數中的this指向當前被綁定的那個元素 函數中的this,要看函數執行前有沒有 . , 有 . 的話,點前面是誰,this就指向誰,如果沒有點,指向window 自執行函數中的this永遠指向window 定時器中函數的this指向window 構造函數中的this指向當前的實例 call、apply、bind可以改變函數的this指向 箭頭函數中沒有this,如果輸出this,就會輸出箭頭函數定義時所在的作用域中的this
所有的函數數據類型都天生自帶一個prototype屬性,該屬性的屬性值是一個對象 prototype的屬性值中天生自帶一個constructor屬性,其constructor屬性值指向當前原型所屬的類 所有的對象數據類型,都天生自帶一個_proto_屬性,該屬性的屬性值指向當前實例所屬類的原型
復制代碼promise、generator、async/await
promise: 1.是一個對象,用來傳遞異步操作的信息。代表著某個未來才會知道結果的時間,并未這個事件提供統一的api,供進異步處理
2.有了這個對象,就可以讓異步操作以同步的操作的流程來表達出來,避免層層嵌套的回調地獄
3.promise代表一個異步狀態,有三個狀態pending(進行中),Resolve(以完成),Reject(失敗)
4.一旦狀態改變,就不會在變。任何時候都可以得到結果。從進行中變為以完成或者失敗
promise.all() 里面狀態都改變,那就會輸出,得到一個數組
promise.race() 里面只有一個狀態變為rejected或者fulfilled即輸出
promis.finally()不管指定不管Promise對象最后狀態如何,都會執行的操作(本質上還是then方法的特例)
復制代碼事件流描述的是從頁面中接受事件的順序,事件 捕獲階段 處于目標階段 事件冒泡階段 addeventListener 最后這個布爾值參數如果是true,表示在捕獲階段調用事件處理程序;如果是false,表示在冒泡階段調用事件處理程序。
1、事件捕獲階段:實際目標div在捕獲階段不會接受事件,也就是在捕獲階段,事件從document到<html>再到<body>就停止了。
2、處于目標階段:事件在div發生并處理,但是事件處理會被看成是冒泡階段的一部分。
3、冒泡階段:事件又傳播回文檔
阻止冒泡事件event.stopPropagation()
function stopBubble(e) {
if (e && e.stopPropagation) { // 如果提供了事件對象event 這說明不是IE瀏覽器
e.stopPropagation()
} else {
window.event.cancelBubble=true //IE方式阻止冒泡
}
}
阻止默認行為event.preventDefault()
function stopDefault(e) {
if (e && e.preventDefault) {
e.preventDefault()
} else {
// IE瀏覽器阻止函數器默認動作的行為
window.event.returnValue=false
}
}
在DOM標準事件模型中,是先捕獲后冒泡。但是如果要實現先冒泡后捕獲的效果, 對于同一個事件,監聽捕獲和冒泡,分別對應相應的處理函數,監聽到捕獲事件,先暫緩執行,直到冒泡事件被捕獲后再執行捕獲事件。
復制代碼千萬不要使用typeof來判斷對象和數組,因為這種類型都會返回object。
typeOf()是判斷基本類型的Boolean,Number,symbol, undefined, String。 對于引用類型:除function,都返回object null返回object。
installOf() 用來判斷A是否是B的實例,installof檢查的是原型。
toString() 是Object的原型方法,對于 Object 對象,直接調用 toString() 就能返回 [Object Object] 。而對于其他對象,則需要通過 call / apply 來調用才能返回正確的類型信息。
hasOwnProperty()方法返回一個布爾值,指示對象自身屬性中是否具有指定的屬性,該方法會忽略掉那些從原型鏈上繼承到的屬性。
isProperty()方法測試一個對象是否存在另一個對象的原型鏈上。
復制代碼因為js是單線程的。瀏覽器遇到etTimeout 和 setInterval會先執行完當前的代碼塊,在此之前會把定時器推入瀏覽器的
待執行時間隊列里面,等到瀏覽器執行完當前代碼之后會看下事件隊列里有沒有任務,有的話才執行定時器里的代碼
復制代碼 1.slice(start,end):方法可以從已有數組中返回選定的元素,返回一個新數組,
包含從start到end(不包含該元素)的數組方法
注意:該方法不會更新原數組,而是返回一個子數組
2.splice():該方法想或者從數組中添加或刪除項目,返回被刪除的項目。(該方法會改變原數組)
splice(index, howmany,item1,...itemx)
·index參數:必須,整數規定添加或刪除的位置,使用負數,從數組尾部規定位置
·howmany參數:必須,要刪除的數量,
·item1..itemx:可選,向數組添加新項目
3.map():會返回一個全新的數組。使用于改變數據值的時候。會分配內存存儲空間數組并返回,forEach()不會返回數據
4.forEach(): 不會返回任何有價值的東西,并且不打算改變數據,單純的只是想用數據做一些事情,他允許callback更改原始數組的元素
5.reduce(): 方法接收一個函數作為累加器,數組中的每一個值(從左到右)開始縮減,最終計算一個值,不會改變原數組的值
6.filter(): 方法創建一個新數組,新數組中的元素是通過檢查指定數組中符合條件的所有元素。它里面通過function去做處理
vue是一個漸進式的JS框架。他易用,靈活,高效; 可以把一個頁面分隔成多個組件;當其他頁面有類似功能時,直接讓封裝的組件進行復用; 他是構建用戶界面的聲明式框架,只關心圖層;不關心具體是如何實現的
Vue的雙向數據綁定是由數據劫持結合發布者訂閱者實現的。 數據劫持是通過Object.defineProperty()來劫持對象數據的setter和getter操作。 在數據變動時作你想做的事
原理 通過Observer來監聽自己的model數據變化,通過Compile來解析編譯模板指令,最終利用Watcher搭起Observer和Compile之間的通信橋梁,達到數據變化->視圖更新 在初始化vue實例時,遍歷data這個對象,給每一個鍵值對利用Object.definedProperty對data的鍵值對新增get和set方法,利用了事件監聽DOM的機制,讓視圖去改變數據
react整體是函數式的思想,把組件設計成純組件,狀態和邏輯通過參數傳入,所以在react中,是單向數據流;
vue的思想是響應式的,也就是基于是數據可變的,通過對每一個屬性建立Watcher來監聽,當屬性變化的時候,響應式的更新對應的虛擬dom。
復制代碼頁面通過mapAction異步提交事件到action。action通過commit把對應參數同步提交到mutation。
mutation會修改state中對于的值。 最后通過getter把對應值跑出去,在頁面的計算屬性中
通過mapGetter來動態獲取state中的值
state中保存著共有數據,數據是響應式的 getter可以對state進行計算操作,主要用來過濾一些數據,可以在多組件之間復用 mutations定義的方法動態修改state中的數據,通過commit提交方法,方法必須是同步的 actions將mutations里面處理數據的方法變成異步的,就是異步操作數據,通store.dispatch來分發actions,把異步的方法寫在actions中,通過commit提交mutations,進行修改數據。 modules:模塊化vuex
hash ——即地址欄URL中的#符號(此hsah 不是密碼學里的散列運算) hash 雖然出現URL中,但不會被包含在HTTP請求中,對后端完全沒有影響,因此改變hash不會重新加載頁面。 history ——利用了HTML5 History Interface 中新增的pushState() 和replaceState() 方法
這兩個方法應用于瀏覽器的歷史記錄站,在當前已有的back、forward、go 的基礎之上,它們提供了對歷史記錄進行修改的功能。只是當它們執行修改是,雖然改變了當前的URL,但你瀏覽器不會立即向后端發送請求。
當 Vue.js 用v-for正在更新已渲染過的元素列表時,它默認用“就地復用”策略。 如果數據項的順序被改變,Vue 將不會移動 DOM 元素來匹配數據項的順序,而是簡單復用此處每個元素,并且確保它在特定索引下顯示已被渲染過的每個元素。
key的作用主要是為了高效的更新虛擬DOM。
$route是“路由信息對象”,包括path,params,hash,query,fullPath,matched,name等路由信息參數。 $router是“路由實例”對象包括了路由的跳轉方法,鉤子函數等。
導航守衛 router.beforeEach 全局前置守衛
復制代碼// main.js 入口文件
import router from './router'; // 引入路由
router.beforeEach((to, from, next)=> {
next();
});
router.beforeResolve((to, from, next)=> {
next();
});
router.afterEach((to, from)=> {
console.log('afterEach 全局后置鉤子');
});
路由獨享的守衛 你可以在路由配置上直接定義 beforeEnter 守衛
復制代碼const router=new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next)=> {
// ...
}
}
]
})
組件內的守衛 你可以在路由組件內直接定義以下路由導航守衛
復制代碼const Foo={
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染該組件的對應路由被 confirm 前調用
// 不!能!獲取組件實例 `this`
// 因為當守衛執行前,組件實例還沒被創建
},
beforeRouteUpdate (to, from, next) {
// 在當前路由改變,但是該組件被復用時調用
// 舉例來說,對于一個帶有動態參數的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉的時候,
// 由于會渲染同樣的 Foo 組件,因此組件實例會被復用。而這個鉤子就會在這個情況下被調用。
// 可以訪問組件實例 `this`
},
beforeRouteLeave (to, from, next) {
// 導航離開該組件的對應路由時調用,我們用它來禁止用戶離開
// 可以訪問組件實例 `this`
// 比如還未保存草稿,或者在用戶離開前,
將setInterval銷毀,防止離開之后,定時器還在調用。
}
}
請求后臺資源的模塊。
復制代碼$ npm install axios -S裝好
然后發送的是跨域,需在配置文件中config/index.js進行設置。后臺如果是Tp5則定義一個資源路由。 js中使用import進來,然后.get或.post。返回在.then函數中如果成功,失敗則是在.catch函數中
1.不要在模板里面寫過多表達式
2.循環調用子組件時添加key
3.頻繁切換的使用v-show,不頻繁切換的使用v-if
4.盡量少用float,可以用flex
5.按需加載,可以用require或者import()按需加載需要的組件
6.路由懶加載
extend 是構造一個組件的語法器。 然后這個組件你可以作用到Vue.component這個全局注冊方法里 還可以在任意vue模板里使用組件。 也可以作用到vue實例或者某個組件中的components屬性中并在內部使用apple組件。 Vue.component 你可以創建 ,也可以取組件。
png24位的圖片在iE6瀏覽器上出現背景 解決方案是做成PNG8.也可以引用一段腳本處理.
瀏覽器默認的margin和padding不同。 解決方案是加一個全局的*{margin:0;padding:0;}來統一。
IE6雙邊距bug:塊屬性標簽float后,又有橫行的margin情況下,在ie6顯示margin比設置的大。
浮動ie產生的雙倍距離(IE6雙邊距問題:在IE6下,如果對元素設置了浮動,同時又設置了margin-left或margin-right,margin值會加倍。) #box{ float:left; width:10px; margin:0 0 0 100px;}
復制代碼=> 相同點:
1.數據驅動頁面,提供響應式的試圖組件
2.都有virtual DOM,組件化的開發,通過props參數進行父子之間組件傳遞數據,都實現了webComponents規范
3.數據流動單向,都支持服務器的渲染SSR
4.都有支持native的方法,react有React native, vue有wexx=> 不同點:
1.數據綁定:Vue實現了雙向的數據綁定,react數據流動是單向的
2.數據渲染:大規模的數據渲染,react更快
3.使用場景:React配合Redux架構適合大規模多人協作復雜項目,Vue適合小快的項目
4.開發風格:react推薦做法jsx + inline style把html和css都寫在js了
vue是采用webpack + vue-loader單文件組件格式,html, js, css同一個文件
復制代碼Redux數據流里,reduces其實是根據之前的狀態(previous state)和現有的action(current action)
更新state(這個state可以理解為上下累加器的結果)
每次redux reducer被執行時,state和action被傳入,這個state根據action進行累加或者是'自身消減'(reduce),
進而返回最新的state,這也就是典型reduce函數的用法:state -> action -> state
復制代碼refs就想一個逃生窗,允許我們之間訪問dom元素或者組件實例,可以向組件添加一個ref屬性的值是一個回調函數,
它將接受地城dom元素或組件的已掛在實例,作為第一個參數
復制代碼幫組我們跟蹤哪些項目已更改、添加、從列表中刪除,key是獨一無二的,可以讓我們高效的去定位元素,并且操作它
復制代碼三個狀態:Mounting(已插入真實的DOM)
Updating(正在被重新渲染)
Unmounting(已移除真實的DOM)
componentDIdMount 在第一次渲染后調用,只在客服端。之后組件已經生成對應的DOM結構,
componentDidUpdate 在組件完成更新后立即調用,在出初始化是不會調用
復制代碼父組件通過props 給子組件傳遞數據,子組件則是通過調用父組件傳給它的函數給父組件傳遞數據。
復制代碼虛擬DOM相當于在js和真實dom中間加了一個緩存,利用dom diff算法避免了沒有必要的doom操作,從而提高性能
具體實現步驟:
·用JavaScript對象結構表示DOM樹的結構;然后用這個樹構建一個真正的DOM樹,插到文檔中
·當狀態變更的時候,重新構造一棵樹的對象樹,然后用新的樹和舊的樹進行對比,記錄兩棵樹差異
·把2所記錄的差異應用到步驟1所構建的真正的DOM樹上,試圖就更新了。
復制代碼1.把樹形結構按照層級分解,只比較同級元素
2.給列表結構的每個單元添加key屬性,方便比較。在實際代碼中,會對新舊兩棵樹進行一個深度優先的遍歷,這樣每個節點都會有一個標記
3.在深度優先遍歷的時候,每遍歷到一個節點就把該節點和新的樹進行對比。如果有差異的話就記錄到一個對象里面
Vritual DOM 算法主要實現上面步驟的三個函數:element, diff, patch。然后就可以實際的進行使用
react只會匹配相同的class的component(這里的class指的是組件的名字)
合并操作,條用component的setState方法的時候,React將其標記為dirty.到每一個時間循環借宿,React檢查所有標記dirty的component重新繪制
4.選擇性子樹渲染。可以重寫shouldComponentUpdate提高diff的性能
復制代碼flux的最大特點,就是數據的‘單向流動’
1.用戶訪問View
2.View發出用戶的Action
3.Dispatcher收到Action,要求state進行相應的更新
4.store更新后,發出一個‘change’事件后,更新頁面
復制代碼shouldComponentUpdate 這個方法用來判斷是否需要調用render方法重新描繪dom.因為dom的描繪非常消耗性能,
如果我們在shouldComponentUpdate方法中能夠寫出更優化的dom diff算法,可以極大的提高性能
復制代碼根據組件的職責通常把組件分為UI組件和容器組件
UI組件負責UI的呈現,容器組件負責管理數據和邏輯
兩者通過React-redux提供connect方法聯系起來
復制代碼setState通過一個隊列機制實現state更新,當執行setState時,會將需要更新的state很后放入狀態隊列
而不會立即更新this.state,隊列機制可以高效地批量更新state。如果不通過setState而直接修改this.state的值
那么該state將不會被放入狀態隊列中。當下次調用setState并對狀態隊列進行合并時,就會忽略之前修改的state,造成不可預知的錯誤
同時,也利用了隊列機制實現了setState的異步更新,避免了頻繁的重復更新state
同步更新state:
setState 函數并不會阻塞等待狀態更新完畢,因此 setNetworkActivityIndicatorVisible 有可能先于數據渲染完畢就執行。
第二個參數是一個回調函數,在setState的異步操作結束并且組件已經重新渲染的時候執行
也就是說,我們可以通過這個回調來拿到更新的state的值,實現代碼的同步
例子:componentDidMount() {
fetch('https://test.com')
.then((res)=> res.json())
.then(
(data)=> {
this.setState({ data:data });
StatusBar.setNetworkActivityIndicatorVisible(false);
}
復制代碼1.異步加載模塊
2.提取第三庫
3.代碼壓縮
4.去除不必要的插件
復制代碼一、減少代碼體積 1.使用CommonsChunksPlugin 提取多個chunk之間的通用模塊,減少總體代碼體積
2.把部分依賴轉移到CDN上,避免每次編譯過程都由Webpack處理
3.對一些組件庫采用按需加載,避免無用的代碼
二、減少目錄檢索范圍
·在使用loader的時候,通過制定exclude和include選項,減少loader遍歷的目錄范圍,從而加快webpack編譯速度
三、減少檢索路經:resolve.alias可以配置webpack模塊解析的別名,對于比較深的解析路經,可以對其配置alias
復制代碼 1、首屏加載和按需加載,懶加載
2、資源預加載
3、圖片壓縮處理,使用base64內嵌圖片
4、合理緩存dom對象
5、使用touchstart代替click(click 300毫秒的延遲)
6、利用transform:translateZ(0),開啟硬件GUP加速
7、不濫用web字體,不濫用float(布局計算消耗性能),減少font-size聲明
8、使用viewport固定屏幕渲染,加速頁面渲染內容
9、盡量使用事件代理,避免直接事件綁定
復制代碼1.減少入口文件體積
2.靜態資源本地緩存
3.開啟Gzip壓縮
4.使用SSR,nuxt.js
復制代碼由來:
300毫米延遲解決的是雙擊縮放。雙擊縮放,手指在屏幕快速點擊兩次。safari瀏覽器就會將網頁縮放值原始比例。由于用戶可以雙擊縮放或者是滾動的操作,
當用戶點擊屏幕一次之后,瀏覽器并不會判斷用戶確實要打開至這個鏈接,還是想要進行雙擊操作
因此,safair瀏覽器就會等待300ms,用來判斷用戶是否在次點擊了屏幕
解決方案:1.禁用縮放,設置meta標簽 user-scalable=no
2.fastclick.js
原理:FastClick的實現原理是在檢查到touchend事件的時候,會通過dom自定義事件立即
發出click事件,并把瀏覽器在300ms之后真正的click事件阻止掉
fastclick.js還可以解決穿透問題
在不改變外部行為的前提下,簡化結構、添加可讀性
復制代碼 2XX(成功處理了請求狀態)
200 服務器已經成功處理請求,并提供了請求的網頁
201 用戶新建或修改數據成功
202 一個請求已經進入后臺
204 用戶刪除成功
3XX(每次請求使用的重定向不要超過5次)
304 網頁上次請求沒有更新,節省帶寬和開銷
4XX(表示請求可能出錯,妨礙了服務器的處理)
400 服務器不理解請求的語法
401 用戶沒有權限(用戶名,密碼輸入錯誤)
403 用戶得到授權(401相反),但是訪問被禁止
404 服務器找不到請求的網頁,
5XX(表示服務器在處理請求的時候發生內部錯誤)
500 服務器遇到錯誤,無法完成請求
503 服務器目前無法使用(超載或停機維護)
復制代碼1.服務器首先產生Etag,服務器可在稍后使用它來判斷頁面是否被修改。本質上,客戶端通過該記號傳回服務器要求服務器驗證(客戶端)緩存)
2.304是 HTTP的狀態碼,服務器用來標識這個文件沒有被修改,不返回內容,瀏覽器接受到這個狀態碼會去去找瀏覽器緩存的文件
3.流程:客戶端請求一個頁面A。服務器返回頁面A,并在A上加一個Tage客服端渲染該頁面,并把Tage也存儲在緩存中。客戶端再次請求頁面A
并將上次請求的資源和ETage一起傳遞給服務器。服務器檢查Tage.并且判斷出該頁面自上次客戶端請求之后未被修改。直接返回304
last-modified: 客服端請求資源,同時有一個last-modified的屬性標記此文件在服務器最后修改的時間
客服端第二次請求此url時,根據http協議。瀏覽器會向服務器發送一個If-Modified-Since報頭,
詢問該事件之后文件是否被修改,沒修改返回304
有了Last-Modified,為什么還要用ETag?
1、因為如果在一秒鐘之內對一個文件進行兩次更改,Last-Modified就會不正確(Last—Modified不能識別秒單位的修改)
2、某些服務器不能精確的得到文件的最后修改時間
3、一些文件也行會周期新的更改,但是他的內容并不改變(僅僅改變修改的事件),這個時候我們并不希望客戶端認為文件被修改,而重新Get
ETag,為什么還要用Last-Modified?
1、兩者互補,ETag的判斷的缺陷,比如一些圖片等靜態文件的修改
2、如果每次掃描內容都生成ETag比較,顯然要比直接比較修改時間慢的多。
ETag是被請求變量的實體值(文件的索引節,大小和最后修改的時間的Hash值)
1、ETag的值服務器端對文件的索引節,大小和最后的修改的事件進行Hash后得到的。
復制代碼1.get數據是存放在url之后,以?分割url和傳輸數據,參數之間以&相連; post方法是把提交的數據放在http包的Body中
2.get提交的數據大小有限制,(因為瀏覽器對url的長度有限制),post的方法提交的數據沒有限制
3.get需要request.queryString來獲取變量的值,而post方式通過request.from來獲取變量的值
4.get的方法提交數據,會帶來安全問題,比如登錄一個頁面,通過get的方式提交數據,用戶名和密碼就會出現在url上
復制代碼1.超文本的傳輸協議,是用于從萬維網服務器超文本傳輸到本地資源的傳輸協議
2.基于TCP/IP通信協議來傳遞數據(HTML,圖片資源)
3.基于運用層的面向對象的協議,由于其簡潔、快速的方法、適用于分布式超媒體信息系統
4.http請求信息request:
請求行(request line)、請求頭部(header),空行和請求數據四部分構成
請求行,用來說明請求類型,要訪問的資源以及所使用的HTTP版本.
請求頭部,用來說明服務器要使用的附加信息
空行,請求頭部后面的空行是必須的
請求數據也叫主體,可以添加任意的其他數據。
5.http相應信息Response
狀態行、消息報頭、空行和響應正文
狀態行,由HTTP協議版本號, 狀態碼, 狀態消息 三部分組成
消息報頭,用來說明客戶端要使用的一些附加信息
空行,消息報頭后面的空行是必須的
響應正文,服務器返回給客戶端的文本信息。
復制代碼https:是以安全為目標的HTTP通道,簡單講是HTTP的安全版本,通過SSL加密
http:超文本傳輸協議。是一個客服端和服務器端請求和應答的標準(tcp),使瀏覽器更加高效,使網絡傳輸減少
復制代碼長連接:HTTP1.0需要使用keep-alive參數來告知服務器建立一個長連接,而HTP1.1默認支持長連接
節約寬帶:HTTP1.1支持只發送一個header信息(不帶任何body信息)
host域(設置虛擬站點,也就是說,web server上的多個虛擬站點可以共享同一個ip端口):HTTP1.0沒有host域
1.http2采用的二進制文本傳輸數據,而非http1文本格式,二進制在協議的解析和擴展更好
2.數據壓縮:對信息頭采用了HPACK進行壓縮傳輸,節省了信息頭帶來的網絡流量
3.多路復用:一個連接可以并發處理多個請求
4.服務器推送:我們對支持HTTP2.0的web server請求數據的時候,服務器會順便把一些客戶端需要的資源一起推送到客戶端,免得客戶端再次創建連接發送請求到服務器端獲取。這種方式非常合適加載靜態資源
復制代碼1.web緩存就是存在于客戶端與服務器之間的一個副本、當你第一個發出請求后,緩存根據請求保存輸出內容的副本
2.緩存的好處
(1)減少不必要的請求
(2)降低服務器的壓力,減少服務器的消耗
(3)降低網絡延遲,加快頁面打開速度(直接讀取瀏覽器的數據)
復制代碼1.sql注入原理:是將sql代碼偽裝到輸入參數中,傳遞到服務器解析并執行的一種攻擊手法。也就是說,
在一些對server端發起的請求參數中植入一些sql代碼,server端在執行sql操作時,會拼接對應參數,
同時也將一些sql注入攻擊的“sql”拼接起來,導致會執行一些預期之外的操作。
防范:1.對用戶輸入進行校驗
2.不適用動態拼接sql
2.XSS(跨站腳本攻擊):往web頁面插入惡意的html標簽或者js代碼。
舉例子:在論壇放置一個看是安全的鏈接,竊取cookie中的用戶信息
防范:1.盡量采用post而不使用get提交表單
2.避免cookie中泄漏用戶的隱式
3.CSRF(跨站請求偽裝):通過偽裝來自受信任用戶的請求
舉例子:黃軼老師的webapp音樂請求數據就是利用CSRF跨站請求偽裝來獲取QQ音樂的數據
防范:在客服端頁面增加偽隨機數,通過驗證碼
XSS和CSRF的區別:
1.XSS是獲取信息,不需要提前知道其他用戶頁面的代碼和數據包
2.CSRF代替用戶完成指定的動作,需要知道其他頁面的代碼和數據包
復制代碼1.盡可能的避開互聯網有可能影響數據傳輸速度和穩定性的瓶頸和環節。使內容傳輸的更快更穩定。
2.關鍵技術:內容存儲和分發技術中
3.基本原理:廣泛采用各種緩存服務器,將這些緩存服務器分布到用戶訪問相對的地區或者網絡中。當用戶訪問網絡時利用全局負載技術
將用戶的訪問指向距離最近的緩存服務器,由緩存服務器直接相應用戶的請求(全局負載技術)
復制代碼客服端發c起請求連接服務器端s確認,服務器端也發起連接確認客服端確認。
第一次握手:客服端發送一個請求連接,服務器端只能確認自己可以接受客服端發送的報文段
第二次握手: 服務端向客服端發送一個鏈接,確認客服端收到自己發送的報文段
第三次握手: 服務器端確認客服端收到了自己發送的報文段
復制代碼1.查詢NDS(域名解析),獲取域名對應的IP地址 查詢瀏覽器緩存
2.瀏覽器與服務器建立tcp鏈接(三次握手)
3.瀏覽器向服務器發送http請求(請求和傳輸數據)
4.服務器接受到這個請求后,根據路經參數,經過后端的一些處理生成html代碼返回給瀏覽器
5.瀏覽器拿到完整的html頁面代碼開始解析和渲染,如果遇到外部的css或者js,圖片一樣的步驟
6.瀏覽器根據拿到的資源對頁面進行渲染,把一個完整的頁面呈現出來
復制代碼流程:解析html以及構建dom樹 -> 構建render樹 -> 布局render樹 -> 繪制render樹
概念:1.構建DOM樹: 渲染引擎解析HTML文檔,首先將標簽轉換成DOM樹中的DOM node(包括js生成的標簽)生成內容樹
2.構建渲染樹: 解析對應的css樣式文件信息(包括js生成的樣式和外部的css)
3.布局渲染樹:從根節點遞歸調用,計算每一個元素的大小,位置等。給出每個節點所在的屏幕的精準位置
4.繪制渲染樹:遍歷渲染樹,使用UI后端層來繪制每一個節點
重繪:當盒子的位置、大小以及其他屬性,例如顏色、字體大小等到確定下來之后,瀏覽器便把這些顏色都按照各自的特性繪制一遍,將內容呈現在頁面上
觸發重繪的條件:改變元素外觀屬性。如:color,background-color等
重繪是指一個元素外觀的改變所觸發的瀏覽器行為,瀏覽器會根據元素的新屬性重新繪制,使元素呈現新的外觀
注意:table及其內部元素需要多次計算才能確定好其在渲染樹中節點的屬性值,比同等元素要多發時間,要盡量避免使用table布局
重排(重構/回流/reflow): 當渲染書中的一部分(或全部)因為元素的規模尺寸,布局,隱藏等改變而需要重新構建,這就是回流。
每個頁面都需要一次回流,就是頁面第一次渲染的時候
重排一定會影響重繪,但是重繪不一定會影響重排
復制代碼1.瀏覽器預先加載css后,可以不必等待HTML加載完畢就可以渲染頁面了
2.其實HTML渲染并不會等到完全加載完在渲染頁面,而是一邊解析DOM一邊渲染。
3.js寫在尾部,主要是因為js主要扮演事件處理的功能,一方面很多操作是在頁面渲染后才執行的。另一方面可以節省加載時間,使頁面能夠更加的加載,提高用戶的良好體驗
但是隨著JS技術的發展,JS也開始承擔頁面渲染的工作。比如我們的UI其實可以分被對待,把渲染頁面的js放在前面,時間處理的js放在后面
復制代碼1.indexBD: 是h5的本地存儲庫,把一些數據存儲到瀏覽器中,沒網絡,瀏覽器可以從這里讀取數據,離線運用。5m
2.Cookie: 通過瀏覽器記錄信息確認用戶身份,最大4kb,這也就限制了傳輸的數據,請求的性能會受到影響
3.Session: 服務器端使用的一種記錄客戶狀態的機制(session_id存在set_cookie發送到客服端,保存為cookie)
4.localStroage: h5的本地存儲,數據永久保存在客服端
1、cookie,sessionStorage,localStorage是存放在客戶端,session對象數據是存放在服務器上 實際上瀏覽器和服務器之間僅需傳遞session id即可,服務器根據session-id找到對應的用戶session對象 session存儲數據更安全一些,一般存放用戶信息,瀏覽器只適合存儲一般的數據 2、cookie數據始終在同源的http請求中攜帶,在瀏覽器和服務器來回傳遞,里面存放著session-id sessionStorage,localStorage僅在本地保存 3、大小限制區別,cookie數據不超過4kb,localStorage在谷歌瀏覽中2.6MB 4、數據有效期不同,cookie在設置的(服務器設置)有效期內有效,不管窗口和瀏覽器關閉 sessionStorage僅在當前瀏覽器窗口關閉前有效,關閉即銷毀(臨時存儲) localStorage始終有效
SessionStorage和localStorage區別: 1.sessionStorage用于本地存儲一個會話(session)中的數據,這些數據只有在用一個會話的頁面中才能被訪問(也就是說在第一次通信過程中) 并且在會話結束后數據也隨之銷毀,不是一個持久的本地存儲,會話級別的儲存 2.localStorage用于持久化的本地存儲,除非主動刪除數據,否則不會過期
復制代碼1、token就是令牌,比如你授權(登錄)一個程序時,他就是個依據,判斷你是否已經授權該軟件(最好的身份認證,安全性好,且是唯一的)
用戶身份的驗證方式
2、cookie是寫在客戶端一個txt文件,里面包括登錄信息之類的,這樣你下次在登錄某個網站,就會自動調用cookie自動登錄用戶名
服務器生成,發送到瀏覽器、瀏覽器保存,下次請求再次發送給服務器(存放著登錄信息)
3、session是一類用來客戶端和服務器之間保存狀態的解決方案,會話完成被銷毀(代表的就是服務器和客戶端的一次會話過程)
cookie中存放著sessionID,請求會發送這個id。sesion因為request對象而產生。
復制代碼 1、用戶通過用戶名和密碼發送請求
2、服務器端驗證
3、服務器端返回一個帶簽名的token,給客戶端
4、客戶端儲存token,并且每次用于發送請求
5、服務器驗證token并且返回數據
每一次請求都需要token
復制代碼 1、cookie數據存放在客戶的瀏覽器上,session數據放在服務器上。
2、cookie不是很安全,別人可以分析存放在本地的COOKIE并進行COOKIE欺騙考慮到安全應當使用session。
3、session會在一定時間內保存在服務器上。當訪問增多,會比較占用你服務器的性能考慮到減輕服務器性能方面,應當使用COOKIE。
4、單個cookie保存的數據不能超過4K,很多瀏覽器都限制一個站點最多保存20個cookie。
復制代碼 1、session認證只是把簡單的User的信息存儲Session里面,sessionID不可預測,一種認證手段。只存在服務端,不能共享到其他的網站和第三方App
2、token是oAuth Token,提供的是認證和授權,認證針對用戶,授權是針對App,目的就是讓某APP有權訪問某用戶的的信息。Token是唯一的,
token不能轉移到其他的App,也不能轉到其他用戶上。(適用于App)
3、session的狀態是存在服務器端的,客戶端只存在session id, Token狀態是存儲在客戶端的
復制代碼 1、數量和長度的限制。每個特定的域名下最多生成20個cookie(chorme和safari沒有限制)
2、安全性問題。
一、觀察者模式:juejin.cn/post/684490… juejin.cn/post/684490… 在軟件開發設計中是一個對象(subject),維護一系列依賴他的對象(observer),當任何狀態發生改變自動通知他們。強依賴關系 簡單理解:數據發生改變時,對應的處理函數就會自動執行。一個Subjet,用來維護Observers,為某些event來通知(notify)觀察者
二、發布-訂閱者 有一個信息中介,過濾 耦合性低 它定義了一種一對多的關系,可以使多個觀察者對象對一個主題對象進行監聽,當這個主題對象發生改變時,依賴的所有對象都會被通知到。
復制代碼1.冒泡排序:重復走訪過要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把它們交換過來。
實現過程:1.比較相鄰的元素。如果第一個比第二個大,就交換他們兩個
2.對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最后一對,這樣在最后的元素應該會是最大的數
3.針對所有的元素重復以上的步驟,除了最后一個
4.重復步驟1-3,直到排序完成。
2.選擇排序:首先在未排序序列中找到最小值,放在排序序列的起始位置,然后,在從剩下未排序元素中繼續尋找最小值,然后放在與排序序列的末尾
實現過程:
3.插入排序:構建有序序列,對于未排序數據,在已排序序列中沖后向前掃描,找到相應位置并插入
實現過程:1.從第一個元素開始,該元素可以認為已經被排序
2.取出下一個元素,在已排序的元素序列中沖后向前掃描
3.如果該元素(以排序)大于新元素,將元素向后移一位
4.在取出一個元素,比較之前的,直到找到自己合適的位置
4.桶排序:將數據分布到有限數量的桶里,每個桶在分別排序
1.快速排序:快速排序使用分治法把一個串(list)分為兩個子串(sub-lists).具體算法實現
實現過程:1.從數組中挑出一個元素,成為一個基準
2.重新排列數組,所有元素比基準小的擺在基準前面,所有元素比基準大的擺在基準后面(相同的可以擺在一邊)
這個分區退出之后,該基準就處于數列的中間位置。成為分區操作。
3.遞歸的把小于基準值的子數列和大于基準值元素的子數列排序
算法實現: function quickSort (arr) {
if (arr.length <=1) {return arr}
var destIndex=Math.floor(arr.length/2)
var left=[], right=[];
var dest=arr.splice(destIndex,1)[0];
for (var i=0;i<arr.length;i++){
if (arr[i]<dest) {
left.push(arr[i])
} else {
right.push(arr[i]) }
return quickSort(left).concat([dest],quickSort(right)
2.堆排序:利用對這種數據結構所涉及的一種排序算法,堆積是一個近乎完全二叉樹的結構,并同時滿足堆積的性質:即子節點的鍵值或索引總是小于(或大于)它的父節點。
實現過程:1.
復制代碼1.雙重循環
2.indexOf
3.數組排序去重 最快你Olong
復制代碼判斷回文字符串:(遞歸的思想)
1.字符串分隔,倒轉,聚合[...obj].reverse().join('')
2.字符串頭部和尾部,逐次向中間檢測
實現:function isPalindrome(line) {
line +='';
for (var i=0,j=line.length-1;i<j;i++,j--) {
if (line.chartAt(i) !==line.chartAt(j) {
return false
}
3.遞歸
復制代碼 二分查找可以解決已排序數組的查找問題,即只要數組中包含T(要查找的值),那么通過不斷的縮小包含T的數據范圍,就可以最終要找到的數
(1) 一開始,數據范圍覆蓋整個數組。
(2) 將數組的中間項與T進行比較,如果T比數組的中間項小,則到數組的前半部分繼續查找,反之,則到數組的后半部分繼續查找。
(3) 就這樣,每次查找都可以排除一半元素,相當于范圍縮小一半。這樣反復比較,反復縮小范圍,最終會在數組中找到T
代碼實現:function binarySearch (data, dest, start, end){
var end=end || data.length-1;
var start=start || 0;
var m=Math.floor((start+end)/2);
if (dest<data[m]){
return binarySearch(data, dest, 0, m-1)
} else {
return binarySearch(data, dest, m+1, end)
}}
return false
復制代碼一句話概括:1.bind()返回一個新函數,并不會立即執行。
2.bind的第一個參數將作為他運行時的this,之后的一系列參數將會在傳遞的實參前傳入作為他的參數
3.bind返回函數作為構造函數,就是可以new的,bind時指定的this值就會消失,但傳入的參數依然生效
復制代碼Function.prototype.bind=function (obj, arg) {
var arg=Array.prototype.slice.call(arguments, 1);
var context=this;
var bound=function (newArg) {
arg=arg.concat(Array.prototype.slice.call(newArg);
return context.apply(obj, arg)
}
var F=function () {} // 在new一個bind會生成新函數,必須的條件就是要繼承原函數的原型,因此用到寄生繼承來完成我們的過程
F.prototype=context.prototype;
bound.prototype=new F();
return bound;
}
復制代碼ajax的原理:相當于在用戶和服務器之間加一個中間層(ajax引擎),使用戶操作與服務器響應異步化。
優點:在不刷新整個頁面的前提下與服務器通信維護數據。不會導致頁面的重載
可以把前端服務器的任務轉嫁到客服端來處理,減輕服務器負擔,節省寬帶
劣勢:不支持back。對搜索引擎的支持比較弱;不容易調試
怎么解決呢?通過location.hash值來解決Ajax過程中導致的瀏覽器前進后退按鍵失效,
解決以前被人常遇到的重復加載的問題。主要比較前后的hash值,看其是否相等,在判斷是否觸發ajax
復制代碼function getData(url) {
var xhr=new XMLHttpRequest(); // 創建一個對象,創建一個異步調用的對象
xhr.open('get', url, true) // 設置一個http請求,設置請求的方式,url以及驗證身份
xhr.send() //發送一個http請求
xhr.onreadystatechange=function () { //設置一個http請求狀態的函數
if (xhr.readyState==4 && xhr.status==200) {
console.log(xhr.responseText) // 獲取異步調用返回的數據
}
}
}
Promise(getData(url)).resolve(data=> data)
AJAX狀態碼:0 - (未初始化)還沒有調用send()方法
1 - (載入)已調用send方法,正在發送請求
2 - (載入完成呢)send()方法執行完成
3 - (交互)正在解析相應內容
4 - (完成)響應內容解析完成,可以在客戶端調用了
```
#### 三、函數節流(throttle)
```
function throttle (func, wait) {
var timeout;
var previous=0;
return function () {
context=this;
args=arguments;
if (!timeout) {
timeout=setTimeout(()=> {
timeout=null;
func.apply(context,args)
}, wait);
}
}
}
}
```
#### 四、函數防抖(dobounce)
```
function debounce (func, wait) {
var timeout;
return function() {
var context=this;
var args=arguments;
clearTimeout(timeout);
timeout=setTimeout(()=> {
func.apply(context,args)
}, wait);
}
}
```
#### 五、實現一個函數clone,可以對JavaScript中的5種主要的數據類型(包括Number、String、Object、Array、Boolean)進行值復制
```
Object.prototype.clone=function() {
var newObject=this.constructor===Array ? [] : {} //對象的深拷貝 獲取對應的構造函數 [] 或者 {}
for (let e in this) { //遍歷對象的屬性 in this[e]
newObject[e]=typeof this[e]==='object' ? this[e].clone() : this[e] //對象中的屬性如果還是對象 那就繼續遞歸 否則就返回基本的數據類型
}
return newObject
}
```
#### 六、實現一個簡單的Promise https://juejin.cn/post/6844903625769091079
```
class Promise {
constructor (executor) { // executor里面有兩個參數,一個叫resolve(成功),一個叫reject(失敗)。
this.status='pending',
this.value=undefined;
this.reason=undefined;
// 成功存放的數組
this.onResolvedCallbacks=[];
// 失敗存放法數組
this.onRejectedCallbacks=[];
let resolve=(value)=> {
if (this.status=='pending') {
this.status='resolve';
this.value=value;
this.onResolvedCallbacks.forEach(fn=> fn())
}
}
let reject=(reason)=> {
if (this.status=='pending') {
this.status='reject';
this.reason=reason;
this.onRejectedCallbacks.forEach(fn=> fn())
}
}
try{
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then (onFullFilled,onRejected) {
if (this.status=='resolved') {
onFullFilled(this.value)
}
if (this.status=='rejectd') {
onRejected(this.reason);
}
if (this.status=='pending') {
this.onResolvedCallbacks.push(()=>{
onFullFilled(this.value);
})
this.onRejectedCallbacks.push(()=> {
onRejected(this.reason);
})
}
}
}
const p=new Promise((resolve, reject)=> {
setTimeout(()=> {
resolve('hello world')
}, 1000);
})
p.then((data)=>{
console.log(data)
},(err)=>{
console.log(err);
})
```
#### 七、發布訂閱者模式(觀察者模式)
```
var event={}; // 發布者
event.clientList=[] //發布者的緩存列表
event.listen=function (fn) { // 增加訂閱者函數
this.clientList.push(fn)
}
event.trigger=function () { // 發布信息
for (var i=0;i<this.clientList.length;i++) {
var fn=this.clientList[i];
fn.apply(this, arguments);
}
}
event.listen (function(time) {
console.log('正式上班時間為:' +time)
})
event.trigger ('2018/7')
```
#### 八、手動寫一個node服務器
```
const http=require('http');
const fs=require('fs');
const server=http.createServer((req,res)=> {
if (reu.url=='/') {
const indexFile=fs.createReadStream('./index.html')
req.writeHead(200,{'context-Type':'text/html;charset=utf8})
indexFile.pipe(res)
}
server.listen(8080)
```
如果本文對你有幫助,就留個關注點個贊支持下吧,你的「關注」和「贊」是我創作的動力。
原文鏈接:https://juejin.cn/post/6844903976693940231
日,我的一位同事向我尋求建議,她打算為自己構建一個博客。于是,我對靜態網站生成器和博客引擎進行了一番研究,發現 Hugo 是一個很不錯的選擇。但是,我的同事還有一些特殊要求,比如,她想要一個自定義的博客網址和 CSS 主題。盡管這些 Hugo 都可以實現,但我并不打算花時間來學習它。我想自己創建一個簡單的靜態網站生成器,以便我的同事在她已經準備好的 HTML 中編寫博客文章。
這個靜態網站生成器的代碼大約 100 行,非常簡潔。它提供了詳細代碼和示例博客 。眾所周知,GitLab 提供靜態頁面的免費托管服務,還帶有 CI/CD 功能,它允許你在部署之前編譯頁面。
以下教程將帶你使用 Node.js 設置自己的靜態網站生成器,Node.js 的版本需要 “>=8.11.x”。
npm init npm i --save-exact bluebird chokidar fs-extra mustache mkdir src mkdir public
首先,設置項目:
開始之前,我們需要弄清楚一個問題:為什么需要靜態網站生成器?因為某些情況并不需要靜態網站生成器。假如你的博客訪問量很小,你只需簡單地手工創建 HTML 頁面并發布它們即可。實際上,在服務器編程興起之前,在很長時間內這就是大多數 Web 的發布方式。但是,一旦頁面和內容增加,對這些頁面中的通用部分(例如頁面底部)進行更改將會變得非常重復和乏味。因此,我們開始尋找一種更加理想的方法,嘗試使用某種簡單的模板引擎來分離常見內容,然后在特定的地方插入所需的內容。
開始研究模板引擎之前,先設置我們的網站。我們需要在項目根目錄下創建 2 個文件夾 :
我們的目標是將 src 目錄的內容復制到 public 目錄中。在項目根目錄下創建 index.js 文件,其內容如下:
const Promise=require("bluebird"); const fse=require("fs-extra"); Promise.resolve().then(async ()=> { await main(); }); const main=async()=> { await generateSite(); }; const generateSite=async()=> { await copyAssets(); }; const copyAssets=async()=> { await fse.emptyDir("public"); await fse.copy("src", "public"); };
執行命令 node index.js,即可啟動該腳本。
祝賀你!此刻,你已榮升為一名后端開發人員。
接下來,我們將添加文件監視器,src 文件夾中的內容一旦發生更改就將重新生成網站。該博客總共包含 500-1000 個文件,我們可以在任何變化發生時重新生成整個網站:
const chokidar=require("chokidar"); const main=async()=> { await generateSite(); watchFiles(); }; const watchFiles=()=> { const watcher=chokidar.watch( [ "src" ], { ignored: /(^|[/\])../, // chokidar will watch folders recursively ignoreInitial: false, persistent: true } ); watcher.on("change", async path=> { console.log("changed " + path + ", recompiling"); await generateSite(); }); // catch ctrl+c event and exit normally process.on("SIGINT", function() { watcher.close(); }); };
上面的代碼清楚地說明了為什么初始版本有一個名為 generateSite 的函數。現在執行命令 node index.js 啟動我們的靜態網站生成器,如果在 src 目錄中編輯任何文件,public 都會發生變化。此時,我們還將添加一個環境變量來區分開發和生產模式。在開發模式中,我們將關注更改情況并重新生成網站,而在生產模式中,我們只需重新生成:
const env=process.env.NODE_ENV || "dev"; const main=async ()=> { console.log("Running app in " + env); await generateSite(); if (env==="dev") { watchFiles(); } };
我們可以執行命令 export NODE_ENV=prod || set NODE_ENV=prod && node index.js 來運行以上代碼。請注意,觀察源目錄的更改和重新編譯并不是每次都必須的,你可以跳過此步驟,只需在每次進行更改時運行腳本即可。
至此,差不多完成了!現在來說說模板。我們將使用 Mustache.js 模板,它非常簡單易用,并且我們的需求并不復雜。創建一個文件夾 src/partials,用來存放網站的公共部分。然后稍微修改我們的網站結構,保證所有頁面都存放在 src/pages 目錄中。接下來加載頁面并使用 Mustache 渲染:
const fs=require("fs"); const generateSite=async ()=> { await copyAssets(); await buildContent(); }; const buildContent=async ()=> { const pages=await compilePages(); await writePages(pages); }; const compilePages=async ()=> { const partials=await loadPartials(); const result={}; const pagesDir=path.join("src", "pages"); const fileNames=await fs.readdirAsync(pagesDir); for (const fileName of fileNames) { const name=path.parse(fileName).name; const fileContent=await fs.readFileAsync(path.join(pagesDir, fileName)); result[name]=Mustache.render(fileContent.toString(), {}, partials); } return result; }; const loadPartials=async ()=> { const result={}; const partialsDir=path.join("src", "partials"); const fileNames=await fs.readdirAsync(partialsDir); for (const fileName of fileNames) { const name=path.parse(fileName).name; const content=await fs.readFileAsync(path.join(partialsDir, fileName)); result[name]=content.toString(); } return result; }; const writePages=async pages=> { for (const page of Object.keys(pages)) { await fs.writeFileAsync(path.join("public", page + ".html"), pages[page]); } };
想要了解最終版本,請查看 Software Dawg 項目(https://gitlab.com/wheresvic/software-dawg)。它與本教程有一些細微差別:
此外,你還可以安裝 browser-sync 軟件包,然后通過命令 npm run live-reload 運行它,如此一來,只要有任何更改發生瀏覽器就會自動刷新。請注意,由于任何更改都將重新生成整個網站,因此并不適用于 Windows。
GitLab 提供靜態網站免費托管,只需一個 .gitlab-ci.yml 配置文件即可。真正令人難以置信之處在于,你可以自定義構建過程,這意味著在該例中,我們可以在部署之前生成網站!有關此功能的詳細信息,請參見https://about.gitlab.com/features/pages/。
本教程到此結束,我的同事對此非常滿意,該方案非常靈活,它允許她根據自己的喜好進行自定義,也希望對你有所助益!
原文:https://smalldata.tech/blog/2018/08/16/building-a-simple-static-site-generator-using-node-js
作者簡介:Victor Parmar,是一位全棧工程師,熱愛旅行,熱愛 DIY。
譯者:安翔,責編:屠敏
者 | L的存在
來源 | 我是程序員小賤(ID:Lanj1995Q)
說到后端開發,難免會遇到各種所謂高大上的「關鍵詞 」,對于我們應屆生小白,難免會覺得比較陌生,因為在學校確實比較少遇見這些所謂高大上的東西,那么今天就帶著學習的態度和大家分享這些看似可以裝逼可以飛的帶逼格的關鍵詞吧。
大綱
在學校里的項目中,一個 Web 系統可能咋們一個人就搞定,因為幾乎不考慮并發量,性能咋樣,所謂「過得去 」足矣,但是為了面試考慮,我們又不得不找點類似秒殺系統作為我們簡歷的支撐項目(即使已經爛大街)。那么先問你第一個問題,為什么就采用了分布式的方案落地這個項目?
當一個人或者幾十個使用你的系統,哎呀我去,請求秒回,效果倍棒,于是乎簡歷砰砰寫上卻多么牛X,當面試官就會問你你這項目做了啥,測試過沒,并發量如何,性能如何?你就…..
當訪問系統的用戶越來越多,可是我們的系統資源有限,所以需要更多的 CPU 和內存去處理用戶的計算請求,當然也就要求更大的網絡帶寬去處理數據的傳輸,也需要更多的磁盤空間存儲數據。
資源不夠,消耗過度,服務器崩潰,系統也就不干活了,那么在這樣的情況怎么處理?
垂直伸縮
縱向生長。通過提升單臺服務器的計算處理能力來抵抗更大的請求訪問量。比如使用更快頻率的CPU,更快的網卡,塞更多的磁盤等。
其實這樣的處理方式在電信,銀行等企業比較常見,讓摩托車變為小汽車,更強大的計算機,處理能力也就越強,但是對于運維而言也就越來越復雜。那真的就這樣花錢買設備就完事了?
當然不,單臺服務器的計算處理能力是有限的,而且也會嚴重受到計算機硬件水平的制約。
水平伸縮
一臺機器處理不過來,我就用多臺廉價的機器合并同時處理,人多力量大嘛,通過多臺服務器構成分布式集群從而提升系統的整體處理能力。這里說到了分布式,那我們看看分布式的成長過程
記住一句話:系統的技術架構是需求所驅動
最初的單體系統,只需要部分用戶訪問:
單體結構
做系統的原因當然是有需求,有價值,可賺錢。隨著使用系統的用戶越來越多,這時候關注的人越來越多,單臺服務器扛不住了,關注的人覺得響應真慢,沒啥意思,就開始吐槽,但是這一吐槽,導致用戶更多,畢竟大家都愛吃瓜。
這樣下去不得不進行系統的升級,將數據庫和應用分離。
數據庫應用分離
這樣子,咋們將數據庫和應用程序分離后,部署在不同的服務器中,從1臺服務器變為多臺服務器,處理響應更快,內容也夠干,訪問的用戶呈指數增長,這多臺服務器都有點扛不住了,怎么辦?
加一個緩存吧,我們不每次從數據庫中讀取數據,而將應用程序需要的數據暫存在緩沖中。緩存呢,又分為本地緩存和分布式的緩存。分布式緩存,顧名思義,使用多臺服務器構成集群,存儲更多的數據并提供緩存服務,從而提升緩存的能力。
加了緩存哪些好處?
應用程序不再直接訪問數據庫,提升訪問效率。因為緩存內容在內存中,不用每次連接存放磁盤中的數據庫。
系統越來越火,于是考慮將應用服務器也作為集群。
集群
干啥啥不行,緩存第一名。不吹牛,緩存應用在計算機的各個角落。緩存可說是軟件技術中的的殺手锏,無論是程序代碼使用buffer,還是網絡架構中使用緩存,虛擬機也會使用大量的緩存。
其實最初在CPU中也就開始使用緩存。緩存分為兩種,一種是通讀緩存,一種是旁路緩存
通讀緩存
假設當前應用程序獲取數據,如果數據存在于通讀緩存中就直接返回。如果不存在于通讀緩存,那么就訪問數據源,同時將數據存放于緩存中。
下次訪問就直接從緩存直接獲取。比較常見的為CDN和反向代理。
通讀緩存
CDN
CDN稱為內容分發網絡。想象我們京東購物的時候,假設我們在成都,如果買的東西在成都倉庫有就直接給我們寄送過來,可能半天就到了,用戶體驗也非常好,就不用從北京再寄過來。
同樣的道理,用戶就可以近距離獲得自己需要的數據,既提高了響應速度,又節約了網絡帶寬和服務器資源。
旁路緩存
應用程序需要自己從數據源讀取數據,然后將這個數據寫入到旁路緩存中。這樣,下次應用程序需要數據的時候,就可以通過旁路緩存直接獲得數據了。
旁路緩存
緩存的好處
因為大部分緩存的數據存儲在內存中,相比于硬盤或者從網絡中獲取效率更高,響應時間更快,性能更好;
通過 CDN 等通讀緩存可以降低服務器的負載能力;
因為緩存通常會記錄計算結果。如果緩存命中直接返回,否則需要進行大量的運算。所以使用緩存也減少了CPU 的計算小號,加快處理速度。
緩存缺點
我們緩存的數據來自源數據,如果源數據被修改了,俺么緩存數據很肯能也是被修改過的,成為臟數據,所以怎么辦?
過期失效
在每次寫入緩存數據的時候標記失效時間,讀取數據的時候檢查數據是否失效,如果失效了就重新從數據源獲取數據。
失效通知
應用程序在更新數據源的時候,通知清除緩存中的數據。
是不是數據使用緩存都有意義呢?
非也,通常放入緩存中的數據都是帶有熱點的數據,比如當日熱賣商品,或者熱門吃瓜新聞,這樣將數據存放在緩存中,會被多次讀取,從而緩存的命中率也會比較高。
在前面中,通過緩存實際上很多時候是解決了讀的問題,加快了讀取數據的能力。因為緩存通常很難保證數據的持久性和一致性,所以我們通常不會將數據直接寫入緩存中,而是寫入 RDBMAS 等數據中,那如何提升系統的寫操作性能呢?
此時假設兩個系統分別為A,B,其中A系統依賴B系統,兩者通信采用遠程調用的方式,此時如果B系統出故障,很可能引起A系統出故障。
從而不得不單獨進行升級,怎么辦?
使用消息隊列的異步架構,也成為事件驅動模型。
異步相對于同步而言,同步通常是當應用程序調用服務的時候,不得不阻塞等待服務期完成,此時CPU空閑比較浪費,直到返回服務結果后才會繼續執行。
同步
舉個例子,小藍今天想在系統中加一個發郵件的功能,通過SMTP和遠程服務器通信,但是遠程服務器有很多郵件需要等待發送呢,當前郵件就可能等待比較長時間才能發送成功,發送成功后反饋與應用程序。
這個過程中,遠程服務器發送郵件的時候,應用程序就阻塞,準確的說是執行應用程序的線程阻塞。
這樣阻塞帶來什么問題“?
不能釋放占用的系統資源,導致系統資源不足,影響系統性能
無法快速給用戶響應結果
但是在實際情況中,我們發送郵件,并不需要得到發送結果。比如用戶注冊,發送賬號激活郵件,無論郵件是否發送成功都會收到"返回郵件已經發送,請查收郵件確認激活",怎樣才能讓應用程序不阻塞?
消息隊列的異步模型
此時就比較清晰了,調用者將消息發送給消息隊列直接返回,應用程序收到返回以后繼續執行,快讀響應用戶釋放資源。
有專門的消費隊列程序從中消息隊列取出數據并進行消費。如果遠程服務出現故障,只會傳遞給消費者程序而不會影響到應用程序。
消息隊列
消息隊列模型中通常有三個角色,分別為生產者,消息隊列和消費者。生產者產生數據封裝為消息發送給消息隊列,專門的消費程序從消息隊列中取出數據,消費數據。
在我看來,消息隊列主要是緩沖消息,等待消費者消費。其中消費的方式分為兩種:
點對點
對生產者多消費者的情況。一個消息被一個消費者消費
多生產消費
上述的發郵件例子就是典型的點對點模式。互不干擾,其中某個服務出現問題不會印象到全局。
訂閱模式
開發人員在消息隊列中設置主題,生產者往相應的主題發送數據,消費者從對應的主題中消費數據,每個消費者按照自己業務邏輯分別進行計算
訂閱模式
這個比較好理解,比如在用戶注冊的時候,我們將注冊信息放入主題用戶中,消費者訂閱了這個主題,可能有構造短信消息的消費者,也有推廣產品的消費者,都可以根據自己業務邏輯進行數據處理。
用戶注冊案例
使用異步模型的優點
快速響應
不在需要等待。生產者將數據發送消息隊列后,可繼續往下執行,不虛等待耗時的消費處理
削峰填谷(需要修改)
互聯網產品會在不同的場景其并發請求量不同。互聯網應用的訪問壓力隨時都在變化,系統的訪問高峰和低谷的并發壓力可能也有非常大的差距。
如果按照壓力最大的情況部署服務器集群,那么服務器在絕大部分時間內都處于閑置狀態。
但利用消息隊列,我們可以將需要處理的消息放入消息隊列,而消費者可以控制消費速度,因此能夠降低系統訪問高峰時壓力,而在訪問低谷的時候還可以繼續消費消息隊列中未處理的消息,保持系統的資源利用率。
降低耦合
如果調用是同步,如果調用是同步的,那么意味著調用者和被調用者必然存在依賴,一方面是代碼上的依賴,應用程序需要依賴發送郵件相關的代碼,如果需要修改發送郵件的代碼,就必須修改應用程序,而且如果要增加新的功能
那么目前主要的消息隊列有哪些,其有缺點是什么?(好好記下這個高頻題目啦)
一臺機器扛不住了,需要多臺機器幫忙,既然使用多臺機器,就希望不要把壓力都給一臺機器,所以需要一種或者多種策略分散高并發的計算壓力,從而引入負載均衡,那么到底是如何分發到不同的服務器的呢?
最初實現負載均衡采取的方案很直接,直接上硬件,當然也就比較貴,互聯網的普及,和各位科學家的無私奉獻,各個企業開始部署自己的方案,從而出現負載均衡服務器。
HTTP重定向負載均衡
也屬于比較直接,當HTTP請求叨叨負載均衡服務器后,使用一套負載均衡算法計算到后端服務器的地址,然后將新的地址給用戶瀏覽器,瀏覽器收到重定向響應后發送請求到新的應用服務器從而實現負載均衡,如下圖所示:
HTTP重定向負載均衡
優點:
簡單,如果是java開發工程師,只需要servlet中幾句代碼即可
缺點:
加大請求的工作量。第一次請求給負載均衡服務器,第二次請求給應用服務器
因為要先計算到應用服務器的 IP 地址,所以 IP 地址可能暴露在公網,既然暴露在了公網還有什么安全可言
DNS負載均衡
了解計算機網絡的你應該很清楚如何獲取 IP 地址,其中比較常見的就是 DNS 解析獲取 IP 地址。
用戶通過瀏覽器發起HTTP請求的時候,DNS 通過對域名進行即系得到 IP 地址,用戶委托協議棧的 IP 地址簡歷 HTTP 連接訪問真正的服務器。這樣不同的用戶進行域名解析將會獲取不同的IP地址從而實現負載均衡。
DNS負載均衡
乍一看,和HTTP重定向的方案不是很相似嗎而且還有 DNS 解析這一步驟,也會解析出 IP 地址,不一樣的暴露?
每次都需要解析嗎,當然不,通常本機就會有緩存,在實際的工程項目中通常是怎么樣的呢?
通過 DNS 解析獲取負載均衡集群某臺服務器的地址;
負載均衡服務器再一次獲取某臺應用服務器,這樣子就不會將應用服務器的 IP 地址暴露在官網了。
反向代理負載均衡
這里典型的就是Nginx提供的反向代理和負載均衡功能。用戶的請求直接叨叨反向代理服務器,服務器先看本地是緩存過,有直接返回,沒有則發送給后臺的應用服務器處理。
反向代理負載均衡
IP負載均衡
上面一種方案是基于應用層的,IP很明顯是從網絡層進行負載均衡。TCP./IP協議棧是需要上下層結合的方式達到目標,當請求到達網絡層的黑猴。
負載均衡服務器對數據包中的IP地址進行轉換,從而發送給應用服務器。
IP負載均衡
注意,這種方案通常屬于內核級別,如果數據比較小還好,但是大部分情況是圖片等資源文件,這樣負載均衡服務器會出現響應或者請求過大所帶來的瓶頸。
數據鏈路負載均衡
它可以解決因為數據量他打而導致負載均衡服務器帶寬不足這個問題。怎么實現的呢。它不修改數據包的IP地址,而是更改mac地址。
應用服務器和負載均衡服務器使用相同的虛擬IP。
數據鏈路負載均衡
以上介紹了幾種負載均衡的方式,但是很重要的負載均衡算法卻沒有設計,其中包含了輪詢,隨機,最少連接,下面分別對此進行介紹。
公司存在的價值在于流量,流量需要數據,可想而知數據的存儲,數據的高可用可說是公司的靈魂。那么改善數據的存儲都有哪些手段或方法呢?
數據主從復制
主從復制比較好理解,需要使用兩個數據庫存儲一樣的數據。其原理為當應用程序A發送更新命令到主服務器的時候,數據庫會將這條命令同步記錄到Binlog中。
然后其它線程會從Binlog中讀取并通過遠程通訊的方式復制到另外服務器。服務器收到這更新日志后加入到自己Relay Log中,然后SQL執行線程從Relay Log中讀取次日志并在本地數據庫執行一遍,從而實現主從數據庫同樣的數據。
主從復制
主從復制可以方便進行讀寫分離,可以使用一主多從的方式保證高可用,如果從數據庫A掛了,可以將讀操作遷移到從數據庫完成高可用。
但是如果主數據庫掛了咋搞,那就Mysql的主主復制。可是不管上面說的那種方式都不是提升它的存儲能力,這就需要進行數據庫的分片了。
數據庫分片
將一張表分成若干片,其中每一片都包含一部分行記錄,然后將每一片存儲在不同的服務器中,這樣就實現一張表存放在多臺服務器中,哪都有哪些分片存儲的方案?
最開始使用"硬編碼"的方式,此方式從字面上可以理解為直接在代碼中指定。假定表為用戶表,通過ID的奇偶存放在不同的服務器上,如下圖:
這種方式的缺點很明顯,當需要增加服務器的時候,就需要改動代碼,這就不友好了。比較常見的數據庫分片算法是通過余數Hash算法,根據主鍵ID和服務器的數量取模,根據余數確定服務器。
我們使用谷歌瀏覽器的時候,輸入搜索關鍵字,就會出現搜索到多少條結果,用時多少,它是如何做到在如此短的時間完成這么大數據量的搜索。
先來想第一個問題,全世界這么多網頁在哪里?
互聯網的存在讓你和我隔著屏幕都知道你多帥。當然,每個網頁中都會存在很多其他網頁的超鏈接,這樣構成了龐大的網絡。
對于搜索引擎而言,目標為解析這些網頁獲取超鏈接,下載鏈接內容(過濾),具體一些說。
將URL存放于池子中,從池子中取出URL模擬請求,下載對應的HTML,并存放于服務器上,解析HTML內容時如果有超鏈接URL,檢測是否已經爬取過,如果沒有暫存隊列,后面再依次爬取。架構圖如下:
爬蟲常規方法
將獲取的所有網頁進行編號并得到網頁集合。然后通過分詞技術獲得每一個單詞,組成矩陣如下所示:
分詞
就這樣按照單詞-文檔的方式組織起來就叫做倒排索引了。
倒排索引
Google通過建立<單詞,地址>這樣的文檔,主要搜索到單詞就能定位到文檔的地址列表,然后根據列表中文檔的編號就展現文檔信息從而實現快速的檢索。
如果搜索出來結果很多,Google是如何能更精準的將我們需要的信息呈現給我們呢。它很明顯有個排序功能,那是如何排序的?
Google使用了一種叫做"PageRank"的算法,通過計算每個網頁的權重,并按照權重排序,權重高的自然就在顯示在前面,那問題來了,為啥權重高的,排在前面的通常就是用戶所需要的呢?這就得了解下pagerank算法了。
在pagerank中,如果網頁A包含網頁B說明A認可B,即投一票。如下圖ABCD四個網頁所示,箭頭代表超鏈接的方向,比如A->B代表A網頁包含B的超鏈接
pagerank
怎么計算的?
ABCD 初始值都為1,然后根據關系計算權重。比如此時B包含了AD兩個網頁,那么權重1被分為兩個1/2分別給A和D,此時A包含BCD,那么此時A頁面新的權重為1/2 + 1/3 + 1=11/6。
pagerank 值越受推薦,代表用戶越想看到。基于每個網頁的 pagerank 值對倒排索引中的文檔列表排序,靠前者則是用戶想看到的文檔。
這種是因為超鏈接,引入權值的方式排序。還有其他諸如對于商品售賣次數排序或者電影點贊或評價分數排序。
還有通過關鍵字查找,希望找到和搜索詞相關,這個時候可能就會采用詞頻TF進行排序,詞頻代表所查詞和文檔的相關程度。
詞頻TF
比如我們搜索"Java后端"出現的結果以"后端"的相關技術。
在大部分的應用中都會涉及到搜索引擎技術,技術龐大且復雜,希望各位老鐵根據自身情況搜索相關所需學習,校招面試中不出現盲點即可。
技術的引進一定是想解決某個痛點。我不希望在一個系統中,一小點改動就影響到全局,希望各個功能模塊拆分清晰,不管是測試還是運維都能節省更多的時間。
那么單體的架構出現了哪些問題?
代碼分支管理困難
各個部門分別完成各自的任務,但是最后需要 merge 在一起成為整個系統,merge過程經歷的人都知道,問題是真XX多。所以不再是996
新增功能麻煩
隨著項目的效益越來越好,用戶的需求也更多,招聘的人可能更多,對于新手來說上來是一臉懵逼的,老員工忙的要死,新員工成為了摸魚專家
耗盡連接
用戶的增多,每個應用都得和數據庫連接,給數據庫的連接造成太大的壓力甚至耗盡連接
微服務
“微"-------微微一笑很傾城,微笑,微小。顧名思義,講一個大的系統,拆分為一個個小的服務,分別對各個小服務進行管理。這樣說感覺太不專業了,專業點
大應用拆分為小模塊
小模塊不屬于集群中
通過遠程調用的方式依賴各個獨立的模塊完成業務的處理
這些小的模塊就叫做------微服務,整體也就是所謂的微服務架構。
既然拆分成了小服務,這么多小服務怎么協調成為一個問題,甚至都不知道怎么掉這個服務,所以在微服務的整體架構中出現了注冊中心,誰需要調用使用提供的接口即可。如下圖所示:
注冊中心
從上圖我們能知道主要是三個概念:
服務提供者
微服務的具體提供者,其他微服務通過接口調用即可
服務消費者
對應于服務提供者,按照提供者接口編程即可。這么輕松的嘛,當然很多細節。舉個例子,注明的dubbo服務框架,服務接口通過Dubbo框架代理機制訪問Dubbo客戶端,客戶端通過服務接口聲明去注冊中心查看有哪些服務器,并將這服務器列表給客戶端。
客戶端然后根據負載均衡策略選擇其中一個服務器,通過遠程調用的方式發送服務調用。那么使用微服務需要注重哪幾點?
選擇中的注意事項
不要拿工具硬上需求,結合業務也許會更佳!
高可用,意味著一臺機器掛了沒事,其他機器可以照常工作,用戶體驗一樣倍棒,用戶壓根就不知道,臥槽,你居然升級了系統,我居然一點感受都沒有。那么高可用總有個標準吧,是百分之80就行還是90?
一個系統突然不能訪問的原因很多:
硬件故障
數據庫宕機
磁盤孫歡
bug
光纜斷了
可用性指標
通過多少個9來衡量,比如大寶系統可用性為4個9,意味著是99.99%,說明它的服務保證運行時間只有0.01不可用。
高可用涉及到技術成本和設備成本,不是說高可用值越高越好,而是根據具體工具具體場景而定,這里分享一些高可用策略。
冗余備份
任何一個服務都有備份,就反復我們都會對我們筆記本電腦相關文件進行備份一樣,以防萬一。即使一臺服務器掛了,可以很快的切換以致于讓用戶不會覺得"這系統怎么這么渣"。
負載均衡
使用多臺服務器分擔一臺服務器的壓力,滿足高并發的請求。怎么實現的呢,應用程序會有一個心跳檢測/健康檢查機制,如果發現不妥切換即可。上面提到的數據庫主主復制也是高可用的方案之一,技術思想果然是相通的
負載均衡
限流降級
我們的目標不是沒有蛀牙,而是希望整個系統不要掛掉。限流是對部分請求進行丟棄處理,保證大部分的用戶可以正常的請求完成任務。
降級:
可以屏蔽部分當前看來不是很有用的任務。比如電商系統做秒殺活動的過程中,確認收貨功能給予的壓力挺大,暫時看來并不是核心任務,而且系統到期也會自動確認收貨,所以暫時關閉,將系統的資源留給準備下單,放購物車的太太們
異地多活:
有時候我在想要是地震,火災等自然災害發生的時候,很多系統的數據怎么辦啊。想多了撒,大些的系統多會在各個地方部署數據中心,采用異地多活的多機房策略。用戶可以訪問任何數據中心,那問題來了,用戶的請求是如何到達不同的機房去的?
域名解析
公司中的數據財產,其重要程度不言而喻。系統的健壯性和安全性是保證系統持久運行的基礎。不要因為數據泄露才去關注安全問題。
也許說到安全問題,首先想到的是“用戶名密碼”泄漏,數據庫被脫褲導致數據泄露,hack直接拿到用戶的敏感信息,所以我們通常有哪些手段或方法來盡全力的抵抗hack嘞,辦法總比問題多嘛。
數據加解密
通過對用戶密碼,身份證等敏感數據加密是常用方法。加密方法通常分為:單向散列加密,對稱加密和非對稱加密。
所謂單向散列加密,主要體現在單向二字,意味著對明文加密后是不可以解密,你即使是給明文加密的加密者,也無法通過密文知道其明文是什么,即加密單向,不支持解密。說的這么絕對?這么無敵?
那只是理論上而已,常用的MD5算法就是大象散列加密,我們完全可以通過彩虹表等方式進行破解。怎么破解?
說個簡單的道理,我們設置密碼的時候,通常會使用生日?手機號?什么love?什么520?,這些組合是有限的,我們就可以建立一個比如生日和密文的映射表,然后通過XX彩虹表就可得到密碼明文。
單向散列加密
所以,通常的情況,使用單向散列加密的時候需要加一點鹽,這樣一來,hack拿到密文,不知道鹽,無法建立彩虹表,就更難還原明文。
應用場景:通常應用在用戶密碼加密。其加密和校驗過程如下:
應用場景
我們通過上圖來回顧一下一個網站的注冊登錄模塊中的用戶部分,用戶注冊需要輸入用戶名和密碼,我們一般不會將裸露的將密碼直接存放在數據庫,不然被脫褲直接算裸奔。
所以,用戶輸入密碼,應用服務器獲得密碼后,調用單向散列加密算法,將加密的密文存放于數據庫,用戶下一次登錄的時候,用戶依然會輸入密碼。
只是到了Web服務器后,Web服務器會對輸入的密碼再進行一次單向散列加密,然后和數據庫中取出來的密文進行對比,如果相同,則用戶的驗證成功,通常這樣的方式可以保證用戶密碼的安全性,當然如果加一點鹽,會增加破解的難度。
對稱加密
對稱加密是通過一個加密算法和密鑰,對一段明文進行加密后得到密文,然后使用相同的密鑰和對應的解密算法進行解密得到明文。
對稱加密
舉個例子,我們不會將銀行卡卡號,有效期等直接存儲在數據庫,而是會通過先加密,然后存儲于數據庫。
使用的時候必須對密文進行解密還原出明文。這個時候使用對稱加密算法,存儲的時候加密算法進行加密,使用的時候解密算法解密。
非對稱加密
非對稱加密是說使用一個加密算法和一個加密秘鑰進行加密得到密文,但是在解密出明文的時候,其加解密密鑰和加密密鑰不同,通常加密密鑰叫做公鑰,解密密鑰叫做私鑰。
非對稱加密
其實我們常用的 HTTPS 即是非對稱加密的應用場景。用戶在客戶端進行通訊的時候,對數據使用的加密密鑰和加密算法進行加密得到密文,到了服務端以后,使用解密密鑰和算法進行解密得到明文。
但是非對稱消耗的資源比較多,所以HTTPS不是每次請求響應都采用非對稱加密,而是先利用非對稱加密,在客戶單和服務器之間交換一個對稱加密的密鑰,然后每次的請求響應再使用對稱加密。
綜上,使用費對稱加密保證對稱加密迷藥的安全,使用對稱加密密鑰保證請求響應數據的安全。
HTTP攻擊與防護
HTTP明文協議,咋們通過嗅探工具就可以清晰查看會話內容,這也是hack攻擊門檻最低的方式。很常見的也就是SQL注入和XSS攻擊。
SQL注入是攻擊者在提交請求參數的時候,包含了惡意的SQL腳本:
SQL注入
server處理計算后向數據庫提交的SQL如下:
Select id from users where username='Mike';
如果是惡意攻擊,提交的HTTP請求如下:
http://www.a.com?username=Mike';drop table users;--
此時最終生成的SQL是:
Select id from users where username='Mike';drop table users;--';
查詢完就直接給刪除表了。怎么防護?
比較常用的解決方案是使用PrepareStaement預編譯,先將SQL交給數據庫生成執行計劃,后面hack不管提交什么字符串也只能交給這個執行計劃執行,不會生成新的SQL,也就不會被攻擊啦。
XSS攻擊
跨站點腳本攻擊,攻擊者通過構造惡意的瀏覽器腳本文件,使其在其他用戶的瀏覽器運行進而進行攻擊。
假設小A將含有惡意腳本的請求給360服務器,服務器將惡意的腳本存儲在本地的數據庫,當其他正常用戶通過這個服務器瀏覽信息的時候,服務器就會讀取數據庫中含有惡意腳本的數據并呈現給用戶,在用戶正常使用瀏覽器的時候達到攻擊的目的。
防御:
比較常見的是在入口處對危險的請求比如drop table等進行攔截,設置一個Web應用防火墻將危險隔離。
安全防御
其實在上面提到的分布式中就有涉及大數據相關知識。無外乎是數據量越來越大,我們如何盡可能使用較低的成本存儲更多的數據,給公司企業帶來更好的利潤。上面說過分布式緩存,負載均衡等技術,其共同特點是如何抵抗高并發的壓力,而這里的大數據技術主要談論的是如何滿足大規模的計算。
通過對數據的分析,進而發掘海量數據中的價值,這里的數據包含數據庫數據,日志信息,用戶行為數據等等。那么這么多不同類型的數據,怎么去存儲呢?
分布式文件存儲 HDFS 架構
如何將數以萬計的服務器組成統一的文件存儲系統?其中使用Namenode服務器作為控制塊,負責元數據的管理(記錄文件名,訪問權限,數據存儲地址等),而真正的文件存儲在DataNode中。
Mapreduce
大量的數據存儲下來的目的是通過相應的算法進行數據分析,獲得通過深度學習/機器學習進行預測,從而獲取有效的價值,這么大的文件,我們不可能將HDFS當做普通的文件,從文件中讀取數據然后計算,這樣子不知道算到何時何地。
大數據處理經典的處理框架即MapReduce,分為Map和Reduce兩個階段,其中一個Map過程是將每個服務器上啟動Map進程,計算后輸出一個
下面以WordCount統計所有數據中相同的詞頻數據為例,詳細看看Map和Reduce的過程。
wordcoun計算過程
在這個例子中,通過對value中的1組成的列表,reduce對這些1進行求和操作從而得到每個單詞的詞頻。代碼實現如下:
public class WordCount {
// Mapper四個參數:第一個Object表示輸入key的類型;第二個Text表示輸入value的類型;第三個Text表示表示輸出鍵的類型;第四個IntWritable表示輸出值的類型。map這里的輸出是指輸出到reduce
public static class doMapper extends Mapper<Object, Text, Text, IntWritable> {
public static final IntWritable one=new IntWritable(1);//這里的IntWritable相當于Int類型
public static Text word=new Text;//Text相當于String類型
// map參數<keyIn key,valueIn value,Context context>,將處理后的數據寫入context并傳給reduce
protected void map(Object key, Text value, Context context) throws IOException, InterruptedException {
//StringTokenizer是Java工具包中的一個類,用于將字符串進行拆分
StringTokenizer tokenizer=new StringTokenizer(value.toString, " ");
//返回當前位置到下一個分隔符之間的字符串
word.set(tokenizer.nextToken);
//將word存到容器中,記一個數
context.write(word, one);
}
}
//參數同Map一樣,依次表示是輸入鍵類型,輸入值類型,輸出鍵類型,輸出值類型。這里的輸入是來源于map,所以類型要與map的輸出類型對應 。
public static class doReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
private IntWritable result=new IntWritable;
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context)
throws IOException, InterruptedException {
int sum=0;
//for循環遍歷,將得到的values值累加
for (IntWritable value : values) {
sum +=value.get;
}
result.set(sum);
context.write(key, result);//將結果保存到context中,最終輸出形式為"key" + "result"
}
}
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
System.out.println("start");
Job job=Job.getInstance;
job.setJobName("wordCount");
Path in=new Path("hdfs://***:9000/user/hadoop/input/buyer_favorite1.txt");//設置這個作業輸入數據的路徑(***部分為自己liunx系統的localhost或者ip地址)
Path out=new Path("hdfs://***:9000/user/hadoop/output/wordCount"); //設置這個作業輸出結果的路徑
FileInputFormat.addInputPath(job, in);
FileOutputFormat.setOutputPath(job, out);
job.setJarByClass(WordCount.class);// 設置運行/處理該作業的類
job.setMapperClass(doMapper.class);//設置實現了Map步的類
job.setReducerClass(doReducer.class);//設置實現了Reduce步的類
job.setOutputKeyClass(Text.class);//設置輸出結果key的類型
job.setOutputValueClass(IntWritable.class);//設置輸出結果value的類型
////執行作業
System.exit(job.waitForCompletion(true) ? 0 : 1);
System.out.println("end");
}
}
那么這個map和reduce進程是怎么在分布式的集群中啟動的呢?
map/reduce啟動過程
上圖比較清晰地闡述的整個過程,再描述一波。MR中主要是兩種進程角色,分別為 JobTracke r和 TaskTracker 兩種。
JobTracker在集群中只有一個,而 TaskTracker 存在多個,當 JobClient 啟動后,往 JobTracker 提交作業,JobTracker查看文件路徑決定在哪些服務器啟動 Map 進程。
然后發送命令給 TaskTracker,告訴它要準備執行任務了,TaskTracker收到任務后就會啟動 TaskRunner 下載任務對應的程序。
map計算完成,TaskTracker對map輸出結果 shuffer 操作然后加載 reduce 函數進行后續計算,這就是各個模塊協同工作的簡單過程。
上述過程還是比較麻煩,我們能不能直接寫SQL,然后引擎幫助我們生成mapreduce代碼,就反復我們在web開發的時候,不直接寫SQL語句,直接交給引擎那么方便,有的,它就是HIVE。
舉個例子:
SQL
拆分
那么使用MR的計算過程完成這條SQL的處理:
MR TO SQL
Spark是基于內存計算的大數據并行計算框架。基于此說說上面hadoop中組件的缺點:
磁盤IO開銷大。每次執行都需要從磁盤讀取并且計算完成后還需要將將中間結果存放于磁盤
表達能力有限。大多數計算都需要轉換為Map和Reduce兩個操作,難以描述復雜的數據處理
spark優點:
編程模型不限于map和reduce,具有更加靈活的編程模型
spark提供內存計算,帶來更高的迭代運算效率且封裝了良好的機器學習算法
采用了基于圖DAG的任務調度機制
Flink
Flink是大數據處理的新規,發展速度之快,這兩年也相繼出現中文資料。作為流式數據流執行引擎,針對數據流的分布式計算提供數據分布,數據通信以及容錯機制等功能。同時Flink也提供了機器學習庫,圖計算庫等。
附一張去年參加會議回答問題中獎的馬克杯,嘻嘻。
去年參會
關于大數據相關知識點可作為擴充點,在面試的過程中經常會有大數問題,除了從算法的角度來闡述,也可以從這些框架中吸取一些經驗。
對于之前從事c/c++開發的我,很多時候是Linux的開發。在學校又沒怎么接觸系統性的項目,更不知道后端技術的博大進深,可能文中涉及的也就一部分,不過希望還在學校的小伙伴可以知道有這些東西,然后通過強大的搜索引擎,給自己個比較明確的方向,也許會少走點彎路,這周的文章就到這了,goodbye!
點分享
*請認真填寫需求信息,我們會在24小時內與您取得聯系。