BUG邦马!BUG!BUG!前端程序員在面對(duì)異常怎么辦宴卖?這樣就so easy

前端一直是距離用戶最近的一層滋将,隨著產(chǎn)品的日益完善,我們會(huì)更加注重用戶體驗(yàn)症昏,而前端異常卻如鯁在喉随闽,甚是煩人。

一肝谭、為什么要處理異常掘宪?

異常是不可控的,會(huì)影響最終的呈現(xiàn)結(jié)果攘烛,但是我們有充分的理由去做這樣的事情魏滚。

1.增強(qiáng)用戶體驗(yàn);

2.遠(yuǎn)程定位問(wèn)題坟漱;

3.未雨綢繆鼠次,及早發(fā)現(xiàn)問(wèn)題;

4.無(wú)法復(fù)線問(wèn)題靖秩,尤其是移動(dòng)端须眷,機(jī)型,系統(tǒng)都是問(wèn)題沟突;

5.完善的前端方案,前端監(jiān)控系統(tǒng)捕传;

對(duì)于JS而言惠拭,我們面對(duì)的僅僅只是異常,異常的出現(xiàn)不會(huì)直接導(dǎo)致JS引擎崩潰庸论,最多只會(huì)使當(dāng)前執(zhí)行的任務(wù)終止职辅。

二、需要處理哪些異常聂示?

對(duì)于前端來(lái)說(shuō)域携,我們可做的異常捕獲還真不少∮愫恚總結(jié)一下秀鞭,大概如下:

JS語(yǔ)法錯(cuò)誤趋观、代碼異常

AJAX請(qǐng)求異常

靜態(tài)資源加載異常

Promise異常

Iframe異常

跨域 Script error

崩潰和卡頓

下面我會(huì)針對(duì)每種具體情況來(lái)說(shuō)明如何處理這些異常。

三锋边、Try-Catch 的誤區(qū)

try-catch只能捕獲到同步的運(yùn)行時(shí)錯(cuò)誤皱坛,對(duì)語(yǔ)法和異步錯(cuò)誤卻無(wú)能為力,捕獲不到豆巨。

1.同步運(yùn)行時(shí)錯(cuò)誤:

try {

let name = 'jartto';

console.log(nam);

} catch(e) {

console.log('捕獲到異常:',e);

}

輸出:

捕獲到異常: ReferenceError: nam is not defined

at :3:15

2.不能捕獲到語(yǔ)法錯(cuò)誤剩辟,我們修改一下代碼,刪掉一個(gè)單引號(hào):

try {

let name = 'jartto;

console.log(nam);

} catch(e) {

console.log('捕獲到異常:',e);

}

輸出:

Uncaught SyntaxError: Invalid or unexpected token

不過(guò)語(yǔ)法錯(cuò)誤在我們開(kāi)發(fā)階段就可以看到往扔,應(yīng)該不會(huì)順利上到線上環(huán)境贩猎。

3.異步錯(cuò)誤

try {

setTimeout(() => {

undefined.map(v => v);

}, 1000)

} catch(e) {

console.log('捕獲到異常:',e);

}

我們看看日志:

Uncaught TypeError: Cannot read property 'map' of undefined

at setTimeout (:3:11)

并沒(méi)有捕獲到異常,這是需要我們特別注意的地方萍膛。

四吭服、window.onerror 不是萬(wàn)能的

當(dāng)JS運(yùn)行時(shí)錯(cuò)誤發(fā)生時(shí),window會(huì)觸發(fā)一個(gè)ErrorEvent接口的error事件卦羡,并執(zhí)行window.onerror()噪馏。

/**

* @param {String} message 錯(cuò)誤信息

* @param {String} source 出錯(cuò)文件

* @param {Number} lineno 行號(hào)

* @param {Number} colno 列號(hào)

* @param {Object} error Error對(duì)象(對(duì)象)

*/

window.onerror = function(message, source, lineno, colno, error) {

console.log('捕獲到異常:',{message, source, lineno, colno, error});

}

1.首先試試同步運(yùn)行時(shí)錯(cuò)誤

window.onerror = function(message, source, lineno, colno, error) {

// message:錯(cuò)誤信息(字符串)。

// source:發(fā)生錯(cuò)誤的腳本URL(字符串)

// lineno:發(fā)生錯(cuò)誤的行號(hào)(數(shù)字)

// colno:發(fā)生錯(cuò)誤的列號(hào)(數(shù)字)

// error:Error對(duì)象(對(duì)象)

console.log('捕獲到異常:',{message, source, lineno, colno, error});

}

