摘要:所有的高階抽象組件是通過定義選項來聲明的。所以一般在生命周期或者中,需要用實例的方法清除可當你有多個時,就需要重復性勞動銷毀這件事兒。更多的配置請看雙端開啟開啟壓縮的好處是什么可以減小文件體積,傳輸速度更快。
本文目錄接口模塊處理
Vue組件動態注冊
頁面性能調試:Hiper
Vue高階組件封裝
性能優化:eventBus封裝
webpack插件:真香
本文項目基于Vue-Cli3,想知道如何正確搭建請看我之前的文章:
「Vue實踐」項目升級vue-cli3的正確姿勢
1. 接口模塊處理 1.1 axios二次封裝
這里封裝的依據是后臺傳的JWT,已封裝好的請跳過。
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)
})
這里做的處理分別是會話已失效和登陸超時,具體的需要根據業務來作變更。
最后是導出基礎請求類型封裝。
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請求加上時間戳參數,避免從緩存中拿數據。 除了基礎請求類型,還有很多類似下載、上傳這種,需要特殊的的請求頭,此時可以根據自身需求進行封裝。
1.2 請求按模塊合并瀏覽器緩存是基于url進行緩存的,如果頁面允許緩存,則在一定時間內(緩存時效時間前)再次訪問相同的URL,瀏覽器就不會再次發送請求到服務器端,而是直接從緩存中獲取指定資源。
模塊的請求:
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:
import http from "@/utils/request"
import account from "./account"
// 忽略...
const api = Object.assign({}, http, account, *...其它模塊*)
export default api
1.3 global.js中的處理
在global.js中引入:
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 ");$message.warning(res.msg)
}
2. 基礎組件自動化全局注冊
來自 @SHERlocked93:Vue 使用中的小技巧
官方文檔:基礎組件的自動化全局注冊
我們寫組件的時候通常需要引入另外的組件:
"searchText" @keydown.enter="search"/>
"search">
"search"/>
寫小項目這么引入還好,但等項目一臃腫起來...嘖嘖。 這里是借助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"
");
最后我們在main.js中
import "components/componentRegister.js"
我們就可以隨時隨地使用這些基礎組件,無需手動引入了。
3. 頁面性能調試:Hiper我們寫單頁面應用,想看頁面修改后性能變更其實挺繁瑣的。有時想知道是「正優化」還是「負優化」只能靠手動刷新查看network。而Hiper很好解決了這一痛點(其實Hiper是后臺靜默運行Chromium來實現無感調試)。
Hiper官方文檔
我們開發完一個項目或者給一個項目做完性能優化以后,如何來衡量這個項目的性能是否達標?
我們的常見方式是在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");
# 加載指定頁面100次
hiper -n 100 "baidu.com");
# 禁用緩存加載指定頁面100次
hiper -n 100 "baidu.com"); --no-cache
# 禁JavaScript加載指定頁面100次
hiper -n 100 "baidu.com"); --no-javascript
# 使用GUI形式加載指定頁面100次
hiper -n 100 "baidu.com"); -H false
# 使用指定useragent加載網頁100次
hiper -n 100 "baidu.com"); -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
4. Vue高階組件封裝
我們常用的
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
},
})
4.1 防抖/節流 抽象組件
關于防抖和節流是啥就不贅述了。這里貼出組件代碼:
改編自: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)
使用方式
"app">
"1000" events="click">
"1000" events="click" :isDebounce="true">
"3300" events="mouseleave" :isDebounce="true">
const app = new Vue({
el: "#app",
data () {
return {
val: 0
}
},
methods: {
onClick ($ev, val) {
this.val += val
},
onAdd () {
this.val += 3
}
}
})
抽象組件是一個接替Mixin實現抽象組件公共功能的好方法,不會因為組件的使用而污染DOM(添加并不想要的div標簽等)、可以包裹任意的單一子元素等等
至于用不用抽象組件,就見仁見智了。
5. 性能優化:eventBus封裝中央事件總線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就是更優的解決方案。
5.1 擁有生命周期的 eventBus我們從Vue源碼Vue.init中可以得知:
Vue.prototype._init = function (options");
每個Vue實例有自己的_uid作為唯一標識,因此我們讓EventBus和_uid關聯起來,并將其改造:
實現來自:讓在Vue中使用的EventBus也有生命周期
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訂閱的事件了
}
6. webpack插件:真香
6.1 取代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
6.2 雙端開啟 gzip
開啟gzip壓縮的好處是什么?
可以減小文件體積,傳輸速度更快。gzip是節省帶寬和加快站點速度的有效方法。
服務端發送數據時可以配置 Content-Encoding:gzip,用戶說明數據的壓縮方式
客戶端接受到數據后去檢查對應字段的信息,就可以根據相應的格式去解碼。
客戶端請求時,可以用 Accept-Encoding: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
})
)
打開/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
通過使用curl測試每個資源的請求響應,并檢查Content-Encoding:
顯示 Content-Encoding: gzip,即為配置成功。
6.2.4 雙端Gzip區別及其意義不同之處在于:
Webpack壓縮會在構建運行期間一次壓縮文件,然后將這些壓縮版本保存到磁盤。
nginx在請求時壓縮文件時,某些包可能內置了緩存,因此性能損失只發生一次(或不經常),但通常不同之處在于,這將在響應 HTTP請求時發生。
對于實時壓縮,讓上游代理(例如 Nginx)處理 gzip和緩存通常更高效,因為它們是專門為此而構建的,并且不會遭受服務端程序運行時的開銷(許多都是用C語言編寫的) 。
使用 Webpack的好處是, Nginx每次請求服務端都要壓縮很久才回返回信息回來,不僅服務器開銷會增大很多,請求方也會等的不耐煩。我們在 Webpack打包時就直接生成高壓縮等級的文件,作為靜態資源放在服務器上,這時將 Nginx作為二重保障就會高效很多(請求其它目錄資源時)。
注:具體是在請求時實時壓縮,或在構建時去生成壓縮文件,就要看項目業務情況。
本來還想謝謝動態配置表單相關,但篇幅太長也太難寫了。
好了,又水完一篇,入正題:
目前本人在(又)準備跳槽,希望各位大佬和HR小姐姐可以內推一份靠譜的深圳前端崗位!996.ICU 就算了。
微信:huab119
郵箱:454274033@qq.com
作者掘金文章總集「Vue實踐」5分鐘擼一個Vue CLI 插件
「Vue實踐」武裝你的前端項目
「中高級前端面試」JavaScript手寫代碼無敵秘籍
「從源碼中學習」面試官都不知道的Vue題目答案
「從源碼中學習」Vue源碼中的JS騷操作
「從源碼中學習」徹底理解Vue選項Props
「Vue實踐」項目升級vue-cli3的正確姿勢
為何你始終理解不了JavaScript作用域鏈?
公眾號
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/7332.html
摘要:更多資源請文章轉自月份前端資源分享的作用數組元素隨機化排序算法實現學習筆記數組隨機排序個變態題解析上個變態題解析下中的數字前端開發筆記本過目不忘正則表達式聊一聊前端存儲那些事兒一鍵分享到各種寫給剛入門的前端工程師的前后端交互指南物聯網世界的 更多資源請Star:https://github.com/maidishike... 文章轉自:https://github.com/jsfr...
摘要:沒想到老師下課點名了,老師李。一哥們答到老師咦,李,你站起來一下這哥們低著腦袋站起來了,老師表格上面寫的李是個女的啊這是不知道從哪個角落傳來一聲討厭啊,人家就是個女的了啦。 SpliderApi https://github.com/ecitlm/Spl... 基于nodejs 的爬蟲 API接口項目,包括前端開發日報、知乎日報、前端top框架排行、妹紙福利、搞笑視頻/ 熱點新聞資訊 ...
摘要:一些知識點有哪些方法方法前端從入門菜鳥到實踐老司機所需要的資料與指南合集前端掘金前端從入門菜鳥到實踐老司機所需要的資料與指南合集歸屬于筆者的前端入門與最佳實踐。 工欲善其事必先利其器-前端實習簡歷篇 - 掘金 有幸認識很多在大廠工作的學長,在春招正式開始前為我提供很多內部推薦的機會,非常感謝他們對我的幫助。現在就要去北京了,對第一份正式的實習工作也充滿期待,也希望把自己遇到的一些問題和...
摘要:一個合格的中級前端工程師必須要掌握的個技巧對在畢業季還在找工作的同學,可以說很實用的,如果能全部掌握這些技巧,拿到幾個應該是沒有問題的,當然要注意平時的積累,能力要全面。而且該托管服務是完全免費的。 showImg(https://segmentfault.com/img/remote/1460000019376228?w=1790&h=898); 【阿里云 TXD 前端月刊】- 熱門...
閱讀 3064·2021-10-12 10:20
閱讀 2809·2021-09-27 13:56
閱讀 790·2021-09-27 13:36
閱讀 1424·2021-09-26 09:46
閱讀 2417·2019-08-30 14:02
閱讀 2685·2019-08-28 18:14
閱讀 1257·2019-08-26 10:32
閱讀 1700·2019-08-23 18:25