1.1需求描述
采用vue.js開發(fā)搜索界面則SEO不友好,需要解決SEO的問題。
1.2了解SEO
總結(jié):seo是網(wǎng)站為了提高自已的網(wǎng)站排名,獲得更多的流量,對(duì)網(wǎng)站的結(jié)構(gòu)及內(nèi)容進(jìn)行調(diào)整優(yōu)化,以便搜索引擎
(百度,google等)更好抓取到更優(yōu)質(zhì)的網(wǎng)站的內(nèi)容。
下圖是搜索引擎爬取網(wǎng)站頁面的大概流程圖:
(搜索引擎的工作流程很復(fù)雜,下圖只是簡單概括)
從上圖可以看到SEO是網(wǎng)站自己為了方便spider抓取網(wǎng)頁而作出的網(wǎng)頁內(nèi)容優(yōu)化,常見的SEO方法比如:
1)對(duì)url鏈接的規(guī)范化,多用restful風(fēng)格的url,多用靜態(tài)資源url;
2) 注意title、keywords的設(shè)置。
3)由于spider對(duì)javascript支持不好,對(duì)于網(wǎng)頁跳轉(zhuǎn)用href標(biāo)簽。
。。。
1.3服務(wù)端渲染和客戶端渲染
采用什么技術(shù)有利于SEO?要解答這個(gè)問題需要理解服務(wù)端渲染和客戶端渲染。什么是服務(wù)端渲染?
我們用傳統(tǒng)的servlet開發(fā)來舉例:瀏覽器請(qǐng)求servlet,servlet在服務(wù)端生成html響應(yīng)給瀏覽器,瀏覽器展示html 的內(nèi)容,這個(gè)過程就是服務(wù)端渲染,如下圖:
服務(wù)端渲染的特點(diǎn):
1)在服務(wù)端生成html網(wǎng)頁的dom元素。
2)客戶端(瀏覽器)只負(fù)責(zé)顯示dom元素內(nèi)容。
當(dāng)初隨著web2.0的到來,A JAX技術(shù)興起,出現(xiàn)了客戶端渲染:客戶端(瀏覽器) 使用A JAX向服務(wù)端發(fā)起http請(qǐng)求,獲取到了想要的數(shù)據(jù),客戶端拿著數(shù)據(jù)開始渲染html網(wǎng)頁,生成Dom元素,并最終將網(wǎng)頁內(nèi)容展示給用戶, 如下圖:
S
客戶端渲染的特點(diǎn):
1)在服務(wù)端只是給客戶端響應(yīng)的了數(shù)據(jù),而不是html網(wǎng)頁
2)客戶端(瀏覽器)負(fù)責(zé)獲取服務(wù)端的數(shù)據(jù)生成Dom元素。
兩種方式各有什么優(yōu)缺點(diǎn)? 客戶端渲染:
1)缺點(diǎn)
不利于網(wǎng)站進(jìn)行SEO,因?yàn)榫W(wǎng)站大量使用javascript技術(shù),不利于spider抓取網(wǎng)頁。
2)優(yōu)點(diǎn)
客戶端負(fù)責(zé)渲染,用戶體驗(yàn)性好,服務(wù)端只提供數(shù)據(jù)不用關(guān)心用戶界面的內(nèi)容,有利于提高服務(wù)端的開發(fā)效率。
3)適用場景
對(duì)SEO沒有要求的系統(tǒng),比如后臺(tái)管理類的系統(tǒng),如電商后臺(tái)管理,用戶管理等。
服務(wù)端渲染:
1)優(yōu)點(diǎn)
有利于SEO,網(wǎng)站通過href的url將spider直接引到服務(wù)端,服務(wù)端提供優(yōu)質(zhì)的網(wǎng)頁內(nèi)容給spider。
2)缺點(diǎn)
服務(wù)端完成一部分客戶端的工作,通常完成一個(gè)需求需要修改客戶端和服務(wù)端的代碼,開發(fā)效率低,不利于系統(tǒng)的 穩(wěn)定性。
3)適用場景
對(duì)SEO有要求的系統(tǒng),比如:門戶首頁、商品詳情頁面等。
2.1Nuxt.js介紹
移動(dòng)互聯(lián)網(wǎng)的興起促進(jìn)了web前后端分離開發(fā)模式的發(fā)展,服務(wù)端只專注業(yè)務(wù),前端只專注用戶體驗(yàn),前端大量運(yùn) 用的前端渲染技術(shù),比如流行的vue.js、react框架都實(shí)現(xiàn)了功能強(qiáng)大的前端渲染。
但是,對(duì)于有SEO需求的網(wǎng)頁如果使用前端渲染技術(shù)去開發(fā)就不利于SEO了,有沒有一種即使用vue.js、react的前 端技術(shù)也實(shí)現(xiàn)服務(wù)端渲染的技術(shù)呢?其實(shí),對(duì)于服務(wù)端渲染的需求,vue.js、react這樣流行的前端框架提供了服務(wù)端渲染的解決方案。
從上圖可以看到:
react框架提供next.js實(shí)現(xiàn)服務(wù)端渲染。
vue.js框架提供Nuxt.js實(shí)現(xiàn)服務(wù)端渲染。
2.2Nuxt.js工作原理
下圖展示了從客戶端請(qǐng)求到Nuxt.js進(jìn)行服務(wù)端渲染的整體的工作流程:
1、用戶打開瀏覽器,輸入網(wǎng)址請(qǐng)求到Node.js
2、部署在Node.js的應(yīng)用Nuxt.js接收瀏覽器請(qǐng)求,并請(qǐng)求服務(wù)端獲取數(shù)據(jù)
3、Nuxt.js獲取到數(shù)據(jù)后進(jìn)行服務(wù)端渲染 4、Nuxt.js將html網(wǎng)頁響應(yīng)給瀏覽器
Nuxt.js使用了哪些技術(shù)?
Nuxt.js使用Vue.js+webpack+Babel三大技術(shù)框架/組件,如下圖:
Babel 是一個(gè)js的轉(zhuǎn)碼器,負(fù)責(zé)將ES6的代碼轉(zhuǎn)成瀏覽器識(shí)別的ES5代碼。
Webpack是一個(gè)前端工程打包工具。Vue.js是一個(gè)優(yōu)秀的前端框架。
Nuxt.js的特性有哪些?
基 于 Vue.js
自動(dòng)代碼分層服務(wù)端渲染
強(qiáng)大的路由功能,支持異步數(shù)據(jù)靜態(tài)文件服務(wù)
ES6/ES7 語法支持
打包和壓縮 JS 和 CSS HTML頭部標(biāo)簽管理本地開發(fā)支持熱加載集成ESLint
支持各種樣式預(yù)處理器: SASS、LESS、 Stylus等等
3.1創(chuàng)建Nuxt工程
nuxt.js有標(biāo)準(zhǔn)的目錄結(jié)構(gòu),官方提供了模板工程,可以模板工程快速創(chuàng)建nuxt項(xiàng)目。
模板工程地址:https://github.com/nuxt-community/starter-template/archive/master.zip
本項(xiàng)目提供基于Nuxt.js的封裝工程,基于此封裝工程開發(fā)搜索前端,見“資料”–》xc-ui-pc-portal.zip,解壓
xc-ui-pc-portal.zip到本項(xiàng)目前端工程目錄下。
本前端工程屬于門戶的一部分,將承載一部分考慮SEO的非靜態(tài)化頁面。
本工程基于Nuxt.js模板工程構(gòu)建,Nuxt.js使用1.3版本,并加入了今后開發(fā)中所使用的依賴包,直接解壓本工程即 可使用。
3.2目錄結(jié)構(gòu)
本工程的目錄結(jié)構(gòu)如下:
‐資源目錄 資源目錄 assets 用于組織未編譯的靜態(tài)資源如 LESS、SASS 或 JavaScript。 ‐組件目錄 組件目錄 components 用于組織應(yīng)用的 Vue.js 組件。Nuxt.js 不會(huì)擴(kuò)展增強(qiáng)該目錄下 Vue.js 組件,即這些組件不會(huì)像頁面組件那樣有 asyncData 方法的特性。 ‐布局目錄 布局目錄 layouts 用于組織應(yīng)用的布局組件。該目錄名為Nuxt.js保留的,不可更改。 ‐中間件目錄 middleware 目錄用于存放應(yīng)用的中間件。 ‐頁面目錄 頁面目錄 pages 用于組織應(yīng)用的路由及視圖。Nuxt.js 框架讀取該目錄下所有的 .vue 文件并自動(dòng)生成對(duì)應(yīng)的路由配置。 該目錄名為Nuxt.js保留的,不可更改。 ‐插件目錄 插件目錄 plugins 用于組織那些需要在 根vue.js應(yīng)用 實(shí)例化之前需要運(yùn)行的 Javascript 插件。 ‐靜態(tài)文件目錄 靜態(tài)文件目錄 static 用于存放應(yīng)用的靜態(tài)文件,此類文件不會(huì)被 Nuxt.js 調(diào)用 Webpack 進(jìn)行構(gòu)建編譯處理。 服務(wù)器啟動(dòng)的時(shí)候,該目錄下的文件會(huì)映射至應(yīng)用的根路徑 / 下。 舉個(gè)例子: /static/logo.png 映射至 /logo.png 該目錄名為Nuxt.js保留的,不可更改。 ‐Store 目 錄 store 目錄用于組織應(yīng)用的 Vuex 狀態(tài)樹 文件。 Nuxt.js 框架集成了 Vuex 狀態(tài)樹 的相關(guān)功能配置,在 store 目錄下創(chuàng)建一個(gè) index.js 文件可激活這些配置。 該目錄名為Nuxt.js保留的,不可更改。 ‐nuxt.config.js 文 件 nuxt.config.js 文件用于組織Nuxt.js 應(yīng)用的個(gè)性化配置,以便覆蓋默認(rèn)配置。該文件名為Nuxt.js保留的,不可更改。 ‐package.json 文 件 package.json 文件用于描述應(yīng)用的依賴關(guān)系和對(duì)外暴露的腳本接口。 該文件名為Nuxt.js保留的,不可更改。
nuxt.js 提供了目錄的別名,方便在程序中引用:
3.3頁面布局
頁面布局就是頁面內(nèi)容的整體結(jié)構(gòu),通過在layouts目錄下添加布局文件來實(shí)現(xiàn)。在layouts 根目錄下的所有文件都屬于個(gè)性化布局文件,可以在頁面組件中利用 layout 屬性來引用。
一個(gè)例子:
1、定義:layouts/test.vue布局文件,如下:
注意:布局文件中一定要加 <nuxt/> 組件用于顯示頁面內(nèi)容。
<template>
<div>
<div>這里是頭</div>
<nuxt/>
<div>這里是尾</div>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
2、在pages目錄創(chuàng)建user目錄,并創(chuàng)建index.vue頁面
在 pages/user/index.vue 頁面里, 可以指定頁面組件使用 test 布局,代碼如下:
<template>
<div>
測試頁面
</div>
</template>
<script>
export default{ layout:'test'
}
</script>
<style>
</style>
3、測試,請(qǐng)求:http://localhost:10000/user,如果如下:
這里是頭測試頁面這里是尾
3.4路由
3.4.1基礎(chǔ)路由
Nuxt.js 依據(jù)目錄結(jié)構(gòu)自動(dòng)生成 vue-router 模塊的路由配置。
Nuxt.js根據(jù)pages的目錄結(jié)構(gòu)及頁面名稱定義規(guī)范來生成路由,下邊是一個(gè)基礎(chǔ)路由的例子:
假設(shè)的目錄結(jié)構(gòu)如下:
pages/ ‐‐| user/ ‐‐‐‐‐| index.vue ‐‐‐‐‐| one.vue
那么,Nuxt.js 自動(dòng)生成的路由配置如下:
router: { routes: [ { name: 'user', path: '/user', component: 'pages/user/index.vue' }, { name: 'user‐one', path: '/user/one', component: 'pages/user/one.vue' } ] }
index.vue代碼如下:
<template> <div> 用戶管理首頁 </div> </template> <script> export default{ layout:"test" } </script> <style> </style>
one.vue代碼如下:
<template> <div> one頁面 </div> </template> <script> export default{ layout:"test" } </script> <style> </style>
分別訪問如下鏈接進(jìn)行測試:
http://localhost:10000/user http://localhost:10000/user/one
3.4.2嵌套路由
你可以通過 vue-router 的子路由創(chuàng)建 Nuxt.js 應(yīng)用的嵌套路由。
創(chuàng)建內(nèi)嵌子路由,你需要添加一個(gè) Vue 文件,同時(shí)添加一個(gè)與該文件同名的目錄用來存放子視圖組件。
別忘了在父級(jí) Vue 文件內(nèi)增加假設(shè)文件結(jié)構(gòu)如:
pages/ ‐‐| user/ ‐‐‐‐‐| _id.vue ‐‐‐‐‐| index.vue ‐‐| user.vue
Nuxt.js 自動(dòng)生成的路由配置如下:
router: { routes: [ { path: '/user', component: 'pages/user.vue', children: [ { path: '', component: 'pages/user/index.vue', name: 'user' }, { path: ':id', component: 'pages/user/_id.vue', name: 'user‐id' } ] } ] }
將user.vue文件創(chuàng)建到與user目錄的父目錄下,即和user目錄保持平級(jí)。
<template> <div> 用戶管理導(dǎo)航,<nuxt‐link :to="'/user/101'">修改</nuxt‐link> <nuxt‐child/> </div> </template> <script> export default{ layout:"test" } </script> <style> </style>
_id.vue頁面實(shí)現(xiàn)了向頁面?zhèn)魅雐d參數(shù),頁面內(nèi)容如下:
<template> <div> 修改用戶信息{{id}} </div> </template> <script> export default{ layout:"test", data(){ return { id:'' } }, mounted(){ this.id=this.$route.params.id; console.log(this.id) } } </script> <style> </style>
測試:http://localhost:10000/user
點(diǎn)擊修改:
3.6獲取數(shù)據(jù)
3.6.1asyncData 方法
Nuxt.js 擴(kuò)展了 Vue.js,增加了一個(gè)叫 asyncData 的方法,
方法會(huì)在組件(限于頁面組件)每次加載之前被調(diào)用。它可以在服務(wù)端或路由更新之前被調(diào)用。 在這個(gè)方法被調(diào)用的時(shí)候,第一個(gè)參數(shù)被設(shè)定為當(dāng)前頁面的上下文對(duì)象,你可以利用返回的數(shù)據(jù)一并返回給當(dāng)前組件。方法來獲取數(shù)據(jù),Nuxt.js 會(huì)將返回的數(shù)據(jù)融合組件方法
注意:由于對(duì)象。
例子:
方法是在組件 初始化 前被調(diào)用的,所以在方法內(nèi)是沒有辦法通過來引用組件的實(shí)例
在上邊例子中的user/_id.vue中添加,頁面代碼如下:
<template> <div> 修改用戶信息{{id}},名稱:{{name}} </div> </template> <script> export default{ layout:'test', //根據(jù)id查詢用戶信息 asyncData(){ console.log("async方法") return { name:'黑馬程序員' } }, data(){ return { id:'' } }, mounted(){ this.id=this.$route.params.id; } } </script> <style> </style>
此方法在服務(wù)端被執(zhí)行,觀察服務(wù)端控制臺(tái)打印輸出“async方法”。
此方法返回data模型數(shù)據(jù),在服務(wù)端被渲染,最后響應(yīng)給前端,刷新此頁面查看頁面源代碼可以看到name模型數(shù)據(jù)已在頁面源代碼中顯示。
3.6.2async /await方法
使用async 和 await配合promise也可以實(shí)現(xiàn)同步調(diào)用,nuxt.js中使用async/await實(shí)現(xiàn)同步調(diào)用效果。1、先測試異步調(diào)用,增加a、b兩個(gè)方法,并在mounted中調(diào)用。
methods:{ a(){ return new Promise(function(resolve,reject){ setTimeout(function () { resolve(1) },2000) }) }, b(){ return new Promise(function(resolve,reject){ setTimeout(function () { resolve(2) },1000) }) } }, mounted(){ this.a().then(res=>{ alert(res) console.log(res) }) this.b().then(res=>{ alert(res) console.log(res) }) }
2、使用async/await完成同步調(diào)用
async asyncData({ store, route }) { console.log("async方法") var a=await new Promise(function (resolve, reject) { setTimeout(function () { console.log("1") resolve(1) },2000) }); var a=await new Promise(function (resolve, reject) { setTimeout(function () { console.log("2") resolve(2) },1000) }); return { name:'黑馬程序員' } },
觀察服務(wù)端控制臺(tái)發(fā)現(xiàn)是按照a、b方法的調(diào)用順序輸出1、2,實(shí)現(xiàn)了使用async/await完成同步調(diào)用。
3.1搜索頁面
3.1.1需求分析
觀察服務(wù)端控制臺(tái)發(fā)現(xiàn)是按照a、b方法的調(diào)用順序輸出1、2,實(shí)現(xiàn)了使用async/await完成同步調(diào)用。
3.1搜索頁面
3.1.1需求分析
<template> <div> <Header /> <nuxt/> <Footer /> </div> </template> <script> import Footer from '../components/Footer.vue' import Header from '../components/Header.vue' export default { components: { Header, Footer } } </script> <style> </style>
3.1.3Nginx代理配置
搜索頁面中以/static開頭的靜態(tài)資源通過nginx解析,如下:
/static/plugins:指向門戶目錄下的plugins目錄。
/static/css:指向門戶目錄下的的css目錄
修改Nginx中www.xuecheng.com虛擬主機(jī)的配置:
#靜態(tài)資源,包括系統(tǒng)所需要的圖片,js、css等靜態(tài)資源location /static/img/ { alias F:/develop/xc_portal_static/img/; } location /static/css/ { alias F:/develop/xc_portal_static/css/; } location /static/js/ { alias F:/develop/xc_portal_static/js/; } location /static/plugins/ { alias F:/develop/xc_portal_static/plugins/; add_header Access‐Control‐Allow‐Origin http://ucenter.xuecheng.com; add_header Access‐Control‐Allow‐Credentials true; add_header Access‐Control‐Allow‐Methods GET; }
配置搜索Url,下圖是Nginx搜索轉(zhuǎn)發(fā)流程圖:
用戶請(qǐng)求/course/search時(shí)Nginx將請(qǐng)求轉(zhuǎn)發(fā)到nuxt.js服務(wù),nginx在轉(zhuǎn)發(fā)時(shí)根據(jù)每臺(tái)nuxt服務(wù)的負(fù)載情況進(jìn)行轉(zhuǎn) 發(fā),實(shí)現(xiàn)負(fù)載均衡。
本教程開發(fā)環(huán)境Nuxt.js服務(wù)和www.xuecheng.com虛擬機(jī)主在同一臺(tái)計(jì)算機(jī),使用同一個(gè)nginx,配置如下:
#前端門戶課程搜索 location ^~ /course/search { proxy_pass http://dynamic_portal_server_pool; } #后端搜索服務(wù) location /openapi/search/ { proxy_pass http://search_server_pool/search/; } #分類信息 location /static/category/ { proxy_pass http://static_server_pool; } #前端動(dòng)態(tài)門戶 upstream dynamic_portal_server_pool{ server 127.0.0.1:10000 weight=10; } #后臺(tái)搜索(公開api) upstream search_server_pool{ server 127.0.0.1:40100 weight=10; }
其它配置:
#開發(fā)環(huán)境webpack定時(shí)加載此文件location ^~ / webpack_hmr { proxy_pass http://dynamic_portal_server_pool/ webpack_hmr; } #開發(fā)環(huán)境nuxt訪問_nuxt location ^~ /_nuxt/ { proxy_pass http://dynamic_portal_server_pool/_nuxt/; }
在靜態(tài)虛擬主機(jī)中添加:
#學(xué)成網(wǎng)靜態(tài)資源server { listen 91; server_name localhost; #分類信息 location /static/category/ { alias F:/develop/xuecheng/static/category/; } ...
3.1.4搜索頁面
創(chuàng)建搜索頁面如下:
3.1.4搜索頁面
創(chuàng)建搜索頁面如下:
//配置文件
let config=require('~/config/sysConfig') import querystring from 'querystring' import * as courseApi from '~/api/course' export default {
head() { return {
title: '傳智播客‐一樣的教育,不一樣的品質(zhì)',
meta: [
{charset: 'utf‐8'},
{name: 'description', content: '傳智播客專注IT培訓(xùn),Java培訓(xùn),Android培訓(xùn),安卓培訓(xùn),PHP培
訓(xùn),C++培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),平面設(shè)計(jì)培訓(xùn),UI設(shè)計(jì)培訓(xùn),移動(dòng)開發(fā)培訓(xùn),網(wǎng)絡(luò)營銷培訓(xùn),web前端培訓(xùn),云計(jì)算大數(shù)據(jù)培訓(xùn), 全棧工程師培訓(xùn),產(chǎn)品經(jīng)理培訓(xùn)。'},
{name: 'keywords', content: this.keywords}
],
link: [
{rel: 'stylesheet', href: '/static/plugins/normalize‐css/normalize.css'},
{rel: 'stylesheet', href: '/static/plugins/bootstrap/dist/css/bootstrap.css'},
{rel: 'stylesheet', href: '/static/css/page‐learing‐list.css'}
]
}
},
<script>
//配置文件
let config=require('~/config/sysConfig') import querystring from 'querystring' import * as courseApi from '~/api/course' export default {
head() { return {
title: '傳智播客‐一樣的教育,不一樣的品質(zhì)',
meta: [
{charset: 'utf‐8'},
{name: 'description', content: '傳智播客專注IT培訓(xùn),Java培訓(xùn),Android培訓(xùn),安卓培訓(xùn),PHP培 訓(xùn),C++培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),平面設(shè)計(jì)培訓(xùn),UI設(shè)計(jì)培訓(xùn),移動(dòng)開發(fā)培訓(xùn),網(wǎng)絡(luò)營銷培訓(xùn),web前端培訓(xùn),云計(jì)算大數(shù)據(jù)培訓(xùn), 全棧工程師培訓(xùn),產(chǎn)品經(jīng)理培訓(xùn)。'},
{name: 'keywords', content: this.keywords}
],
link: [
{rel: 'stylesheet', href: '/static/plugins/normalize‐css/normalize.css'},
{rel: 'stylesheet', href: '/static/plugins/bootstrap/dist/css/bootstrap.css'},
{rel: 'stylesheet', href: '/static/css/page‐learing‐list.css'}
]
}
},
async asyncData({ store, route }) { return {
courselist: {},
first_category:{},
second_category:{}, mt:'',
st:'',
grade:'',
keyword:'', total:0,
imgUrl:config.imgUrl
}
},
data() { return {
courselist: {}, first_category:{}, second_category:{}, mt:'',
st:'',
grade:'',
keyword:'', imgUrl:config.imgUrl, total:0,//總記錄數(shù)page:1,//頁碼page_size:12//每頁顯示個(gè)數(shù)
}
},
watch:{//路由發(fā)生變化立即搜索search表示search方法'$route':'search'
},
methods: {
//分頁觸發(fā)handleCurrentChange(page) {
},
//搜索方法search(){
//刷新當(dāng)前頁面
window.location.reload();
}
}
}
</script>
3.1.5測試
重啟Nginx,請(qǐng)求:http://www.xuecheng.com/course/search,頁面效果如下:
3.2查詢?nèi)?/strong>
3.2.1需求分析
初次進(jìn)入頁面,沒有輸入任何查詢條件,默認(rèn)查詢?nèi)空n程,分頁顯示。
3.2.2API方法
在api目錄創(chuàng)建本工程所用的api方法類,api方法類使用了public.js等一些抽取類:
/api/public.js抽取axios 的基礎(chǔ)方法
/api/util.js工具類
/con?g/sysCon?g.js系統(tǒng)配置類,配置了系統(tǒng)參數(shù)變量
創(chuàng)建course.js,作為課程相關(guān)業(yè)務(wù)模塊的api方法類。
import http from './public' import qs from 'qs' let config=require('~/config/sysConfig') let apiURL=config.apiURL let staticURL=config.staticURL if (typeof window==='undefined') { apiURL=config.backApiURL staticURL=config.backStaticURL } /*搜索*/ export const search_course=(page,size,params)=> { let querys=qs.stringify(params); return http.requestQuickGet(apiURL+"/search/course/list/"+page+"/"+size+"?"+querys); }
3.2.3搜索方法
實(shí)現(xiàn)思路如下:
1、用戶請(qǐng)求本頁面到達(dá)node.js
2、在asyncData方法中向服務(wù)端請(qǐng)求查詢課程 3、asyncData方法執(zhí)行完成開始服務(wù)端渲染
在asyncData中執(zhí)行搜索,代碼如下:
async asyncData({ store, route }) {//服務(wù)端調(diào)用方法 //搜索課程 let page=route.query.page; if(!page){ page=1; }else{ page=Number.parseInt(page) } console.log(page); //請(qǐng)求搜索服務(wù),搜索服務(wù) let course_data=await courseApi.search_course(page,2,route.query); console.log(course_data) if (course_data && course_data.queryResult ) { let keywords='' let mt='' let st='' let grade='' let keyword='' let total=course_data.queryResult.total if( route.query.mt){ mt=route.query.mt } if( route.query.st){ st=route.query.st } if( route.query.grade){ grade=route.query.grade } if( route.query.keyword){ keyword=route.query.keyword } return { courselist: course_data.queryResult.list,//課程列表keywords:keywords, mt:mt, st:st, grade:grade, keyword:keyword, page:page, total:total, imgUrl:config.imgUrl } }else{ return { courselist: {}, first_category:{}, second_category:{}, mt:'', st:'', grade:'', keyword:'', page:page, total:0, imgUrl:config.imgUrl } } }
3.2.5 頁面
在頁面中展示課程列表。
<div class="content‐list"> <div class="recom‐item" v‐for="(course, index) in courselist"> <nuxt‐link :to="'/course/detail/'+course.id+'.html'" target="_blank"> <div v‐if="course.pic"> <p><img :src="imgUrl+'/'+course.pic" width="100%" alt=""></p> </div> <div v‐else> <p><img src="/img/widget‐demo1.png" width="100%" alt=""></p> </div> <ul > <li class="course_title"><span v‐html="course.name"></span></li> <li style="float: left"><span v‐if="course.charge=='203001'">免費(fèi)</span> <span v‐if="course.charge=='203002'">¥{{course.price | money}}</span> <!‐‐ <em> ? </em>‐‐> <!‐‐<em>1125人在學(xué)習(xí)</em>‐‐></li> </ul> </nuxt‐link> </div> <li class="clearfix"></li> </div>
3.3分頁查詢
3.3.1服務(wù)端代碼
... //分頁 //當(dāng)前頁碼 if(page<=0){ page=1; } //起始記錄下標(biāo) int from=(page ‐1) * size; searchSourceBuilder.from(from); searchSourceBuilder.size(size); ...
3.3.2前端代碼
使用Element-UI的el-pagination分頁插件:
<div style="text‐align: center"> <el‐pagination background layout="prev, pager, next" @current‐change="handleCurrentChange" :total="total" :page‐size="page_size" :current‐page="page" prev‐text="上一頁" next‐text="下一頁"> </el‐pagination> </div>
定義分頁觸發(fā)方法:
methods:{ //分頁觸發(fā)handleCurrentChange(page) { this.page=page this.$route.query.page=page let querys=querystring.stringify(this.$route.query) window.location='/course/search?'+querys; } ...
3.4按分類搜索
3.4.1需求分析
1、通過一級(jí)分類搜索
2、選擇一級(jí)分類后將顯示下屬的二級(jí)分類
3、選擇二分類進(jìn)行搜索
4、選擇一級(jí)分類的全部則表示沒有按照分類搜索
5、選擇一級(jí)分類的全部時(shí)二級(jí)分類不顯示
3.4.2API方法
課程分類將通過頁面靜態(tài)化的方式寫入靜態(tài)資源下,通過/category/category.json可訪問,通過
www.xuecheng.com/static/category/category.json即可訪問。
category.json的內(nèi)容如下:
我們需要定義api方法獲取所有的分類在/api/course.js中添加:
/*獲取分類*/ export const sysres_category=()=> { return http.requestQuickGet(staticURL+"/static/category/category.json"); }
3.4.3在asyncData中查詢分類
進(jìn)入搜索頁面將默認(rèn)顯示所有一級(jí)分類,當(dāng)前如果已選擇一級(jí)分類則要顯示所有一級(jí)分類及該一級(jí)分類下屬的二級(jí) 分類。
在asyncData方法中實(shí)現(xiàn)上邊的需求,代碼如下:
async asyncData({ store, route }) {//服務(wù)端調(diào)用方法 //搜索課程 let page=route.query.page; if(!page){ page=1; }else{ page=Number.parseInt(page) } console.log(page); //請(qǐng)求搜索服務(wù),搜索服務(wù) let course_data=await courseApi.search_course(page,2,route.query); console.log(course_data) //查詢分類 let category_data=await courseApi.sysres_category() if (course_data && course_data.queryResult ) { //全部分類 let category=category_data.category//分部分類 let first_category=category[0].children//一級(jí)分類let second_category=[]//二級(jí)分類 let keywords='' let mt='' let st='' let grade='' let keyword='' let total=course_data.queryResult.total if( route.query.mt){ mt=route.query.mt } if( route.query.st){ st=route.query.st } if( route.query.grade){ grade=route.query.grade } if( route.query.keyword){ keyword=route.query.keyword } //遍歷一級(jí)分類 for(var i in first_category){ keywords+=first_category[i].name+' ' if(mt!=''&& mt==first_category[i].id){ //取出二級(jí)分類 second_category=first_category[i].children; // console.log(second_category) break; } } return { courselist: course_data.queryResult.list,//課程列表first_category: first_category, second_category: second_category, keywords:keywords, mt:mt, st:st, grade:grade, keyword:keyword, page:page, total:total, imgUrl:config.imgUrl } }else{ return { courselist: {}, first_category:{}, second_category:{}, mt:'', st:'', grade:'', keyword:'', page:page, total:0, imgUrl:config.imgUrl } } }
3.3.4頁面
在頁面顯示一級(jí)分類及二級(jí)分類,需要根據(jù)當(dāng)前是否選擇一級(jí)分類、是否選擇二分類顯示頁面內(nèi)容。
<ul> <li>一級(jí)分類:</li> <li v‐if="mt!=''"><nuxt‐link class="title‐link" :to="'/course/search? keyword='+keyword+'&grade='+grade">全部</nuxt‐link></li> <li class="all" v‐else>全部</li> <ol> <li v‐for="category_v in first_category"> <nuxt‐link class="title‐link all" :to="'/course/search?keyword='+keyword+'&mt=' + category_v.id" v‐if="category_v.id==mt">{{category_v.name}}</nuxt‐link> <nuxt‐link class="title‐link" :to="'/course/search?keyword='+keyword+'&mt=' + category_v.id" v‐else>{{category_v.name}}</nuxt‐link> </li> </ol> <!‐‐<ol> <li>數(shù)據(jù)分析</li> <li>機(jī)器學(xué)習(xí)工程</li> <li>前端開發(fā)工程</li> </ol>‐‐> </ul> <ul> <li>二級(jí)分類:</li> <li v‐if="st!=''"><nuxt‐link class="title‐link" :to="'/course/search? keyword='+keyword+'&mt='+mt+'&grade='+grade">全部</nuxt‐link></li> <li class="all" v‐else>全部</li> <ol v‐if="second_category.length>0"> <li v‐for="category_v in second_category"> <nuxt‐link class="title‐link all" :to="'/course/search?keyword='+keyword+'&mt='+mt+'&st=' + category_v.id" v‐if="category_v.id==st">{{category_v.name}}</nuxt‐link> <nuxt‐link class="title‐link" :to="'/course/search?keyword='+keyword+'&mt='+mt+'&st=' + category_v.id" v‐else>{{category_v.name}}</nuxt‐link> </li> <!‐‐ <li>大數(shù)據(jù)</li> <li>云計(jì)算</li>‐‐> </ol> <!‐‐<a href="#" class="more">更多 ∨</a>‐‐> </ul>
當(dāng)用戶點(diǎn)擊分類時(shí)立即執(zhí)行搜索,實(shí)現(xiàn)思路如下:
1)點(diǎn)擊分類立即更改路由。
2)通過監(jiān)聽路由,路由更改則刷新頁面。
1)創(chuàng)建搜索方法
search(){ //刷新當(dāng)前頁面window.location.reload(); }
2)定義watch
通過vue.js的watch可以實(shí)現(xiàn)監(jiān)視某個(gè)變量,當(dāng)變量值出現(xiàn)變化時(shí)執(zhí)行某個(gè)方法。 實(shí)現(xiàn)思路是:
1、點(diǎn)擊分類頁面路由更改
2、通過watch監(jiān)視路由,路由更改觸發(fā)search方法與methods并行定義watch:
watch:{//路由發(fā)生變化立即搜索search表示search方法'$route':'search' },
3.5按難度等級(jí)搜索
3.5.1需求分析
用戶選擇不同的課程難度等級(jí)去搜索課程。
3.5.2API方法
使用 search_course方法完成搜索。
3.5.3頁面
按難度等級(jí)搜索思路如下:
1)點(diǎn)擊難度等級(jí)立即更改路由。
2)通過監(jiān)聽路由,路由更改則立即執(zhí)行search搜索方法。按難度等級(jí)搜索頁面代碼如下:
<ul> <li>難度等級(jí):</li> <li v‐if="grade!=''"> <nuxt‐link class="title‐link" :to="'/course/search?keyword='+keyword+'&mt=' + mt+'&st='+st+'&grade='">全部 </nuxt‐link> </li> <li class="all" v‐else>全部</li> <ol> <li v‐if="grade=='200001'" class="all">初級(jí)</li> <li v‐else><nuxt‐link class="title‐link" :to="'/course/search?keyword='+keyword+'&mt=' + mt+'&st='+st+'&grade=200001'">初級(jí)</nuxt‐link></li> <li v‐if="grade=='200002'" class="all">中級(jí)</li> <li v‐else><nuxt‐link class="title‐link" :to="'/course/search?keyword='+keyword+'&mt=' + mt+'&st='+st+'&grade=200002'">中級(jí)</nuxt‐link></li> <li v‐if="grade=='200003'" class="all">高級(jí)</li> <li v‐else><nuxt‐link class="title‐link" :to="'/course/search?keyword='+keyword+'&mt=' + mt+'&st='+st+'&grade=200003'">高級(jí)</nuxt‐link></li> </ol> </ul>
3.6高亮顯示
3.6.1服務(wù)端代碼
修改service的搜索方法,添加高亮設(shè)置:
... //定義高亮 HighlightBuilder highlightBuilder=new HighlightBuilder(); highlightBuilder.preTags("<font class='eslight'>"); highlightBuilder.postTags("</font>"); highlightBuilder.fields().add(new HighlightBuilder.Field("name")); searchSourceBuilder.highlighter(highlightBuilder); ... //解析高亮字段 for(SearchHit hit:searchHits){ CoursePub coursePub=new CoursePub(); //源文檔 Map<String, Object> sourceAsMap=hit.getSourceAsMap(); //課程id String id=(String) sourceAsMap.get("id"); coursePub.setId(id); //取出name String name=(String) sourceAsMap.get("name"); //取出高亮字段 Map<String, HighlightField> highlightFields=hit.getHighlightFields(); if(highlightFields.get("name")!=null){ HighlightField highlightField=highlightFields.get("name"); Text[] fragments=highlightField.fragments(); StringBuffer stringBuffer=new StringBuffer(); for(Text text:fragments){ stringBuffer.append(text); } name=stringBuffer.toString(); } coursePub.setName(name); ....
3.6.2前端代碼
在search/index.vue中定義eslight樣式:
<style> .eslight{ color: red; } ...
4.1需求分析
本次集成測試的目的如下:
1、測試課程發(fā)布與CMS接口是否正常。
2、測試課程發(fā)布與ES接口是否正常。
3、測試課程從創(chuàng)建到發(fā)布的整個(gè)過程。
4.2準(zhǔn)備環(huán)境
1、啟動(dòng)MySQL、MongoDB
2、啟動(dòng)ElasticSearch、RabbitMQ 3、啟動(dòng)Eureka Server
4、啟動(dòng)CMS、課程管理服務(wù)、搜索服務(wù)。
5、啟動(dòng)Nginx、系統(tǒng)管理前端、教學(xué)管理前端、Nuxt.js。
試中常被問到的問題,此問題包含web開發(fā)中從前端到后端到運(yùn)維的絕大多數(shù)知識(shí),主要考察面試者知識(shí)的廣度。本文會(huì)根據(jù)作者了解的程度增加不斷更新,不足之處歡迎評(píng)論區(qū)補(bǔ)充。
首先了解一下URL的組成:
http://www.baidu.com:8080/news/index.asp?boardID=5&ID=24618&page=1#name
從上面的URL可以看出,一個(gè)完整的URL包括以下幾部分:
1、協(xié)議部分:該URL的協(xié)議部分為“http:”,這代表網(wǎng)頁使用的是HTTP協(xié)議。在Internet中可以使用多種協(xié)議,如HTTP,F(xiàn)TP等等本例中使用的是HTTP協(xié)議。在"HTTP"后面的“//”為分隔符
2、域名部分:該URL的域名部分為“www.baidu.com”。一個(gè)URL中,也可以使用IP地址作為域名使用
3、端口部分:跟在域名后面的是端口,域名和端口之間使用“:”作為分隔符。端口不是一個(gè)URL必須的部分,如果省略端口部分,將采用默認(rèn)端口80
4、虛擬目錄部分:從域名后的第一個(gè)“/”開始到最后一個(gè)“/”為止,是虛擬目錄部分。虛擬目錄也不是一個(gè)URL必須的部分。本例中的虛擬目錄是“/news/”
5、文件名部分:從域名后的最后一個(gè)“/”開始到“?”為止,是文件名部分,如果沒有“?”,則是從域名后的最后一個(gè)“/”開始到“#”為止,是文件部分,如果沒有“?”和“#”,那么從域名后的最后一個(gè)“/”開始到結(jié)束,都是文件名部分。本例中的文件名是“index.asp”。文件名部分也不是一個(gè)URL必須的部分,如果省略該部分,則使用默認(rèn)的文件名
6、錨部分:從“#”開始到最后,都是錨部分。本例中的錨部分是“name”。錨部分也不是一個(gè)URL必須的部分
7、參數(shù)部分:從“?”開始到“#”為止之間的部分為參數(shù)部分,又稱搜索部分、查詢部分。本例中的參數(shù)部分為“boardID=5&ID=24618&page=1”。參數(shù)可以允許有多個(gè)參數(shù),參數(shù)與參數(shù)之間用“&”作為分隔符。
很多大公司面試喜歡問這樣一道面試題,輸入U(xiǎn)RL到看見頁面發(fā)生了什么?,今天我們來總結(jié)一下。 簡單來說,共有以下幾個(gè)過程
下面我們來看看具體的細(xì)節(jié)
發(fā)送至 DNS 服務(wù)器并獲得域名對(duì)應(yīng)的 WEB 服務(wù)器的 ip 地址。
DNS 解析首先會(huì)從你的瀏覽器的緩存中去尋找是否有這個(gè)網(wǎng)址對(duì)應(yīng)的 IP 地址,如果沒有就向OS系統(tǒng)的 DNS 緩存中尋找,如果沒有就是路由器的 DNS 緩存, 如果沒有就是 ISP 的DNS 緩存中尋找。 所以,緩存的尋找過程就是: 瀏覽器 -> 系統(tǒng) -> 路由器 -> ISP。 如果在某一個(gè)緩存中找到的話,就直接跳到下一步。 如果都沒有找到的話,就會(huì)向 ISP 或者公共的域名解析服務(wù)發(fā)起 DNS 查找請(qǐng)求。這個(gè)查找的過程還是一個(gè)遞歸查詢的過程。
輸入www.google.com網(wǎng)址后,首先在本地的域名服務(wù)器中查找,沒找到去根域名服務(wù)器查找,沒有再去com頂級(jí)域名服務(wù)器查找,,如此的類推下去,直到找到IP地址,然后把它記錄在本地,供下次使用。大致過程就是. -> .com -> google.com. -> www.google.com.。 (你可能覺得我多寫 .,并木有,這個(gè).對(duì)應(yīng)的就是根域名服務(wù)器,默認(rèn)情況下所有的網(wǎng)址的最后一位都是.,既然是默認(rèn)情況下,為了方便用戶,通常都會(huì)省略,瀏覽器在請(qǐng)求DNS的時(shí)候會(huì)自動(dòng)加上)
既然已經(jīng)懂得了解析的具體過程,我們可以看到上述一共經(jīng)過了N個(gè)過程,每個(gè)過程有一定的消耗和時(shí)間的等待,因此我們得想辦法解決一下這個(gè)問題!
DNS存在著多級(jí)緩存,從離瀏覽器的距離排序的話,有以下幾種: 瀏覽器緩存,系統(tǒng)緩存,路由器緩存,IPS服務(wù)器緩存,根域名服務(wù)器緩存,頂級(jí)域名服務(wù)器緩存,主域名服務(wù)器緩存。
在你的chrome瀏覽器中輸入:chrome://dns/,你可以看到chrome瀏覽器的DNS緩存。
系統(tǒng)緩存主要存在/etc/hosts(Linux系統(tǒng))中
檢查瀏覽器是否有緩存
通過Cache-Control和Expires來檢查是否命中強(qiáng)緩存,命中則直接取本地磁盤的html(狀態(tài)碼為200 from disk(or memory) cache,內(nèi)存or磁盤);
如果沒有命中強(qiáng)緩存,則會(huì)向服務(wù)器發(fā)起請(qǐng)求(先進(jìn)行下一步的TCP連接),服務(wù)器通過Etag和Last-Modify來與服務(wù)器確認(rèn)返回的響應(yīng)是否被更改(協(xié)商緩存),若無更改則返回狀態(tài)碼(304 Not Modified),瀏覽器取本地緩存;
若強(qiáng)緩存和協(xié)商緩存都沒有命中則返回請(qǐng)求結(jié)果。
不知道你們有沒有注意這樣一件事,你訪問http://baidu.com的時(shí)候,每次響應(yīng)的并非是同一個(gè)服務(wù)器(IP地址不同),一般大公司都有成百上千臺(tái)服務(wù)器來支撐訪問,假設(shè)只有一個(gè)服務(wù)器,那它的性能和存儲(chǔ)量要多大才能支撐這樣大量的訪問呢?DNS可以返回一個(gè)合適的機(jī)器的IP給用戶,例如可以根據(jù)每臺(tái)機(jī)器的負(fù)載量,該機(jī)器離用戶地理位置的距離等等,這種過程就是DNS負(fù)載均衡
TCP 協(xié)議通過三次握手建立連接。
翻譯成大白話就是:
為什么是3次?:避免歷史連接,確認(rèn)客戶端發(fā)來的請(qǐng)求是這次通信的人。
為什么不是4次?:3次夠了第四次浪費(fèi)
建立連接的過程是利用客戶服務(wù)器模式,假設(shè)主機(jī)A為客戶端,主機(jī)B為服務(wù)器端。
采用三次握手是為了防止失效的連接請(qǐng)求報(bào)文段突然又傳送到主機(jī)B,因而產(chǎn)生錯(cuò)誤。失效的連接請(qǐng)求報(bào)文段是指:主機(jī)A發(fā)出的連接請(qǐng)求沒有收到主機(jī)B的確認(rèn),于是經(jīng)過一段時(shí)間后,主機(jī)A又重新向主機(jī)B發(fā)送連接請(qǐng)求,且建立成功,順序完成數(shù)據(jù)傳輸。考慮這樣一種特殊情況,主機(jī)A第一次發(fā)送的連接請(qǐng)求并沒有丟失,而是因?yàn)榫W(wǎng)絡(luò)節(jié)點(diǎn)導(dǎo)致延遲達(dá)到主機(jī)B,主機(jī)B以為是主機(jī)A又發(fā)起的新連接,于是主機(jī)B同意連接,并向主機(jī)A發(fā)回確認(rèn),但是此時(shí)主機(jī)A根本不會(huì)理會(huì),主機(jī)B就一直在等待主機(jī)A發(fā)送數(shù)據(jù),導(dǎo)致主機(jī)B的資源浪費(fèi)。
采用兩次握手不行,原因就是上面說的失效的連接請(qǐng)求的特殊情況。而在三次握手中, client和server都有一個(gè)發(fā)syn和收ack的過程, 雙方都是發(fā)后能收, 表明通信則準(zhǔn)備工作OK.
為什么不是四次握手呢? 大家應(yīng)該知道通信中著名的藍(lán)軍紅軍約定, 這個(gè)例子說明, 通信不可能100%可靠, 而上面的三次握手已經(jīng)做好了通信的準(zhǔn)備工作, 再增加握手, 并不能顯著提高可靠性, 而且也沒有必要。
第一次握手:
客戶端發(fā)送syn包(Seq=x)到服務(wù)器,并進(jìn)入SYN_SEND狀態(tài),等待服務(wù)器確認(rèn);
第二次握手:
服務(wù)器收到syn包,必須確認(rèn)客戶的SYN(ack=x+1),同時(shí)自己也發(fā)送一個(gè)SYN包(Seq=y),即SYN+ACK包,此時(shí)服務(wù)器進(jìn)入SYN_RECV狀態(tài);
第三次握手:
客戶端收到服務(wù)器的SYN+ACK包,向服務(wù)器發(fā)送確認(rèn)包ACK(ack=y+1),此包發(fā)送完畢,客戶端和服務(wù)器進(jìn)入ESTABLISHED狀態(tài),完成三次握手。
握手過程中傳送的包里不包含數(shù)據(jù),三次握手完畢后,客戶端與服務(wù)器才正式開始傳送數(shù)據(jù)。理想狀態(tài)下,TCP連接一旦建立,在通信雙方中的任何一方主動(dòng)關(guān)閉連接之前,TCP 連接都將被一直保持下去。
HTTPS=HTTP + 加密 + 認(rèn)證 + 完整性保護(hù)
要先申請(qǐng)CA證書,并安裝在服務(wù)器上(一個(gè)文件,配置nginx支持監(jiān)聽443端口開啟ssl并設(shè)置證書路徑)
瀏覽器發(fā)送請(qǐng)求;
網(wǎng)站從瀏覽器發(fā)過來的加密規(guī)則中選一組自身也支持的加密算法和hash算法,并向?yàn)g覽器發(fā)送帶有公鑰的證書,當(dāng)然證書還包含了很多信息,如網(wǎng)站地址、證書的頒發(fā)機(jī)構(gòu)、過期時(shí)間等。
瀏覽器解析證書。
驗(yàn)證證書的合法性。如頒發(fā)機(jī)構(gòu)是否合法、證書中的網(wǎng)站地址是否與訪問的地址一致,若不合法,則瀏覽器提示證書不受信任,若合法,瀏覽器會(huì)顯示一個(gè)小鎖頭。
若合法,或用戶接受了不合法的證書,瀏覽器會(huì)生成一串隨機(jī)數(shù)的密碼(即密鑰),并用證書中提供的公鑰加密。
使用約定好的hash計(jì)算握手消息,并使用生成的隨機(jī)數(shù)(即密鑰)對(duì)消息進(jìn)行加密,最后將之前生成的所有消息一并發(fā)送給網(wǎng)站服務(wù)器。
網(wǎng)站服務(wù)器解析消息。用已有的私鑰將密鑰解密出來,然后用密鑰解密發(fā)過來的握手消息,并驗(yàn)證是否跟瀏覽器傳過來的一致。然后再用密鑰加密一段握手消息,發(fā)送給瀏覽器。
瀏覽器解密并計(jì)算握手消息的HASH,如果與服務(wù)端發(fā)來的HASH一致,此時(shí)握手過程結(jié)束,之后所有的通信數(shù)據(jù)將由之前瀏覽器生成的隨機(jī)密碼并利用對(duì)稱加密算法進(jìn)行加密。這里瀏覽器與網(wǎng)站互相發(fā)送加密的握手消息并驗(yàn)證,目的是為了保證雙方都獲得了一致的密碼,并且可以正常的加密解密數(shù)據(jù),為后續(xù)真正數(shù)據(jù)的傳輸做一次測試。
發(fā)送HTTP請(qǐng)求
首先科補(bǔ)一個(gè)小知識(shí),HTTP的端口為80/8080,而HTTPS的端口為443
發(fā)送HTTP請(qǐng)求的過程就是構(gòu)建HTTP請(qǐng)求報(bào)文并通過TCP協(xié)議中發(fā)送到服務(wù)器指定端口 請(qǐng)求報(bào)文由請(qǐng)求行,請(qǐng)求抱頭,請(qǐng)求正文組成。
請(qǐng)求行
請(qǐng)求行的格式為Method Request-URL HTTP-Version CRLF eg: GET index.html HTTP/1.1 常用的方法有: GET, POST, PUT, DELETE, OPTIONS, HEAD。
常見的請(qǐng)求方法區(qū)別
這里主要展示POST和GET的區(qū)別
常見的區(qū)別
注意一點(diǎn)你也可以在GET里面藏body,POST里面帶參數(shù)
重點(diǎn)區(qū)別
GET會(huì)產(chǎn)生一個(gè)TCP數(shù)據(jù)包,而POST會(huì)產(chǎn)生兩個(gè)TCP數(shù)據(jù)包。
詳細(xì)的說就是:
注意一點(diǎn),并不是所有的瀏覽器都會(huì)發(fā)送兩次數(shù)據(jù)包,F(xiàn)irefox就發(fā)送一次
請(qǐng)求報(bào)頭
請(qǐng)求報(bào)頭允許客戶端向服務(wù)器傳遞請(qǐng)求的附加信息和客戶端自身的信息。
從圖中可以看出,請(qǐng)求報(bào)頭中使用了Accept, Accept-Encoding, Accept-Language, Cache-Control, Connection, Cookie等字段。Accept用于指定客戶端用于接受哪些類型的信息,Accept-Encoding與Accept類似,它用于指定接受的編碼方式。Connection設(shè)置為Keep-alive用于告訴客戶端本次HTTP請(qǐng)求結(jié)束之后并不需要關(guān)閉TCP連接,這樣可以使下次HTTP請(qǐng)求使用相同的TCP通道,節(jié)省TCP連接建立的時(shí)間。
請(qǐng)求正文
當(dāng)使用POST, PUT等方法時(shí),通常需要客戶端向服務(wù)器傳遞數(shù)據(jù)。這些數(shù)據(jù)就儲(chǔ)存在請(qǐng)求正文中。在請(qǐng)求包頭中有一些與請(qǐng)求正文相關(guān)的信息,例如: 現(xiàn)在的Web應(yīng)用通常采用Rest架構(gòu),請(qǐng)求的數(shù)據(jù)格式一般為json。這時(shí)就需要設(shè)置Content-Type: application/json。
更重要的事情-HTTP緩存
HTTP屬于客戶端緩存,我們常認(rèn)為瀏覽器有一個(gè)緩存數(shù)據(jù)庫,用來保存一些靜態(tài)文件,下面我們分為以下幾個(gè)方面來簡單介紹HTTP緩存
緩存的規(guī)則
緩存規(guī)則分為強(qiáng)制緩存和協(xié)商緩存
強(qiáng)制緩存
當(dāng)緩存數(shù)據(jù)庫中有客戶端需要的數(shù)據(jù),客戶端直接將數(shù)據(jù)從其中拿出來使用(如果數(shù)據(jù)未失效),當(dāng)緩存服務(wù)器沒有需要的數(shù)據(jù)時(shí),客戶端才會(huì)向服務(wù)端請(qǐng)求。
又稱對(duì)比緩存。客戶端會(huì)先從緩存數(shù)據(jù)庫拿到一個(gè)緩存的標(biāo)識(shí),然后向服務(wù)端驗(yàn)證標(biāo)識(shí)是否失效,如果沒有失效服務(wù)端會(huì)返回304,這樣客戶端可以直接去緩存數(shù)據(jù)庫拿出數(shù)據(jù),如果失效,服務(wù)端會(huì)返回新的數(shù)據(jù)
強(qiáng)制緩存
對(duì)于強(qiáng)制緩存,服務(wù)器響應(yīng)的header中會(huì)用兩個(gè)字段來表明——Expires和Cache-Control。
Expires
Exprires的值為服務(wù)端返回的數(shù)據(jù)到期時(shí)間。當(dāng)再次請(qǐng)求時(shí)的請(qǐng)求時(shí)間小于返回的此時(shí)間,則直接使用緩存數(shù)據(jù)。但由于服務(wù)端時(shí)間和客戶端時(shí)間可能有誤差,這也將導(dǎo)致緩存命中的誤差,另一方面,Expires是HTTP1.0的產(chǎn)物,故現(xiàn)在大多數(shù)使用Cache-Control替代。
Cache-Control
Cache-Control有很多屬性,不同的屬性代表的意義也不同。
協(xié)商緩存
協(xié)商緩存需要進(jìn)行對(duì)比判斷是否可以使用緩存。瀏覽器第一次請(qǐng)求數(shù)據(jù)時(shí),服務(wù)器會(huì)將緩存標(biāo)識(shí)與數(shù)據(jù)一起響應(yīng)給客戶端,客戶端將它們備份至緩存中。再次請(qǐng)求時(shí),客戶端會(huì)將緩存中的標(biāo)識(shí)發(fā)送給服務(wù)器,服務(wù)器根據(jù)此標(biāo)識(shí)判斷。若未失效,返回304狀態(tài)碼,瀏覽器拿到此狀態(tài)碼就可以直接使用緩存數(shù)據(jù)了。
對(duì)于協(xié)商緩存來說,緩存標(biāo)識(shí)我們需要著重理解一下,下面我們將著重介紹它的兩種緩存方案。
Last-Modified
Last-Modified:服務(wù)器在響應(yīng)請(qǐng)求時(shí),會(huì)告訴瀏覽器資源的最后修改時(shí)間。
從字面上看,就是說:從某個(gè)時(shí)間節(jié)點(diǎn)算起,是否文件被修改了
這兩個(gè)的區(qū)別是一個(gè)是修改了才下載一個(gè)是沒修改才下載。
Last-Modified 說好卻也不是特別好,因?yàn)槿绻诜?wù)器上,一個(gè)資源被修改了,但其實(shí)際內(nèi)容根本沒發(fā)生改變,會(huì)因?yàn)長ast-Modified時(shí)間匹配不上而返回了整個(gè)實(shí)體給客戶端(即使客戶端緩存里有個(gè)一模一樣的資源)。為了解決這個(gè)問題,HTTP1.1推出了Etag。
Etag
Etag:服務(wù)器響應(yīng)請(qǐng)求時(shí),通過此字段告訴瀏覽器當(dāng)前資源在服務(wù)器生成的唯一標(biāo)識(shí)(生成規(guī)則由服務(wù)器決定)
但是實(shí)際應(yīng)用中由于Etag的計(jì)算是使用算法來得出的,而算法會(huì)占用服務(wù)端計(jì)算的資源,所有服務(wù)端的資源都是寶貴的,所以就很少使用Etag了。
緩存的優(yōu)點(diǎn)
不同刷新的請(qǐng)求執(zhí)行過程
瀏覽器地址欄中寫入U(xiǎn)RL,回車
F5
Ctrl+F5
服務(wù)器處理請(qǐng)求并返回HTTP報(bào)文
它會(huì)對(duì)TCP連接進(jìn)行處理,對(duì)HTTP協(xié)議進(jìn)行解析,并按照?qǐng)?bào)文格式進(jìn)一步封裝成HTTP Request對(duì)象,供上層使用。這一部分工作一般是由Web服務(wù)器去進(jìn)行,我使用過的Web服務(wù)器有Tomcat, Nginx和Apache等等 HTTP報(bào)文也分成三份,狀態(tài)碼 ,響應(yīng)報(bào)頭和響應(yīng)報(bào)文
狀態(tài)碼
狀態(tài)碼是由3位數(shù)組成,第一個(gè)數(shù)字定義了響應(yīng)的類別,且有五種可能取值:
平時(shí)遇到比較常見的狀態(tài)碼有:200, 204, 301, 302, 304, 400, 401, 403, 404, 422, 500
常見狀態(tài)碼區(qū)別
200 成功
請(qǐng)求成功,通常服務(wù)器提供了需要的資源。
204 無內(nèi)容
服務(wù)器成功處理了請(qǐng)求,但沒有返回任何內(nèi)容。
301 永久移動(dòng)
請(qǐng)求的網(wǎng)頁已永久移動(dòng)到新位置。 服務(wù)器返回此響應(yīng)(對(duì) GET 或 HEAD 請(qǐng)求的響應(yīng))時(shí),會(huì)自動(dòng)將請(qǐng)求者轉(zhuǎn)到新位置。
302 臨時(shí)移動(dòng)
服務(wù)器目前從不同位置的網(wǎng)頁響應(yīng)請(qǐng)求,但請(qǐng)求者應(yīng)繼續(xù)使用原有位置來進(jìn)行以后的請(qǐng)求。
304 未修改
自從上次請(qǐng)求后,請(qǐng)求的網(wǎng)頁未修改過。 服務(wù)器返回此響應(yīng)時(shí),不會(huì)返回網(wǎng)頁內(nèi)容。
400 錯(cuò)誤請(qǐng)求
服務(wù)器不理解請(qǐng)求的語法。
401 未授權(quán)
請(qǐng)求要求身份驗(yàn)證。 對(duì)于需要登錄的網(wǎng)頁,服務(wù)器可能返回此響應(yīng)。
403 禁止
服務(wù)器拒絕請(qǐng)求。
404 未找到
服務(wù)器找不到請(qǐng)求的網(wǎng)頁。
422 無法處理
請(qǐng)求格式正確,但是由于含有語義錯(cuò)誤,無法響應(yīng)
500 服務(wù)器內(nèi)部錯(cuò)誤
服務(wù)器遇到錯(cuò)誤,無法完成請(qǐng)求。
響應(yīng)報(bào)頭
常見的響應(yīng)報(bào)頭字段有: Server, Connection...。
響應(yīng)報(bào)文
你從服務(wù)器請(qǐng)求的HTML,CSS,JS文件就放在這里面
就是Webkit解析渲染頁面的過程。
這個(gè)過程涉及兩個(gè)比較重要的概念回流和重繪,DOM結(jié)點(diǎn)都是以盒模型形式存在,需要瀏覽器去計(jì)算位置和寬度等,這個(gè)過程就是回流。等到頁面的寬高,大小,顏色等屬性確定下來后,瀏覽器開始繪制內(nèi)容,這個(gè)過程叫做重繪。瀏覽器剛打開頁面一定要經(jīng)過這兩個(gè)過程的,但是這個(gè)過程非常非常非常消耗性能,所以我們應(yīng)該盡量減少頁面的回流和重繪
這個(gè)過程中可能會(huì)有dom操作、ajax發(fā)起的http網(wǎng)絡(luò)請(qǐng)求等。
web-socket、ajax等,這個(gè)過程通常是為了獲取數(shù)據(jù)
setTimeout、setInterval、Promise等宏任務(wù)、微任務(wù)隊(duì)列
當(dāng)Render Tree中部分或全部元素的尺寸、結(jié)構(gòu)、或某些屬性發(fā)生改變時(shí),瀏覽器重新渲染部分或全部文檔的過程稱為回流。
會(huì)導(dǎo)致回流的操作:
一些常用且會(huì)導(dǎo)致回流的屬性和方法:
當(dāng)頁面中元素樣式的改變并不影響它在文檔流中的位置時(shí)(例如:color、background-color、visibility等),瀏覽器會(huì)將新樣式賦予給元素并重新繪制它,這個(gè)過程稱為重繪。
JS的解析是由瀏覽器的JS引擎完成的。由于JavaScript是單線程運(yùn)行,也就是說一個(gè)時(shí)間只能干一件事,干這件事情時(shí)其他事情都有排隊(duì),但是有些人物比較耗時(shí)(例如IO操作),所以將任務(wù)分為同步任務(wù)和異步任務(wù),所有的同步任務(wù)放在主線程上執(zhí)行,形成執(zhí)行棧,而異步任務(wù)等待,當(dāng)執(zhí)行棧被清空時(shí)才去看看異步任務(wù)有沒有東西要搞,有再提取到主線程執(zhí)行,這樣往復(fù)循環(huán)(冤冤相報(bào)何時(shí)了,阿彌陀佛),就形成了Event Loop事件循環(huán),下面來看看大人物
先看一段代碼
setTimeout(function(){
console.log('定時(shí)器開始啦')
});
new Promise(function(resolve){
console.log('馬上執(zhí)行for循環(huán)啦');
for(var i=0; i < 10000; i++){
i==99 && resolve();
}
}).then(function(){
console.log('執(zhí)行then函數(shù)啦')
});
console.log('代碼執(zhí)行結(jié)束');
結(jié)果我想大家都應(yīng)該知道。主要來介紹JavaScript的解析,至于Promise等下一節(jié)再說
JavaScript是一門單線程語言,盡管H5中提出了Web-Worker,能夠模擬實(shí)現(xiàn)多線程,但本質(zhì)上還是單線程,說它是多線程就是扯淡。
既然是單線程,每個(gè)事件的執(zhí)行就要有順序,比如你去銀行取錢,前面的人在進(jìn)行,后面的就得等待,要是前面的人弄個(gè)一兩個(gè)小時(shí),估計(jì)后面的人都瘋了,因此,瀏覽器的JS引擎處理JavaScript時(shí)分為同步任務(wù)和異步任務(wù)
這張圖我們可以清楚看到
js引擎存在monitoring process進(jìn)程,會(huì)持續(xù)不斷的檢查主線程執(zhí)行棧是否為空,一旦為空,就會(huì)去Event Queue那里檢查是否有等待被調(diào)用的函數(shù)。 估計(jì)看完這些你對(duì)事件循環(huán)有一定的了解,但是事實(shí)上我們看對(duì)的沒這么簡單,通常我們會(huì)看到Promise,setTimeout,process.nextTick(),這個(gè)時(shí)候你和我就懵逼。
除了同步任務(wù)和異步任務(wù),我們還分為宏任務(wù)和微任務(wù),常見的有以下幾種
不同任務(wù)會(huì)進(jìn)入不同的任務(wù)隊(duì)列來執(zhí)行。 JS引擎開始工作后,先在宏任務(wù)中開始第一次循環(huán)(script里面先執(zhí)行,不過我喜歡把它拎出來,直接稱其進(jìn)入執(zhí)行棧),當(dāng)主線程執(zhí)行棧全部任務(wù)被清空后去微任務(wù)看看,如果有等待執(zhí)行的任務(wù),執(zhí)行全部的微任務(wù)(其實(shí)將其回調(diào)函數(shù)推入執(zhí)行棧來執(zhí)行),再去宏任務(wù)找最先進(jìn)入隊(duì)列的任務(wù)執(zhí)行,執(zhí)行這個(gè)任務(wù)后再去主線程執(zhí)行任務(wù)(例如執(zhí)行```console.log("hello world")這種任務(wù)),執(zhí)行棧被清空后再去微任務(wù),這樣往復(fù)循環(huán)(冤冤相報(bào)何時(shí)了)
Tip:微任務(wù)會(huì)全部執(zhí)行,而宏任務(wù)會(huì)一個(gè)一個(gè)來執(zhí)行
下面來看一段代碼
setTimeout(function() {
console.log('setTimeout');
})
new Promise(function(resolve) {
console.log('promise');
resolve();
}).then(function() {
console.log('then');
})
console.log('console');
我們看看它的執(zhí)行情況
具體的執(zhí)行過程大致就是這樣。
文本標(biāo)記語言(HTML)是用于在 Internet 上顯示 Web 頁面的主要標(biāo)記語言。換句話說,網(wǎng)頁由 HTML 組成,用于通過 Web 瀏覽器顯示文本,圖像或其他資源。所有 HTML 都是純文本,這意味著它不是編譯的,可以由人類閱讀。HTML 文件的文件擴(kuò)展名為.htm 或.html。
自1990年以來,HTML就一直被用作WWW的信息表示語言,使用HTML語言描述的文件需要通過WWW瀏覽器顯示出效果。HTML是一種建立網(wǎng)頁文件的語言,通過標(biāo)記式的指令(Tag),將影像、聲音、圖片、文字動(dòng)畫、影視等內(nèi)容顯示出來。事實(shí)上,每一個(gè)HTML文檔都是一種靜態(tài)的網(wǎng)頁文件,這個(gè)文件里面包含了HTML指令代碼,這些指令代碼并不是一種程序語言,只是一種排版網(wǎng)頁中資料顯示位置的標(biāo)記結(jié)構(gòu)語言,易學(xué)易懂,非常簡單。
HTML的普遍應(yīng)用就是帶來了超文本的技術(shù)―通過單擊鼠標(biāo)從一個(gè)主題跳轉(zhuǎn)到另一個(gè)主題,從一個(gè)頁面跳轉(zhuǎn)到另一個(gè)頁面,與世界各地主機(jī)的文件鏈接超文本傳輸協(xié)議規(guī)定了瀏覽器在運(yùn)行HTML文檔時(shí)所遵循的規(guī)則和進(jìn)行的操作。HTTP協(xié)議的制定使瀏覽器在運(yùn)行超文本時(shí)有了統(tǒng)一的規(guī)則和標(biāo)準(zhǔn)。
萬維網(wǎng)(world wide web)上的一個(gè)超媒體文檔稱之為一個(gè)頁面(外語:page)。作為一個(gè)組織或者個(gè)人在萬維網(wǎng)上放置開始點(diǎn)的頁面稱為主頁(外語:Homepage)或首頁,主頁中通常包括有指向其他相關(guān)頁面或其他節(jié)點(diǎn)的指針(超級(jí)鏈接),所謂超級(jí)鏈接,就是一種統(tǒng)一資源定位器(Uniform Resource Locator,外語縮寫:URL)指針,通過激活(點(diǎn)擊)它,可使瀏覽器方便地獲取新的網(wǎng)頁。這也是HTML獲得廣泛應(yīng)用的最重要的原因之一。在邏輯上將視為一個(gè)整體的一系列頁面的有機(jī)集合稱為網(wǎng)站(Website或Site)。超級(jí)文本標(biāo)記語言(英文縮寫:HTML)是為“網(wǎng)頁創(chuàng)建和其它可在網(wǎng)頁瀏覽器中看到的信息”設(shè)計(jì)的一種標(biāo)記語言。
當(dāng) Web 開發(fā)人員構(gòu)建應(yīng)用程序時(shí),工作在服務(wù)器上執(zhí)行,原始 HTML 將發(fā)送給用戶。使用 AJAX 等技術(shù),服務(wù)器端開發(fā)和客戶端開發(fā)之間的界限很模糊。
HTML 從未被設(shè)計(jì)用于當(dāng)今存在的 Web,因?yàn)樗皇且环N在控制和設(shè)計(jì)方面具有嚴(yán)重限制的標(biāo)記語言。已經(jīng)使用了許多技術(shù)來解決這個(gè)問題 – 最重要的是層疊樣式表(CSS)。
長期解決方案是(或希望是)HTML5,它是下一代 HTML,允許更多的控制和交互性。與 Web 上的任何開發(fā)一樣,遷移到標(biāo)準(zhǔn)是一個(gè)緩慢而艱巨的過程,Web 開發(fā)人員和設(shè)計(jì)人員必須使用當(dāng)前和支持的技術(shù),這意味著基本 HTML 將繼續(xù)使用一段時(shí)間。
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。