Jartto;

可以看到绿饵,我們捕獲到了異常:

?

2.再試試語(yǔ)法錯(cuò)誤呢欠肾?

window.onerror = function(message, source, lineno, colno, error) {

console.log('捕獲到異常:',{message, source, lineno, colno, error});

}

let name = 'Jartto

控制臺(tái)打印出了這樣的異常:

Uncaught SyntaxError: Invalid or unexpected token

什么,竟然沒(méi)有捕獲到語(yǔ)法錯(cuò)誤拟赊?

3.懷著忐忑的心刺桃,我們最后來(lái)試試異步運(yùn)行時(shí)錯(cuò)誤:

window.onerror = function(message, source, lineno, colno, error) {

console.log('捕獲到異常:',{message, source, lineno, colno, error});

}

setTimeout(() => {

Jartto;

});

控制臺(tái)輸出了:

捕獲到異常: {message: "Uncaught ReferenceError: Jartto is not defined", source: "http://127.0.0.1:8001/", lineno: 36, colno: 5, error: ReferenceError: Jartto is not defined

at setTimeout (http://127.0.0.1:8001/:36:5)}

4.接著,我們?cè)囋嚲W(wǎng)絡(luò)請(qǐng)求異常的情況:

window.onerror = function(message, source, lineno, colno, error) {

console.log('捕獲到異常:',{message, source, lineno, colno, error});

return true;

}


我們發(fā)現(xiàn)吸祟,不論是靜態(tài)資源異常瑟慈,或者接口異常,錯(cuò)誤都無(wú)法捕獲到屋匕。

補(bǔ)充一點(diǎn):window.onerror函數(shù)只有在返回true的時(shí)候葛碧,異常才不會(huì)向上拋出,否則即使是知道異常的發(fā)生控制臺(tái)還是會(huì)顯示Uncaught Error: xxxxx

window.onerror = function(message, source, lineno, colno, error) {

console.log('捕獲到異常:',{message, source, lineno, colno, error});

return true;

}

setTimeout(() => {

Jartto;

});

控制臺(tái)就不會(huì)再有這樣的錯(cuò)誤了:

Uncaught ReferenceError: Jartto is not defined

at setTimeout ((index):36)

需要注意:

onerror最好寫在所有JS腳本的前面过吻,否則有可能捕獲不到錯(cuò)誤进泼;

onerror無(wú)法捕獲語(yǔ)法錯(cuò)誤;

到這里基本就清晰了:在實(shí)際的使用過(guò)程中纤虽,onerror主要是來(lái)捕獲預(yù)料之外的錯(cuò)誤柠辞,而try-catch則是用來(lái)在可預(yù)見(jiàn)情況下監(jiān)控特定的錯(cuò)誤饲做,兩者結(jié)合使用更加高效基协。

問(wèn)題又來(lái)了蜘渣,捕獲不到靜態(tài)資源加載異常怎么辦?

五杰刽、window.addEventListener

當(dāng)一項(xiàng)資源(如圖片或腳本)加載失敗菠发,加載資源的元素會(huì)觸發(fā)一個(gè)Event接口的error事件王滤,并執(zhí)行該元素上的onerror()處理函數(shù)。這些error事件不會(huì)向上冒泡到window雷酪,不過(guò)(至少在Firefox中)能被單一的window.addEventListener捕獲淑仆。

window.addEventListener('error', (error) => {

console.log('捕獲到異常:', error);

}, true)


控制臺(tái)輸出:

?

由于網(wǎng)絡(luò)請(qǐng)求異常不會(huì)事件冒泡,因此必須在捕獲階段將其捕捉到才行哥力,但是這種方式雖然可以捕捉到網(wǎng)絡(luò)請(qǐng)求的異常蔗怠,但是無(wú)法判斷HTTP的狀態(tài)是404還是其他比如500等等,所以還需要配合服務(wù)端日志才進(jìn)行排查分析才可以吩跋。

需要注意:

不同瀏覽器下返回的error對(duì)象可能不同寞射,需要注意兼容處理。

需要注意避免addEventListener重復(fù)監(jiān)聽(tīng)锌钮。

六桥温、Promise Catch

在promise中使用catch可以非常方便的捕獲到異步error,這個(gè)很簡(jiǎn)單梁丘。

沒(méi)有寫catch的Promise中拋出的錯(cuò)誤無(wú)法被onerror或try-catch捕獲到侵浸,所以我們務(wù)必要在Promise中不要忘記寫catch處理拋出的異常。

解決方案: 為了防止有漏掉的Promise異常氛谜,建議在全局增加一個(gè)對(duì)unhandledrejection的監(jiān)聽(tīng)掏觉,用來(lái)全局監(jiān)聽(tīng)Uncaught Promise Error。使用方式:

window.addEventListener("unhandledrejection", function(e){

console.log(e);

});

我們繼續(xù)來(lái)嘗試一下:

window.addEventListener("unhandledrejection", function(e){

e.preventDefault()

console.log('捕獲到異常:', e);

return true;

});

Promise.reject('promise error');

可以看到如下輸出:

?

那如果對(duì)Promise不進(jìn)行catch呢值漫?

window.addEventListener("unhandledrejection", function(e){

e.preventDefault()

console.log('捕獲到異常:', e);

return true;

});

new Promise((resolve, reject) => {

reject('jartto: promise error');

});

嗯澳腹,事實(shí)證明,也是會(huì)被正常捕獲到的杨何。

所以酱塔,正如我們上面所說(shuō),為了防止有漏掉的Promise異常危虱,建議在全局增加一個(gè)對(duì)unhandledrejection的監(jiān)聽(tīng)羊娃,用來(lái)全局監(jiān)聽(tīng)Uncaught Promise Error。

補(bǔ)充一點(diǎn):如果去掉控制臺(tái)的異常顯示埃跷,需要加上:

event.preventDefault();

七迁沫、VUE errorHandler

Vue.config.errorHandler = (err, vm, info) => {

console.error('通過(guò)vue errorHandler捕獲的錯(cuò)誤');

console.error(err);

console.error(vm);

console.error(info);

}

八、React 異常捕獲

React 16提供了一個(gè)內(nèi)置函數(shù)componentDidCatch捌蚊,使用它可以非常簡(jiǎn)單的獲取到react下的錯(cuò)誤信息

componentDidCatch(error, info) {

console.log(error, info);

}

除此之外,我們可以了解一下:error boundary

UI的某部分引起的JS錯(cuò)誤不應(yīng)該破壞整個(gè)程序近弟,為了幫React的使用者解決這個(gè)問(wèn)題缅糟,React 16介紹了一種關(guān)于錯(cuò)誤邊界(error boundary)的新觀念。

需要注意的是: error boundaries 并不會(huì)捕捉下面這些錯(cuò)誤祷愉。

1.事件處理器

2.異步代碼

3.服務(wù)端的渲染代碼

4.在error boundaries區(qū)域內(nèi)的錯(cuò)誤

我們來(lái)舉一個(gè)小例子窗宦,在下面這個(gè)componentDIdCatch(error,info)里的類會(huì)變成一個(gè)error boundary:

class ErrorBoundary extends React.Component {

constructor(props) {

super(props);

this.state = { hasError: false };

}

componentDidCatch(error, info) {

// Display fallback UI

this.setState({ hasError: true });

// You can also log the error to an error reporting service

logErrorToMyService(error, info);

}

render() {

if (this.state.hasError) {

// You can render any custom fallback UI

return

Something went wrong.

;

}

return this.props.children;

}

}

然后我們像使用普通組件那樣使用它:

componentDidCatch()方法像JS的catch{}模塊一樣工作赦颇,但是對(duì)于組件,只有class類型的組件(class component)可以成為一個(gè)error boundaries赴涵。

實(shí)際上媒怯,大多數(shù)情況下我們可以在整個(gè)程序中定義一個(gè)error boundary組件,之后就可以一直使用它了髓窜!

九扇苞、iframe 異常

對(duì)于iframe的異常捕獲,我們還得借力window.onerror:

window.onerror = function(message, source, lineno, colno, error) {

console.log('捕獲到異常:',{message, source, lineno, colno, error});

}

一個(gè)簡(jiǎn)單的例子可能如下:

<iframe src="./iframe.html" frameborder="0"></iframe>

<script>

window.frames[0].onerror = function (message, source, lineno, colno, error) {

console.log('捕獲到 iframe 異常:',{message, source, lineno, colno, error});

return true;

};

十寄纵、Script error

一般情況鳖敷,如果出現(xiàn)Script error這樣的錯(cuò)誤,基本上可以確定是出現(xiàn)了跨域問(wèn)題程拭。這時(shí)候定踱,是不會(huì)有其他太多輔助信息的,但是解決思路無(wú)非如下:

跨源資源共享機(jī)制(CORS):我們?yōu)閟cript標(biāo)簽添加crossOrigin屬性恃鞋。

