本文項目基於Vue-Cli3,想知道如何正確搭建請看我以前的文章:javascript
axios
二次封裝這裏封裝的依據是後臺傳的JWT
,已封裝好的請跳過。html
import axios from 'axios' import router from '../router' import {MessageBox, Message} from 'element-ui' let loginUrl = '/login' // 根據環境切換接口地址 axios.defaults.baseURL = process.env.VUE_APP_API axios.defaults.headers = {'X-Requested-With': 'XMLHttpRequest'} axios.defaults.timeout = 60000 // 請求攔截器 axios.interceptors.request.use( config => { if (router.history.current.path !== loginUrl) { let token = window.sessionStorage.getItem('token') if (token == null) { router.replace({path: loginUrl, query: {redirect: router.currentRoute.fullPath}}) return false } else { config.headers['Authorization'] = 'JWT ' + token } } return config }, error => { Message.warning(error) return Promise.reject(error) }) 複製代碼
緊接着的是響應攔截器(即異常處理)前端
axios.interceptors.response.use( response => { return response.data }, error => { if (error.response !== undefined) { switch (error.response.status) { case 400: MessageBox.alert(error.response.data) break case 401: if (window.sessionStorage.getItem('out') === null) { window.sessionStorage.setItem('out', 1) MessageBox.confirm('會話已失效! 請從新登陸', '提示', {confirmButtonText: '從新登陸', cancelButtonText: '取消', type: 'warning'}).then(() => { router.replace({path: loginUrl, query: {redirect: router.currentRoute.fullPath}}) }).catch(action => { window.sessionStorage.clear() window.localStorage.clear() }) } break case 402: MessageBox.confirm('登錄超時 !', '提示', {confirmButtonText: '從新登陸', cancelButtonText: '取消', type: 'warning'}).then(() => { router.replace({path: loginUrl, query: {redirect: router.currentRoute.fullPath}}) }) break case 403: MessageBox.alert('沒有權限!') break // ...忽略 default: MessageBox.alert(`鏈接錯誤${error.response.status}`) } return Promise.resolve(error.response) } return Promise.resolve(error) }) 複製代碼
這裏作的處理分別是會話已失效和登錄超時,具體的須要根據業務來做變動。vue
最後是導出基礎請求類型封裝。java
export default { get (url, param) { if (param !== undefined) { Object.assign(param, {_t: (new Date()).getTime()}) } else { param = {_t: (new Date()).getTime()} } return axios({method: 'get', url, params: param}) }, // 不常更新的數據用這個 getData (url, param) { return axios({method: 'get', url, params: param}) }, post (url, param, config) { return axios.post(url, param, config) }, put: axios.put, _delete: axios.delete } 複製代碼
其中給get
請求加上時間戳參數,避免從緩存中拿數據。 除了基礎請求類型,還有不少相似下載、上傳這種,須要特殊的的請求頭,此時能夠根據自身需求進行封裝。node
瀏覽器緩存是基於url進行緩存的,若是頁面容許緩存,則在必定時間內(緩存時效時間前)再次訪問相同的URL,瀏覽器就不會再次發送請求到服務器端,而是直接從緩存中獲取指定資源。webpack
import http from '@/utils/request' export default { A (param) { return http.get('/api/', param) }, B (param) { return http.post('/api/', param) } C (param) { return http.put('/api/', param) }, D (param) { return http._delete('/api/', {data: param}) }, } 複製代碼
utils/api/index.js
:ios
import http from '@/utils/request' import account from './account' // 忽略... const api = Object.assign({}, http, account, \*...其它模塊*\) export default api 複製代碼
在global.js
中引入:nginx
import Vue from 'vue' import api from './api/index' // 略... const errorHandler = (error, vm) => { console.error(vm) console.error(error) } Vue.config.errorHandler = errorHandler export default { install (Vue) { // 添加組件 // 添加過濾器 }) // 全局報錯處理 Vue.prototype.$throw = (error) => errorHandler(error, this) Vue.prototype.$http = api // 其它配置 } } 複製代碼
寫接口的時候就能夠簡化爲:
async getData () { const params = {/*...key : value...*/} let res = await this.$http.A(params) res.code === 4000 ? (this.aSata = res.data) : this.$message.warning(res.msg) } 複製代碼
咱們寫組件的時候一般須要引入另外的組件:
<template> <BaseInput v-model="searchText" @keydown.enter="search"/> <BaseButton @click="search"> <BaseIcon name="search"/> </BaseButton> </template> <script> import BaseButton from './baseButton' import BaseIcon from './baseIcon' import BaseInput from './baseInput' export default { components: { BaseButton, BaseIcon, BaseInput } } </script> 複製代碼
寫小項目這麼引入還好,但等項目一臃腫起來...嘖嘖。 這裏是藉助webpack
,使用 require.context()
方法來建立本身的模塊上下文,從而實現自動動態require
組件。
這個方法須要3個參數:
在你放基礎組件的文件夾根目錄下新建componentRegister.js
:
import Vue from 'vue' /** * 首字母大寫 * @param str 字符串 * @example heheHaha * @return {string} HeheHaha */ function capitalizeFirstLetter (str) { return str.charAt(0).toUpperCase() + str.slice(1) } /** * 對符合'xx/xx.vue'組件格式的組件取組件名 * @param str fileName * @example abc/bcd/def/basicTable.vue * @return {string} BasicTable */ function validateFileName (str) { return /^\S+\.vue$/.test(str) && str.replace(/^\S+\/(\w+)\.vue$/, (rs, $1) => capitalizeFirstLetter($1)) } const requireComponent = require.context('./', true, /\.vue$/) // 找到組件文件夾下以.vue命名的文件,若是文件名爲index,那麼取組件中的name做爲註冊的組件名 requireComponent.keys().forEach(filePath => { const componentConfig = requireComponent(filePath) const fileName = validateFileName(filePath) const componentName = fileName.toLowerCase() === 'index' ? capitalizeFirstLetter(componentConfig.default.name) : fileName Vue.component(componentName, componentConfig.default || componentConfig) }) 複製代碼
最後咱們在main.js
中
import 'components/componentRegister.js'
咱們就能夠隨時隨地使用這些基礎組件,無需手動引入了。
咱們寫單頁面應用,想看頁面修改後性能變動其實挺繁瑣的。有時想知道是「正優化」仍是「負優化」只能靠手動刷新查看network
。而Hiper
很好解決了這一痛點(其實Hiper
是後臺靜默運行Chromium
來實現無感調試)。
咱們開發完一個項目或者給一個項目作完性能優化之後,如何來衡量這個項目的性能是否達標?
咱們的常見方式是在Dev Tool
中的performance
和network
中看數據,記錄下幾個關鍵的性能指標,而後刷新幾回再看這些性能指標。
有時候咱們發現,因爲樣本太少,受當前「網絡」、「CPU」、「內存」的繁忙程度的影響很重,有時優化後的項目反而比優化前更慢。
若是有一個工具,一次性地請求N次網頁,而後把各個性能指標取出來求平均值,咱們就能很是準確地知道這個優化是「正優化」仍是「負優化」。
而且,也能夠作對比,拿到「具體優化了多少」的準確數據。這個工具就是爲了解決這個痛點的。
sudo npm install hiper -g # 或者使用 yarn: # sudo yarn global add hiper 複製代碼
Key | Value |
---|---|
DNS查詢耗時 | domainLookupEnd - domainLookupStart |
TCP鏈接耗時 | connectEnd - connectStart |
第一個Byte到達瀏覽器的用時 | responseStart - requestStart |
頁面下載耗時 | responseEnd - responseStart |
DOM Ready以後又繼續下載資源的耗時 | domComplete - domInteractive |
白屏時間 | domInteractive - navigationStart |
DOM Ready 耗時 | domContentLoadedEventEnd - navigationStart |
頁面加載總耗時 | loadEventEnd - navigationStart |
developer.mozilla.org/zh-CN/docs/…
# 當咱們省略協議頭時,默認會在url前添加`https://` # 最簡單的用法 hiper baidu.com # 如何url中含有任何參數,請使用雙引號括起來 hiper "baidu.com?a=1&b=2" # 加載指定頁面100次 hiper -n 100 "baidu.com?a=1&b=2" # 禁用緩存加載指定頁面100次 hiper -n 100 "baidu.com?a=1&b=2" --no-cache # 禁JavaScript加載指定頁面100次 hiper -n 100 "baidu.com?a=1&b=2" --no-javascript # 使用GUI形式加載指定頁面100次 hiper -n 100 "baidu.com?a=1&b=2" -H false # 使用指定useragent加載網頁100次 hiper -n 100 "baidu.com?a=1&b=2" -u "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36" 複製代碼
此外,還能夠配置Cookie
訪問
module.exports = { .... cookies: [{ name: 'token', value: process.env.authtoken, domain: 'example.com', path: '/', httpOnly: true }], .... } 複製代碼
# 載入上述配置文件(假設配置文件在/home/下) hiper -c /home/config.json # 或者你也可使用js文件做爲配置文件 hiper -c /home/config.js 複製代碼
咱們經常使用的<transition>
和<keep-alive>
就是一個高階(抽象)組件。
export default { name: 'keep-alive', abstract: true, ... } 複製代碼
全部的高階(抽象)組件是經過定義abstract
選項來聲明的。高階(抽象)組件不渲染真實DOM
。 一個常規的抽象組件是這麼寫的:
import { xxx } from 'xxx' const A = () => { ..... } export default { name: 'xxx', abstract: true, props: ['...', '...'], // 生命週期鉤子函數 created () { .... }, .... destroyed () { .... }, render() { const vnode = this.$slots.default .... return vnode }, }) 複製代碼
關於防抖和節流是啥就不贅述了。這裏貼出組件代碼:
改編自:Vue實現函數防抖組件
const throttle = function(fn, wait=50, isDebounce, ctx) { let timer let lastCall = 0 return function (...params) { if (isDebounce) { if (timer) clearTimeout(timer) timer = setTimeout(() => { fn.apply(ctx, params) }, wait) } else { const now = new Date().getTime() if (now - lastCall < wait) return lastCall = now fn.apply(ctx, params) } } } export default { name: 'Throttle', abstract: true, props: { time: Number, events: String, isDebounce: { type: Boolean, default: false }, }, created () { this.eventKeys = this.events.split(',') this.originMap = {} this.throttledMap = {} }, render() { const vnode = this.$slots.default[0] this.eventKeys.forEach((key) => { const target = vnode.data.on[key] if (target === this.originMap[key] && this.throttledMap[key]) { vnode.data.on[key] = this.throttledMap[key] } else if (target) { this.originMap[key] = target this.throttledMap[key] = throttle(target, this.time, this.isDebounce, vnode) vnode.data.on[key] = this.throttledMap[key] } }) return vnode }, }) 複製代碼
經過第三個參數isDebounce
來控制切換防抖節流。 最後在main.js
裏引用:
import Throttle from '../Throttle' .... Vue.component('Throttle', Throttle) 複製代碼
<div id="app"> <Throttle :time="1000" events="click"> <button @click="onClick($event, 1)">click+1 {{val}}</button> </Throttle> <Throttle :time="1000" events="click" :isDebounce="true"> <button @click="onAdd">click+3 {{val}}</button> </Throttle> <Throttle :time="3300" events="mouseleave" :isDebounce="true"> <button @mouseleave.prevent="onAdd">click+3 {{val}}</button> </Throttle> </div> 複製代碼
const app = new Vue({ el: '#app', data () { return { val: 0 } }, methods: { onClick ($ev, val) { this.val += val }, onAdd () { this.val += 3 } } }) 複製代碼
抽象組件是一個接替Mixin實現抽象組件公共功能的好方法,不會由於組件的使用而污染DOM(添加並不想要的div標籤等)、能夠包裹任意的單一子元素等等
至於用不用抽象組件,就見仁見智了。
中央事件總線eventBus的實質就是建立一個vue實例,經過一個空的vue實例做爲橋樑實現vue組件間的通訊。它是實現非父子組件通訊的一種解決方案。
而eventBus
實現也很是簡單
import Vue from 'Vue' export default new Vue 複製代碼
咱們在使用中常常最容易忽視,又必然不能忘記的東西,那就是:清除事件總線eventBus
。
不手動清除,它是一直會存在,這樣當前執行時,會反覆進入到接受數據的組件內操做獲取數據,本來只執行一次的獲取的操做將會有屢次操做。原本只會觸發並只執行一次,變成了屢次,這個問題就很是嚴重。
當不斷進行操做幾分鐘後,頁面就會卡頓,並佔用大量內存。
因此通常在vue生命週期beforeDestroy
或者destroyed
中,須要用vue實例的$off
方法清除eventBus
beforeDestroy(){ bus.$off('click') } 複製代碼
可當你有多個eventBus
時,就須要重複性勞動$off
銷燬這件事兒。 這時候封裝一個 eventBus
就是更優的解決方案。
eventBus
咱們從Vue源碼Vue.init
中能夠得知:
Vue.prototype._init = function (options?: Object) { const vm: Component = this // a uid vm實例惟一標識 vm._uid = uid++ // .... } 複製代碼
每一個Vue實例有本身的_uid
做爲惟一標識,所以咱們讓EventBus
和_uid
關聯起來,並將其改造:
class EventBus { constructor (vue) { if (!this.handles) { Object.defineProperty(this, 'handles', { value: {}, enumerable: false }) } this.Vue = vue // _uid和EventName的映射 this.eventMapUid = {} } setEventMapUid (uid, eventName) { if (!this.eventMapUid[uid]) this.eventMapUid[uid] = [] this.eventMapUid[uid].push(eventName) // 把每一個_uid訂閱的事件名字push到各自uid所屬的數組裏 } $on (eventName, callback, vm) { // vm是在組件內部使用時組件當前的this用於取_uid if (!this.handles[eventName]) this.handles[eventName] = [] this.handles[eventName].push(callback) if (vm instanceof this.Vue) this.setEventMapUid(vm._uid, eventName) } $emit () { let args = [...arguments] let eventName = args[0] let params = args.slice(1) if (this.handles[eventName]) { let len = this.handles[eventName].length for (let i = 0; i < len; i++) { this.handles[eventName][i](...params) } } } $offVmEvent (uid) { let currentEvents = this.eventMapUid[uid] || [] currentEvents.forEach(event => { this.$off(event) }) } $off (eventName) { delete this.handles[eventName] } } // 寫成Vue插件形式,直接引入而後Vue.use($EventBus)進行使用 let $EventBus = {} $EventBus.install = (Vue, option) => { Vue.prototype.$eventBus = new EventBus(Vue) Vue.mixin({ beforeDestroy () { // 攔截beforeDestroy鉤子自動銷燬自身全部訂閱的事件 this.$eventBus.$offVmEvent(this._uid) } }) } export default $EventBus 複製代碼
使用:
// main.js中 ... import EventBus from './eventBus.js' Vue.use(EnemtBus) ... 複製代碼
組件中使用:
created () { let text = Array(1000000).fill('xxx').join(',') this.$eventBus.$on('home-on', (...args) => { console.log('home $on====>>>', ...args) this.text = text }, this) // 注意第三個參數須要傳當前組件的this,若是不傳則須要手動銷燬 }, mounted () { setTimeout(() => { this.$eventBus.$emit('home-on', '這是home $emit參數', 'ee') }, 1000) }, beforeDestroy () { // 這裏就不須要手動的off銷燬eventBus訂閱的事件了 } 複製代碼
uglifyjs
的Terser Plugin
在二月初項目升級Vue-cli3時遇到了一個問題:uglifyjs
再也不支持webpack4.0。找了一圈,在Google
搜索裏查到Terser Plugin
這個插件。
我主要用到了其中這幾個功能:
cache
,啓用文件緩存。parallel
,使用多進程並行來提升構建速度。sourceMap
,將錯誤消息位置映射到模塊(儲存着位置信息)。drop_console
,打包時剔除全部的console
語句drop_debugger
,打包時剔除全部的debugger
語句做爲一個管小組前端的懶B,不少時候寫頁面會遺留console.log
,影響性能。設置個drop_console
就很是香。如下配置親測有效。
const TerserPlugin = require('terser-webpack-plugin') .... new TerserPlugin({ cache: true, parallel: true, sourceMap: true, // Must be set to true if using source-maps in production terserOptions: { compress: { drop_console: true, drop_debugger: true } } }) 複製代碼
更多的配置請看Terser Plugin
開啓gzip壓縮的好處是什麼?
能夠減少文件體積,傳輸速度更快。gzip是節省帶寬和加快站點速度的有效方法。
Webpack
開啓gzip
這裏使用的插件爲:CompressionWebpackPlugin
const CompressionWebpackPlugin = require('compression-webpack-plugin') module.exports = { 「plugins」:[new CompressionWebpackPlugin] } 複製代碼
具體配置:
const CompressionWebpackPlugin = require('compression-webpack-plugin'); webpackConfig.plugins.push( new CompressionWebpackPlugin({ asset: '[path].gz[query]', algorithm: 'gzip', test: new RegExp('\\.(js|css)$'), // 只處理大於xx字節 的文件,默認:0 threshold: 10240, // 示例:一個1024b大小的文件,壓縮後大小爲768b,minRatio : 0.75 minRatio: 0.8 // 默認: 0.8 // 是否刪除源文件,默認: false deleteOriginalAssets: false }) ) 複製代碼
Nginx
的gzip
設置打開/etc/nginx/conf.d
編寫如下配置。
server {
gzip on;
gzip_static on;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
gzip_proxied any;
gzip_vary on;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
...
}
複製代碼
Nginx
嘗試查找併發送文件/path/to/bundle.js.gz
。若是該文件不存在,或者客戶端不支持 gzip
,Nginx則會發送該文件的未壓縮版本。
保存配置後,從新啓動Nginx
:
$ sudo service nginx restart
複製代碼
gzip
?經過使用curl
測試每一個資源的請求響應,並檢查Content-Encoding
:
顯示 Content-Encoding: gzip
,即爲配置成功。
不一樣之處在於:
Webpack
壓縮會在構建運行期間一次壓縮文件,而後將這些壓縮版本保存到磁盤。
nginx
在請求時壓縮文件時,某些包可能內置了緩存,所以性能損失只發生一次(或不常常),但一般不一樣之處在於,這將在響應 HTTP請求時發生。
對於實時壓縮,讓上游代理(例如 Nginx)處理 gzip和緩存一般更高效,由於它們是專門爲此而構建的,而且不會遭受服務端程序運行時的開銷(許多都是用C語言編寫的) 。
使用 Webpack的
好處是, Nginx
每次請求服務端都要壓縮好久纔回返回信息回來,不只服務器開銷會增大不少,請求方也會等的不耐煩。咱們在 Webpack
打包時就直接生成高壓縮等級的文件,做爲靜態資源放在服務器上,這時將 Nginx
做爲二重保障就會高效不少(請求其它目錄資源時)。
注:具體是在請求時實時壓縮,或在構建時去生成壓縮文件,就要看項目業務狀況。
原本還想謝謝動態配置表單相關,但篇幅太長也太難寫了。
好了,又水完一篇,入正題:
目前本人在(又)準備跳槽,但願各位大佬和HR小姐姐能夠內推一份靠譜的深圳前端崗位!996.ICU
就算了。
huab119
454274033@qq.com