前端代碼Review主要關注以下幾個方面:
1、代碼質量:代碼是否簡潔、易讀、易維護。是否遵循了一致的編碼風格和規范,例如ESLint、Prettier等。
2、功能實現:代碼是否實現了預期的功能,是否有潛在的bug或邏輯錯誤。
3、性能優化:代碼是否進行了必要的性能優化,例如避免不必要的渲染、使用了合適的數據結構和算法等。
4、安全性:代碼是否存在可能的安全風險,例如XSS攻擊、CSRF攻擊等。5、可測試性:代碼是否易于測試,是否有單元測試和集成測試。
6、可讀性:代碼是否易于理解,是否有足夠的注釋和文檔。
7、可復用性:代碼是否有高度的模塊化和復用性。
8、兼容性:代碼是否兼容不同的瀏覽器和設備。
助記:功性讀復全兼測
前端代碼中一些典型的、肉眼可見的需要性能優化的地方包括:
1、過度渲染:如果你的應用在沒有必要的情況下進行了過多的渲染,這可能會導致性能問題。例如,在React中,如果你的組件在props沒有改變的情況下進行了重新渲染,那么你可能需要使用shouldComponentUpdate/SCU或React.PureComponent或useEffect來避免不必要的渲染。
2、大量DOM操作:頻繁的DOM操作是非常耗費性能的。如果你的代碼中有大量的DOM操作,你可能需要考慮使用虛擬DOM或其他優化技術(例如DocumentFragment)。
3、大量的網絡請求:如果你的應用發送了大量的網絡請求,這可能會導致性能問題。你可能需要考慮使用緩存、預加載、懶加載等技術來減少網絡請求。
4、大型JavaScript文件:如果你的JavaScript文件過大,這可能會導致加載和解析時間過長。你可能需要考慮使用代碼分割、懶加載等技術來減小文件大小。
5、未優化的圖片和媒體文件:如果你的網站使用了大量的未優化的圖片和媒體文件,這可能會導致加載時間過長。你可能需要考慮使用圖片壓縮、適當的文件格式、CDN等技術來優化你的媒體文件。
6、阻塞渲染的CSS和JavaScript:如果你的CSS和JavaScript阻塞了頁面的渲染,這可能會導致用戶體驗不佳。你可能需要考慮使用非阻塞的加載技術,如異步加載、延遲加載等。
阻塞渲染的CSS和JavaScript是一個常見的前端性能優化問題。當瀏覽器加載網頁時,它會按照HTML文檔的順序解析每個元素。當遇到<link>或<script>標簽時,瀏覽器會停止HTML解析,去下載和執行CSS或JavaScript文件,這就是所謂的“阻塞渲染”。
以下是一些優化阻塞渲染的CSS和JavaScript的策略:
1、異步加載JavaScript:使用async屬性可以使瀏覽器異步加載JavaScript,即在下載JavaScript文件的同時,瀏覽器可以繼續解析HTML。但是,這可能會導致JavaScript在DOM還未完全解析時就開始執行,因此只適用于那些不依賴DOM的JavaScript文件。
2、延遲加載JavaScript:使用defer屬性可以使瀏覽器延遲執行JavaScript,即在DOM解析完畢后,再執行JavaScript。這適用于那些依賴DOM的JavaScript文件。
3、內聯關鍵CSS:將關鍵的CSS(即渲染首屏內容所需的CSS)內聯到HTML中,可以避免瀏覽器等待CSS文件的下載和解析。但是,這可能會增加HTML文件的大小,因此只適用于小量的CSS。
4、媒體查詢:使用媒體查詢可以讓瀏覽器只下載適用于當前設備的CSS,從而減少不必要的下載;本質是一種CSS的按需加載技術
以上就是優化阻塞渲染的CSS和JavaScript的一些策略。
關注一下以下要點:
正在review的這段代碼可復用性如何,高的話是否可以:
在前端代碼的安全性Review中,主要關注以下幾個方面:
1、跨站腳本攻擊(XSS):檢查代碼中是否正確地對用戶輸入進行了轉義,以防止插入惡意的JavaScript代碼。例如,當使用React時,它默認會轉義所有的用戶輸入,但如果你使用dangerouslySetInnerHTML(或Vue中的v-html),則需要特別小心。
2、跨站請求偽造(CSRF):檢查是否在所有的POST請求中使用了CSRF令牌,以防止攻擊者偽造用戶的請求。
3、點擊劫持:檢查是否使用了適當的HTTP頭,如X-Frame-Options,來防止點擊劫持攻擊。
4、內容篡改:檢查是否所有的HTTP請求都使用了HTTPS,以防止混合內容問題,這可能會導致用戶的數據被竊取(傳輸過程中被抓包)。
5、輸入驗證:檢查是否在客戶端和服務器端都進行了輸入驗證,以防止注入攻擊,圖形或字符驗證碼、短信驗證、CSRF-token等的本質都是加入隨機真人校驗機制,狠重要!
6、敏感信息泄露:檢查代碼中是否有可能泄露敏感信息的地方,如在URL、錯誤消息或日志中包含敏感信息。
7、依賴的安全性:檢查所有的第三方依賴是否都是最新的,是否有已知的安全漏洞。
X-Frame-Options 是一個HTTP響應頭,用于控制網頁是否可以被其他網頁通過、或等元素嵌入。這個響應頭的主要目的是為了防止點擊劫持/Clickjacking攻擊。
X-Frame-Options有三個可能的值:
1. DENY:此頁面不能被嵌入到任何其他頁面中。
2. SAMEORIGIN:只有同源的頁面(即,URL的協議、域名和端口都相同)才能嵌入此頁面。
3. ALLOW-FROM uri:只有指定的頁面可以嵌入此頁面。但是,這個值已經被廢棄,不再被大多數現代瀏覽器支持。
如果沒有設置X-Frame-Options頭,或者設置的值不是上述三個值之一,那么任何頁面都可以嵌入此頁面。因此,為了防止點擊劫持攻擊,建議總是設置X-Frame-Options頭。
在前端代碼的兼容性Review中,主要關注以下幾個方面:
1、瀏覽器兼容性:檢查代碼是否能在所有支持的瀏覽器中正常工作。這包括檢查是否使用了某些瀏覽器可能不支持的JavaScript特性或CSS屬性,以及是否正確地使用了polyfill和前綴。
2、設備兼容性:檢查代碼是否能在所有支持的設備中正常工作,包括不同的操作系統、屏幕大小和分辨率。
3、響應式設計:檢查代碼是否適應了不同的屏幕大小,包括手機、平板和桌面。
4、無障礙性:檢查代碼是否遵循了無障礙性標準,如WAI-ARIA,以確保所有用戶,包括那些使用輔助技術的用戶,都能使用你的應用。
5、國際化和本地化:檢查代碼是否支持多種語言和地區,包括正確地使用了日期、時間和數字的格式。
6、性能:檢查代碼在低性能的設備或網絡環境下是否還能正常工作。
即充分考慮不同瀏覽器、不同設備、不同屏幕、不同國家、不同人群。
以上就是配置polyfill的一個示例。需要注意的是,這只是一個基本的配置,實際的配置可能會更復雜,取決于你的具體需求和環境。
在前端代碼的可測試性Review中,主要關注以下幾個方面:
1、單元測試:檢查代碼是否易于進行單元測試。這包括檢查函數是否是純函數(即,相同的輸入總是產生相同的輸出,沒有副作用),以及是否避免了全局狀態。
2、集成測試:檢查代碼是否易于進行集成測試。這包括檢查組件是否正確地使用了props和state,以及是否避免了直接操作DOM。
3、端到端測試:檢查代碼是否易于進行端到端測試。這包括檢查頁面是否有易于定位的元素(如有特定id或data-test屬性的元素),以及是否有清晰的用戶流程。
4、測試覆蓋率:檢查代碼的測試覆蓋率是否足夠。這包括檢查是否所有的函數和分支都有對應的測試。
5、模擬和打樁:檢查代碼是否易于進行模擬和打樁。這包括檢查函數是否避免了直接調用復雜的依賴(如網絡請求或數據庫操作),以及是否提供了足夠的接口來替換這些依賴。
6、錯誤處理:檢查代碼是否正確地處理了可能的錯誤。這包括檢查是否有錯誤處理函數或catch塊,以及是否有對應的錯誤測試。
1、安裝Cypress:首先,你需要在你的項目中安裝Cypress。你可以使用npm或yarn來安裝:
2、編寫測試:然后,你可以在cypress/integration目錄下編寫你的E2E測試。以下是一個簡單的測試示例,它測試了用戶是否能正確地登錄:
3、運行測試:最后,你可以使用Cypress的CLI來運行你的測試:
這將會打開Cypress的測試運行器,你可以在這里選擇你要運行的測試。
以上就是在React應用中進行E2E測試的基本步驟。需要注意的是,這只是一個基本的示例,實際的E2E測試可能會更復雜,包括測試更多的用戶交互和應用狀態。
功性讀復全兼測! OVER,收工干飯!
原文鏈接:https://juejin.cn/post/7283748394601840680
者:magentaqin,騰訊前端開發工程師
說到 Code Review,經常有同學會問,究竟從哪些方面下手?除了一些抽象的 Review 原則,有沒有更細化的實施準則來指導實踐?
PCG 代碼委員會曾推出過通道晉級代碼檢查報告。筆者打算在這些報告基礎上,從代碼格式、代碼錯誤、代碼習慣、代碼優化四個角度,并結合騰訊醫典前端 Code Review 過程中遇到的一些 bad case,逐一列出更細化的實施準則。希望對各位有一定的參考價值。
代碼格式問題完全可以通過自動化工具來解決。 標準的 eslint 規則( 如Airbnb或公司統一推出的eslint規則) + husky( 本地pre-commit校驗 ) + 遠端 CI 流水線 eslint 校驗(開啟cache,增量校驗)就可以解決。
對于 SPA 應用,用戶無需刷新瀏覽器,所以要想確保垃圾回收生效,我們需要在組件對應生命周期里做主動銷毀。
全局變量,除非你關閉窗口或者刷新頁面,才會被釋放,如果緩存大量數據,很可能導致內存泄露。 比如,我們之前就遇到過把 IM SDK放在全局window上,但在頁面卸載時卻沒有解除引用。
mounted () {
window.im=TWebLive.createIM({ SDKAppID });
}
解決方案:在頁面卸載時解除該全局引用。
destroyed () {
window.im=null;
}
其實該 im 實例也不需要掛在window上,直接綁定在vue實例上即可,組件銷毀時該實例也會銷毀;但沒有綁定在vue實例上的一定要主動銷毀。
來看一個容易忽視的閉包引發內存泄漏的例子。 outer函數內部定義了兩個函數: unused 和 foo。雖然inner函數中并沒有使用outer函數中的變量,但是由于unsed函數使用了outer函數的bar變量,bar也不會被釋放,所以foo相當于隱式持有了bar。每次執行outer,bar都會指向上一次的foo;而foo也會隱式持有bar,這樣的引用關系導致bar和foo都無法釋放。
let foo=null;
function outer() {
let bar=foo;
// 該函數歷史原因,調用方被注釋掉。并無調用
function unused () {
doSomething();
console.log(`unused ${bar}`)
}
// foo賦值
foo={
bigData: new Array(10000),
inner: function () {
doSomething();
}
}
}
for (let i=0; i < 1000; i++) {
outer();
}
解決方案:在 outer 執行完畢時手動釋放bar。這樣,隱式持有 bar 的 foo 也沒有其他變量引用,也會被回收了。
let foo=null;
function outer() {
let bar=foo;
// 該函數歷史原因,調用方被注釋掉。并無調用
function unused () {
doSomething();
console.log(`unused ${bar}`)
}
// foo賦值
foo={
bigData: new Array(10000),
inner: function () {
doSomething();
}
}
bar=null; // 手動釋放bar
}
for (let i=0; i < 1000; i++) {
outer();
}
常見的情況是mounted設置了定時任務,但卻沒有及時清理。
mounted () {
this.timer=setTimeout(()=> {
doSomething();
}, 300)
}
參考寫法,頁面銷毀時需清理定時器:
destroyed () {
if (this.timer) {
clearTimeout(this.timer)
}
}
window/body 等事件需要解綁:
mounted() {
window.addEventListener(‘resize’, this.func)
}
beforeDestroy () {
window.removeEventListener('resize', this.func);
}
destroyed () {
this.eventBus.off()
}
拿 vue 官網避免內存泄漏 的例子來看下。
v-if 指令只是控制虛擬DOM的添加和移除,但是由 Choices.js 添加的 DOM 片段并沒有被移除。
<template>
<div id="app">
<button v-if="showChoices" @click="hide">Hide</button>
<button v-if="!showChoices" @click="show">Show</button>
<div v-if="showChoices">
<select id="choices-single-default"></select>
</div>
</div>
</template>
<script>
new Vue({
el: "#app",
data: function () {
return {
showChoices: true
}
},
mounted: function () {
this.initializeChoices()
},
methods: {
initializeChoices: function () {
let list=[]
for (let i=0; i < 1000; i++) {
list.push({
label: "Item " + i,
value: i
})
}
new Choices("#choices-single-default", {
searchEnabled: true,
removeItemButton: true,
choices: list
})
},
show: function () {
this.showChoices=true
this.$nextTick(()=> {
this.initializeChoices()
})
},
hide: function () {
this.showChoices=false
}
}
})
</script>
解決辦法是在hide方法里調用 Choices.js 的API來清理DOM片段:
hide: function() {
this.choicesSelect.destroy();
}
以下是優化前、后的 JS Heap 對比圖:
異步操作拿接口請求來說,大家都知道的是,使用 promise 時要有.catch 處理。但使用 async/await 時,有.catch 處理的,也有try...catch處理的使用方法。這里推薦使用.catch。原因在于:
// CASE 1: 接口報錯,阻塞業務邏輯執行
async fetchList() {
const res=await someApi().catch(error=> {
// error處理邏輯
})
if (res) {
doA();
}
}
// CASE 2: 接口報錯,不阻塞業務邏輯執行
async fetchList() {
const res=await someApi().catch(error=> {
// error處理邏輯
})
doA();
}
// CASE 3:使用try...catch的情況
async fetchList() {
try {
const res=await someApi()
doA();
} catch (error) {
// 接口請求出錯 + 接口響應成功后業務邏輯處理出錯都會進入catch block。需要進一步區分錯誤類型
if (error.bizcode !==0 || error.retcode !==0) {
reportApiError(error)
} else {
reportBusinessError(error)
}
}
}
拿醫典3月中下旬的錯誤日志來說,這類錯誤在錯誤日志中占了1/3。
如果項目里已經全量使用了Typescript,這類錯誤應該都可以避免。 但如果項目里還存在 js 代碼,可以使用lodash.get來做空判斷,在調用函數之前要對函數做類型判斷。
無意義的if else代碼塊,指的不僅是空的if else 代碼塊,還有只寫了console.log 的情況。 另外,也存在條件判斷過于復雜,else情況考慮不全,導致邏輯沒有正常處理的情況。
// else 代碼塊里只寫了console.log
if (a) {
} else {
console.log('something')
}
// 條件判斷過于復雜,else情況考慮不全,導致邏輯不能正常處理
if ((a && (b || c)) || d || (e && (f || g))) {
} else {
doSomething()
}
解決辦法: 開啟eslint no-empty 規則,不允許有空的block。但這個插件的問題在于,如果是無效的代碼塊,比如在else代碼塊只做了console.log操作,并不會檢測出來。 另外,較為復雜的條件判斷盡量拆成單獨的變量,并分別配上注釋說明,這樣可以防止邏輯處理漏。
和無意義的 else 代碼塊一樣,也存在空catch代碼塊、只有console.log的catch代碼塊的情況。
// bad case
try {
doSomething();
} catch (e) {
}
// bad case
try {
doSomething();
} catch (e) {
console.log(e);
}
// bad case
somePromise().then(()=> {
doSomething()
}).catch(()=> {
})
somePromise().then(()=> {
doSomething()
}).catch((e)=> {
console.log(e)
})
為了解決這個問題,醫典這邊做了一個eslint插件@tencent/eslint-plugin-medical,能夠檢查try catch里的catch代碼塊、promise 的catch代碼塊,是否為空,是否只有console調用。cuow當然,有些時候,并不需要對異常邏輯進行額外業務邏輯處理,catch里可以加一個上報。
這一步可以在流水線里接入安全風險檢測插件進行處理。以下是醫典接入后的一個示例分析
確實存在一些誤判,比如會把mixin關鍵字當做泄露人名來進行告警,開發人員可以對照分析是否需要處理。
An
前端常見的硬編碼場景有:
// 請求參數硬編碼
getCommentsApi({ doctorId: 1 }).then((res)=> {
doSomething()
}).catch(()=> {
handleError();
})
也出現過ABtest接口,開發同學本地模擬返回對象,忘記刪除硬編碼的情況
fetchABTestApi().then((res)=> {
// res.data
const obj={
imgSrc: 'xxx',
name: 'qinmu'
}
})
接口mock的硬編碼,完全可以通過使用mock平臺來解決。推薦使用專業的接口管理平臺來進行接口管理、mock等,這里我們使用的是騰訊內部接口管理平臺tolstoy。該產品還未正式開源,歡迎提前關注。
// bad case 硬編碼1001
const isActive=this.$route.query.id==='1001'
// good case 寫到配置信息中。這樣,id和狀態的對應關系一目了然,便于管理和維護。
const idConfig={
1001: STATUS.ACTIVE
}
const isActive=idConfig[this.$route.query.id]===STATUS.ACTIVE
輸入框的校驗規則除了滿足產品需求,比如多少字符以內、允許哪些字符,還有一個點:前后端需要校驗規則保持一致。最好用統一的正則表達式,不然容易造成前端校驗通過、后端校驗不通過的情況。
上傳文件,前后端需求校驗文件格式、文件大小。尤其是后端,需要對 content-type 為 text/html 的加以限制,防止出現安全問題。我們已經有過此類安全問題的工單了。
拒絕面條代碼,減少代碼中各種結構的嵌套,例如 if-else、try-catch、循環等。盡量控制在三層以內,增加可讀性、降低圈層復雜度。 你肯定不愿意維護這樣辣眼睛的代碼:
async getConfig(id, type='', isReset=false) {
try {
if (liveid) {
const res=await someApi(id);
if (res && res.info) {
const { status }=res.info
status===2 && doA({id, type});
if (status===1 || status===2 || status===4) {
this.setData({
info: res.info,
status,
})
if (isReset) {
this.setData({
current: 0,
index: 0
})
status===2 && doB();
}
return { code: 0};
}
return doC();
} else if (isReset) {
resetSomething();
} else if (isReset && type===someType) {
handleType();
}
return wx.showModal({ //... })
}
} catch (error) {
if (error.code===1001) {
reportA();
} else {
reportB();
doD();
}
}
}
邏輯相同或相似的代碼,應封裝為函數進行調用。
// bad case 都有展示modal的邏輯,開發同學直接復制粘貼
function handleA(msg) {
wx.showModal({
title: '提示',
content: msg,
showCancel: false,
confirmText: '確定',
confirmColor: '#02BACC',
success: (res)=> {
if (res.confirm) {
doA();
}
},
});
}
function handleB(msg) {
wx.showModal({
title: '提示',
content: msg,
showCancel: false,
confirmText: '確定',
confirmColor: '#02BACC',
success: (res)=> {
if (res.confirm) {
doB();
}
},
});
}
function handleC(msg) {
wx.showModal({
title: '提示',
content: msg,
showCancel: false,
confirmText: '確定',
confirmColor: '#02BACC',
success: (res)=> {
if (res.confirm) {
doC();
}
},
});
}
解決方案,封裝showModal函數。
function showModal (msg) {
return new Promise((resolve, reject)=> {
wx.showModal({
title: '提示',
content: msg,
showCancel: false,
confirmText: '確定',
confirmColor: '#02BACC',
success: (res)=> {
if (res.confirm) resolve()
},
fail: (err)=> {
reject(err)
}
})
})
}
funtion handleA(msg) {
showModal(msg).then(
doA();
).catch(()=> { catchHandler();})
}
funtion handleB(msg) {
showModal(msg).then(
doB();
).catch(()=> { catchHandler();})
}
funtion handleC(msg) {
showModal(msg).then(
doC();
).catch(()=> { catchHandler();})
}
在Object.prototype上定義方法就相當于C++里定義宏, 而且還是 #define private public 這種。 從可靠性來說,多人協作很容易出現沖突。 從兼容性來說,你不能保證后續推出的原生方法實現和你現有的一致,也不能保證多個庫之間對該方法的實現一致。比較有名的故事是 prototype庫的getElementsByClassName。在還沒有這個原生方法之前,prototype這個庫實現的是返回Array,并且加了“each”方法:document.getElementsByClassName('myclass').each(doSomething);但原生方法出來后,返回的是NodeList,并沒有each方法;所以就悲劇了。 詳細說明可以看:Nicholas C. Zakas 的 Maintainable JavaScript: Don’t modify objects you don’t own
減少回調的嵌套,避免產生callback hell:
fs.readdir(source, function (err, files) {
if (err) {
console.log('Error finding files: ' + err)
} else {
files.forEach(function (filename, fileIndex) {
console.log(filename)
gm(source + filename).size(function (err, values) {
if (err) {
console.log('Error identifying file size: ' + err)
} else {
console.log(filename + ' : ' + values)
aspect=(values.width / values.height)
widths.forEach(function (width, widthIndex) {
height=Math.round(width / aspect)
console.log('resizing ' + filename + 'to ' + height + 'x' + height)
this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
if (err) console.log('Error writing file: ' + err)
})
}.bind(this))
}
})
})
}
})
建議使用promise、async/await 的方式讓代碼更為清晰可讀;也可以將callback要做的事拆成獨立的function,并分別對err進行處理。
函數盡量精簡在80行以內,并且以小function進行組織,方便維護、復用。
除了知道下面的邏輯是在繪制canvas,其他邏輯你能看懂嗎?
function doSomething() {
let count;
if ((count=width * height / 1000000) > 1) {
count=~~(Math.sqrt(count) + 1);
const nw=~~(width / count);
const nh=~~(height / count);
const tCanvas=document.createElement('canvas');
const tctx=tCanvas.getContext('2d');
tCanvas.width=nw;
tCanvas.height=nh;
for (let i=0; i < count; i++) {
for (let j=0; j < count; j++) {
tctx.drawImage(img, i * nw * ratio, j * nh * ratio, nw * ratio, nh * ratio, 0, 0, nw, nh);
ctx.drawImage(tCanvas, i * nw, j * nh, nw, nh);
}
}
} else {
ctx.drawImage(img, 0, 0, width, height);
}
}
常用的注釋分類有這些,建議參考 JSDoc:
1)文件注釋
2)變量注釋
3)常量注釋
4)函數注釋
5)枚舉注釋
6)類的注釋
7)類的屬性注釋
這一條在團隊里是出現過現網bug的。
故事背景是開發M在重構代碼時,設置底部欄狀態這一邏輯已經封裝出來,所以根據注釋,下面幾行代碼做的事情也是設置底部欄狀態,開發M就把這幾行代碼都刪掉了。但是注釋下面的代碼,除了做設置底部欄狀態的事情,還有一個setBanner 函數,是為了設置banner位的,也被連同刪掉,進而導致了bug。
追溯原因,設置底部欄狀態是A同學做的,設置 banner 位是B同學做的,B同學在沒有看注釋的情況下,直接把setBanner放在了錯誤的位置上。
function fetchData() {
// ...
doManythings();
// 設置底部欄狀態
setBanner();
fetchStatusApi().then(res=> {
// ...
}).catch(()=> {
// ...
})
}
git的版本管理能幫我們回溯之前的代碼。如果項目里存在大量注釋掉的代碼,會降低可讀性。
雖然console.log調試日志在生產環境構建時不會輸出,但就本地開發環境來說,代碼里慘雜過多console.log調試日志,控制臺滿屏的調試日志,對于每個接手的開發都是噩夢。另外,就像上面說的一樣,catch處理或else分支里存在只打console.log而不做任何處理的情況。盡量避免少使用console.log,也可以減少這類意外的發生。
所以,日常開發調試建議使用瀏覽器sources tab的斷點調試;另外,就算要輸出調試日志,也不止有console.log可以使用,參考這篇文章。你可以使用console.table等來格式化輸出
我能想到的允許 eslint-disable的場景只有一種,那就是解構后端返回對象。后端返回對象屬性名是下劃線,這個時候可能需要 // eslint-disable-next-line camelcase。其他情況我都不建議使用 eslint-disable,尤其是整個文件全局eslint-disable。
之前遇到過某文件全局禁用"no-undef"規則,結果代碼里使用了未定義的變量,導致現網bug。如果你有全局定義的變量,建議寫在eslintrc.js的globals字段里。當然,就正如上文代碼錯誤-內存泄露提到的一樣,非必要情況,不建議使用全局變量。
為了增強可讀性,建議使用空行對代碼分組。
常見的不規范命名有這些,會讓之后維護的同學很懵逼:
// bad case 1
function doctor () {}
// bad case 2
computed: {
getUserInfo() {}
}
// bad case 3
close=false;
同學,就不能好好寫代碼嗎?
// good case 1
function getDoctorInfo() {}
// good case 2
computed: {
getUserInfo() {}
}
// good case 3
closed=false
如果在業務邏輯里摻雜太多的上報,后續理解業務邏輯時需要看上報邏輯,查上報邏輯的時候也需要理解大量的業務代碼。 點擊埋點和曝光埋點都可以以屬性的形式掛在元素上,通過冒泡,統一進行處理。
<button
data-exposurename="test-exposure"
:data-exposureval="{event:'bottom.btn'} | jsonStringify"
data-eventname="button.click"
:data-eventval="{id: buttonId} | jsonStringify"
/>
如果你上報的參數需要根據不同渠道來配置,建議封裝出來,不要和業務邏輯耦合了。
除了項目的READMD,每個模塊都應該有各自的README,說明這個模塊的功能點、技術實現方案等。看個人習慣,你也可以寫在iwiki里,在README放一個iwiki的鏈接。
export default 有兩個問題: 1)不利于tree shaking 2)如果使用了一個導出對象上不存在的屬性,要運行時才能發現。
直接操作DOM的性能損耗至少有兩個地方:進行DOM操作的時候上下文切換 + DOM操作引起的頁面重繪
delete 操作符并不會釋放內存,而且會使得附加到對象上的hidden class失效,讓對象變成slow object。 (hidden class是V8為了優化屬性訪問時間而創建的隱藏類) 來看一下執行速度對比:undefined > delete > omit 。
比如做一個簡單圖表的需求,不選輕量的庫,非要整一個echarts;或者實現一個簡單的代碼編輯器,monaco-editor有min版本不使用,非要引用一整個monaco-editor。還有,lodash沒法做tree-shaking,要么引用一個具體的子包lodash.get,要么引用lodash-es,非要引用一整個lodash。
如果代碼里引用的是本地圖片,構建打包會有耗時。可以在引用之前就把圖片傳到cdn上,代碼里直接使用cdn地址。
以上就是CR的細則了。
手都寫麻了。當然,肯定還有很多遺漏的點,歡迎補充。
信圍觀的讀者不是初來乍到的小白,在這里不多贅述亞馬遜review的歷史變遷以及重要性。一段時間以來我們集中處理amazon review問題,時至今日有了亞馬遜測評系統(以下簡稱系統)也有了一些明確思路。一路走來,也參加了市面上大大小小的review課程,但這些review攻略偏灰色,不成體系。今天的終極攻略希望能從亞馬遜review的整體布局思路和具體方法上給予讀者一定的啟發和幫助。通過本篇文章,你將獲得:
1.亞馬遜review維護全局框架
2.多渠道多策略建立亞馬遜測評體系
3.不同補評(測評)策略時效性,安全性,可持續性以及成本對比分析
4.真人測評與刷單上評的識別與開發
5.新品LISTING安全快速補評方法
6.如何通過自動化補評規則提高評價維護效率
溫馨提示:
為了避諱一些敏感字眼,賓果博客內定義所有人為干預的上評行為均稱為補評,所有人為干預的出單行為均稱為補單
影響亞馬遜關鍵詞排名因素千千萬,相關性、轉化率與用戶反饋(review)至關重要。在review方面(本文不講轉化率):新品,我們希望短時間內有足夠高質量和足夠數量的review上線,既是為了在新品扶持期內加快新品推廣進展,也是為了規避差評風險。老品,我們希望零差評的同時每個月都有成比例的好評上線。滿懷期待,但有時卻力不從心,把以下四項策略堅持執行到位即可解決亞馬遜review中的90%的問題:
1.亞馬遜review差評預防
在產品Listing的評分里,1個差評可以抹掉5個好評,換句話說5個好評只可以抹掉1個差評。對很多從事亞馬遜運營的賣家來說,差評本身不易移除,這將會對抵抗力不強的LISTING銷量帶來毀滅性打擊。亞馬遜review差評的危害巨大,一定是以預防為主。
2.亞馬遜review差評移除
兵來將擋,水來土掩。差評來了也莫怕,據相關經驗,亞馬遜review差評有30%-50%移除的概率,當然有很多深圳大賣能做到50%或更高。
3.亞馬遜測評(快速補評)
今天的一個重頭戲,相信也是各位讀者最想了解的一部分。本文將從時效性.安全性.可持續性以及成本四個維度展開不同測評渠道的講解。前期建議以服務商的真人測評放單為主,同時快速建立起自己的測評體系。等體系建立后,用自己的方法更安全。
4.自動化補評規則
我們需要更高的測評效率,不僅僅是打通測評渠道更要建立自動化的測評管理。在不人為干預的情況下,評價維護負責人即使不懂亞馬遜運營也明白什么時間該對什么產品補評了,哪些產品需要優先處理等等。這對多賬戶多Listing操作的賣家有很大幫助。
接下來從策略到方法,從方法到執行,一步步分解亞馬遜review維護。
亞馬遜review差評預防攻略
1.采購端
第一,明確質檢方向讓懂產品的人做質檢工作。特別注意的一點是應考慮產品在亞馬遜上經常遇到的差評內容,讓懂產品的人做有針對性的質檢工作。第二,100%質檢。按照亞馬遜0.5%-1%的留評率計算,如果100件商品內有1件的瑕疵品,意味著所有的自然留評全是差評,夠恐怖吧。但傳統大貿環境下,工廠都是習慣進行抽檢工作,很難保證0.5%內的瑕疵率。所以當從事亞馬遜B2C電商與工廠打交道時,務必確認100%的質檢率。哪怕每件多付1元的質檢費用,也要保證不出現因為商品質量瑕疵帶來的差評。
案例:2018年4月上架了一批魚竿產品,通過推廣陸陸續續有了穩定的訂單。時間進入5月,接連出現差評和退貨,處理差評和上評的速度跟不上差評的速度(那時也沒有一套完整評價維護體系),結果可想而知。后來找到原因:本批次魚竿采用了分體輪座的技術,好處是讓魚竿更輕但是因為輪座分體很容易導致漁輪和輪座卡不緊。魚竿體積比較大,沒經過自己的倉庫直接運輸到港口出貨,都忽視了質檢。
2.銷售端
文案嚴謹,說明注意事項。文案描述不要夸大產品,避免因與用戶期望值不符帶來的差評。同時更要描述清楚產品的適用范圍、適用人群,注意事項,避免誤解誤用帶來的差評。
案例:一段時間發現有些客戶反映某些魚竿的輪座和他們的漁輪不適配。后來發現:原來是文案和圖片描述說本產品適用于多種漁輪,一是給用戶的錯覺是適用于所有漁輪,至少適用于他手中的漁輪。二是,既然提出魚竿漁輪適配情況卻未給出嚴謹的表達。所以當用戶用不上自己的漁輪時,心情很不爽。
3.客服端
如果前面1&2沒有做到位,還有差評預防的補救措施:在用戶很不爽的時候第一時間能找到最簡單快捷的發泄渠道。而作者認為感謝卡,產品以及包裝上的聯系方式最為方便客戶聯系到你,這樣用戶就會聯系你發泄不滿代替登錄后臺留差評。用了這個方法后,在ins或者facebook上陸陸續續接到了客戶的一些不滿反饋,極大降低了留差評的可能性,不僅如此通過提供解決方案還得到了用戶的認可,愿意留好評!
總結:能把差評堵在萌芽中,省心省錢!差評預防中,細節即大事!
亞馬遜review差評移除攻略
在review差評移除中可以很好的用“快,準,狠”三個字形容:出現差評反應要快,移除方法切入點要準,雙管齊下力道要狠。那么如何做到快速反應,切入要害,雙管齊下呢,我們首先看一下差評移除的幾個重要方法:服務商移除,溝通移除,亞馬遜移除。
1.服務商移除
服務商移除差評作者很少用,一是因為溝通移除和亞馬遜移除的效果還可以,二是因為費用高,三是因為各個服務商移除差評的方法層出不窮自己沒法控制過程風險比較高。之所以在這里提出,是給到大家一個應急的方法:既然出來做差評服務,我想在時效上和成功率上應該都不錯。當差評嚴重影響自己的時候可以考慮使用。
2.溝通移除 (此方法本身不難,難點在時間節點控制和郵件內容)
溝通移除是指獲取用戶真實郵箱后,通過幫助解決問題或者給予一定補償得到用戶諒解和認可,繼而要求刪掉/修改差評的過程。
@節點1.記錄所留差評,記錄時間。力保第一時間發現差評,快速獲取真實郵箱。
@節點2.記錄真實郵箱,記錄獲取的時間。市面上有很多獲取真實郵箱的途徑,15-25元不等。2個工作日內獲取郵箱,不要拖太久。
@節點3.發送郵件聯系,記錄發送時間。獲取郵箱后,按照制定好的標準郵件內容發送。
@節點4.獲得客戶反饋,記錄反饋時間 。到此步,收到反饋進入下一步。經常收不到反饋需要考慮重新編輯郵件內容,偶爾收不到反饋考慮用戶可能沒有看到郵件,在跟一封“Friendly reminder”
@節點5.根據郵件要求,給予問題解決或給予補償操作。
@節點6.按用戶要求完成后,用戶自己知道要刪除評價。
除時間節點跟蹤好外,此方法最有技術含量的是節點3的郵件內容。做好郵件內容的要點,一是郵件主旨內容(直奔主題就好):愿意為review做出補償。這一點非常重要。只要用戶回復,意味著用戶默許了你的建議,只要做了補償,用戶就得處理差評。二是補償建議:必須有方案,給到用戶A與B的選擇,要知道用戶是沒時間給你瞎掰的,除非你的產品貨值很高。三是郵件Title:能不能打開你的郵件,就看你的TITLE用不用心了。通過以上幾點反復斟酌自己的郵件內容,一定會事半功倍。
3.亞馬遜移除(此方法難點在找準切入點)
亞馬遜有review留評的明確條文,凡是不符合亞馬遜規則的評價都將予以刪除,并不允許在此LISTING再次留評。我們要做的是合理利用亞馬遜規則,向亞馬遜客服提出所處理差評的不合理性。鏈接www.amazon*com/gp/help/customer/display.html?nodeId=201929730是亞馬遜review的一些規則,空了可以細細品讀。根據以上規則和實戰經驗總結如下:
@規則1.review與feedback混淆。review僅是涉及物流客服包裝等服務方面的內容。
@規則2.含誹謗,騷擾,威脅,煽動性,淫穢,色情或猥褻內容
@規則3.含個人隱私內容
@規則4:含促銷內容
@規則5:差評專業戶留評
@規則6:內容積極但星級是差評
…………..
找準切入點后有3處提交入口;1是Report Abuse , 2是后臺幫助中的amazon review板塊 ,3是community-help*amazon.com.為了達到更好的效果,溝通移除與亞馬遜移除同步進行,雙管齊下,收效更好!
本章亞馬遜review維護攻略梳理到這里,下個章節將詳述市面上主流的亞馬遜測評渠道補評方法并從時效性、安全性、可持續性和費用四個維度進行分析,最后帶給大家如何建立快速補評自動化規則。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。