<script src="http://jartto.wang/main.js" crossorigin></script>

或者動(dòng)態(tài)去添加js腳本:

const script = document.createElement('script');

script.crossOrigin = 'anonymous';

script.src = url;

document.body.appendChild(script);

特別注意崖媚,服務(wù)器端需要設(shè)置:Access-Control-Allow-Origin

十一、崩潰和卡頓

卡頓也就是網(wǎng)頁(yè)暫時(shí)響應(yīng)比較慢恤浪,JS可能無(wú)法及時(shí)執(zhí)行畅哑。但崩潰就不一樣了,網(wǎng)頁(yè)都崩潰了资锰,JS都不運(yùn)行了敢课,還有什么辦法可以監(jiān)控網(wǎng)頁(yè)的崩潰,并將網(wǎng)頁(yè)崩潰上報(bào)呢绷杜?

崩潰和卡頓也是不可忽視的直秆,也許會(huì)導(dǎo)致你的用戶流失。

1.利用window對(duì)象的load和beforeunload事件實(shí)現(xiàn)了網(wǎng)頁(yè)崩潰的監(jiān)控鞭盟。

不錯(cuò)的文章圾结,推薦閱讀:Logging Information on Browser Crashes

window.addEventListener('load', function () {

sessionStorage.setItem('good_exit', 'pending');

setInterval(function () {

sessionStorage.setItem('time_before_crash', new Date().toString());

}, 1000);

});

window.addEventListener('beforeunload', function () {

sessionStorage.setItem('good_exit', 'true');

});

if(sessionStorage.getItem('good_exit') &&

sessionStorage.getItem('good_exit') !== 'true') {

/*

insert crash logging code here

*/

alert('Hey, welcome back from your crash, looks like you crashed on: ' + sessionStorage.getItem('time_before_crash'));

}

2.基于以下原因齿诉,我們可以使用Service Worker來(lái)實(shí)現(xiàn)網(wǎng)頁(yè)崩潰的監(jiān)控:

Service Worker有自己獨(dú)立的工作線程筝野,與網(wǎng)頁(yè)區(qū)分開(kāi),網(wǎng)頁(yè)崩潰了粤剧,Service Worker一般情況下不會(huì)崩潰歇竟;

Service Worker生命周期一般要比網(wǎng)頁(yè)還要長(zhǎng),可以用來(lái)監(jiān)控網(wǎng)頁(yè)的狀態(tài)抵恋;

網(wǎng)頁(yè)可以通過(guò)navigator.serviceWorker.controller.postMessage API向掌管自己的SW發(fā)送消息焕议。

十二、錯(cuò)誤上報(bào)

1.通過(guò)Ajax發(fā)送數(shù)據(jù)

因?yàn)锳jax請(qǐng)求本身也有可能會(huì)發(fā)生異常弧关,而且有可能會(huì)引發(fā)跨域問(wèn)題盅安,一般情況下更推薦使用動(dòng)態(tài)創(chuàng)建img標(biāo)簽的形式進(jìn)行上報(bào)唤锉。

2.動(dòng)態(tài)創(chuàng)建img標(biāo)簽的形式

function report(error) {

let reportUrl = 'http://jartto.wang/report';

new Image().src = `${reportUrl}?logs=${error}`;

}

收集異常信息量太多,怎么辦别瞭?實(shí)際中窿祥,我們不得不考慮這樣一種情況:如果你的網(wǎng)站訪問(wèn)量很大,那么一個(gè)必然的錯(cuò)誤發(fā)送的信息就有很多條蝙寨,這時(shí)候晒衩,我們需要設(shè)置采集率,從而減緩服務(wù)器的壓力:

Reporter.send = function(data) {

// 只采集 30%

if(Math.random() < 0.3) {

send(data) // 上報(bào)錯(cuò)誤信息

}

}

采集率應(yīng)該通過(guò)實(shí)際情況來(lái)設(shè)定籽慢,隨機(jī)數(shù)浸遗,或者某些用戶特征都是不錯(cuò)的選擇。

十三箱亿、總結(jié)

回到我們開(kāi)頭提出的那個(gè)問(wèn)題跛锌,如何優(yōu)雅的處理異常呢?

1.可疑區(qū)域增加Try-Catch

2.全局監(jiān)控JS異常window.onerror

3.全局監(jiān)控靜態(tài)資源異常window.addEventListener

4.捕獲沒(méi)有Catch的Promise異常:unhandledrejection

5.VUE errorHandler和React componentDidCatch

6.監(jiān)控網(wǎng)頁(yè)崩潰:window對(duì)象的load和beforeunload

7.跨域crossOrigin解決

其實(shí)很簡(jiǎn)單届惋,正如上文所說(shuō):采用組合方案髓帽,分類型的去捕獲異常,這樣基本 80%-90% 的問(wèn)題都化于無(wú)形脑豹。

我自己是一名從事了多年前端開(kāi)發(fā)的老程序員郑藏,辭職目前在做自己的私人定制課程,今年年初我花了一個(gè)月整理了一份最適合2019年學(xué)習(xí)的前端學(xué)習(xí)干貨瘩欺,從最基礎(chǔ)的到深入的都有整理必盖。送給每一位想學(xué)習(xí)的小伙伴。

學(xué)習(xí)討論企鵝群:956766604

?

?

今天就整理這幾個(gè)問(wèn)題俱饿,后期再跟大家一起交流~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末歌粥,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子拍埠,更是在濱河造成了極大的恐慌失驶,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件枣购,死亡現(xiàn)場(chǎng)離奇詭異嬉探,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)棉圈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門涩堤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人分瘾,你說(shuō)我怎么就攤上這事定躏。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵痊远,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我氏捞,道長(zhǎng)碧聪,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任液茎,我火速辦了婚禮逞姿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘捆等。我一直安慰自己滞造,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布栋烤。 她就那樣靜靜地躺著谒养,像睡著了一般。 火紅的嫁衣襯著肌膚如雪明郭。 梳的紋絲不亂的頭發(fā)上买窟,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音薯定,去河邊找鬼始绍。 笑死,一個(gè)胖子當(dāng)著我的面吹牛话侄,可吹牛的內(nèi)容都是我干的亏推。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼年堆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼吞杭!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起嘀韧,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤篇亭,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后锄贷,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體译蒂,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年谊却,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了柔昼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡炎辨,死狀恐怖捕透,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤乙嘀,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布末购,位于F島的核電站,受9級(jí)特大地震影響虎谢,放射性物質(zhì)發(fā)生泄漏盟榴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一婴噩、第九天 我趴在偏房一處隱蔽的房頂上張望擎场。 院中可真熱鬧,春花似錦几莽、人聲如沸迅办。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)站欺。三九已至,卻和暖如春究驴,著一層夾襖步出監(jiān)牢的瞬間镊绪,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工洒忧, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蝴韭,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓熙侍,卻偏偏與公主長(zhǎng)得像榄鉴,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蛉抓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 現(xiàn)行有一些已經(jīng)開(kāi)源的前端異常監(jiān)控庫(kù)庆尘,如騰訊的badJs,全棧js監(jiān)控fundebug巷送,國(guó)外的sentry等驶忌。 錯(cuò)誤...
    滾石_c2a6閱讀 3,991評(píng)論 3 0
  • Promise 對(duì)象 Promise 的含義 Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函...
    neromous閱讀 8,698評(píng)論 1 56
  • 最近在做一個(gè)前端監(jiān)控的js 如圖笑跛,一個(gè)大概的思路是這樣的付魔。 圖片版 -----------------------...
    Estarsyang閱讀 531評(píng)論 0 0
  • 前端可以說(shuō)是最貼近用戶的一層,當(dāng)產(chǎn)品不斷的迭代完善飞蹂,產(chǎn)品的用戶體驗(yàn)會(huì)更加趨向于完美几苍,然而前端異常卻是很另人頭疼的一...
    編程鴨閱讀 444評(píng)論 0 0
  • 我慈愛(ài)的外婆于5.16日凌晨去世,去了另一個(gè)世界陈哑。 一個(gè)月前妻坝,外婆被診斷出肺癌晚期伸眶。遠(yuǎn)在湖南的我,心里除了擔(dān)心刽宪,就...
    女王萌閱讀 212評(píng)論 0 0