亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

京東單品頁(yè)前端開發(fā)那些不得不說(shuō)的事兒

FleyX / 1969人閱讀

摘要:是負(fù)責(zé)展示京東商品的落地頁(yè)面。比如京東首頁(yè),正常情況加載完頁(yè)面一共有多個(gè)節(jié)點(diǎn),基本上全部用于展示商品信息廣告圖和內(nèi)容布局,頁(yè)面上的三方異步服務(wù)也比較少。

原文:https://keelii.github.io/2016/07/31/something-have-to-say-with-JD-item

簡(jiǎn)介

詳情頁(yè)也叫做單品頁(yè),域名以「item.jd.com/skuid.html」為格式的頁(yè)面。是負(fù)責(zé)展示京東商品 SKU 的落地頁(yè)面。主要任務(wù)是展示和商品相關(guān)的信息,如:價(jià)格、促銷、庫(kù)存、推薦,從而引導(dǎo)用戶進(jìn)入購(gòu)買流程。同時(shí)單品頁(yè)有很多版本。一般分為兩類。一類我們通??吹降摹竿ㄓ妙惸吭斍轫?yè)」—— 所有類目都可以使用,一類是不經(jīng)??吹降摹复怪睂傩栽斍轫?yè)」—— 一些有特殊屬性的商品集合。

首先。由于詳情頁(yè)大量(sku上億)、高并發(fā)(日 pv 約 5000 萬(wàn))等特性,在很長(zhǎng)的一段時(shí)間里,單品頁(yè)面都是后端程序生成靜態(tài)頁(yè)面使用 CDN 來(lái)解決大量、高并發(fā)的問(wèn)題。

其次。單品頁(yè)涉及的「三方」系統(tǒng)特別多,比如:促銷、庫(kù)存、合約、秒殺、預(yù)售、推薦、IM、店鋪、評(píng)價(jià)社區(qū)。而單品頁(yè)的主要任務(wù)就是展示這些系統(tǒng)的信息,并且適當(dāng)?shù)奶幚硭麄冎g的沖突關(guān)系,而這些系統(tǒng)的接口一般都使用 異步 Ajax 來(lái)完成,因?yàn)?其一 CDN 無(wú)法做到頁(yè)面的動(dòng)態(tài)化,其二 一些系統(tǒng)的信息對(duì)實(shí)時(shí)性要求特別高(價(jià)格、秒殺),即使使用后端動(dòng)態(tài)渲染也很難做到無(wú)緩存 0 延遲。

基于上面兩個(gè)原因,注定了單品頁(yè)是一種重多系統(tǒng)業(yè)務(wù)邏輯展示型頁(yè)面。重前端頁(yè)面。我大概匯總了一下頁(yè)面上異步接口,總共約有 30 個(gè),頁(yè)首屏的接口特別重要,接口之間幾乎都有耦合關(guān)系。

前端的發(fā)展歷程 混沌時(shí)期

混沌時(shí)期的單品頁(yè)并沒有前端開發(fā)的概念。核心的功能腳本只有三個(gè):促銷價(jià)格(promotion.js)、庫(kù)存地區(qū)(iplocation.js)、其它邏輯(pshow.js)。這三個(gè)腳本分別是三個(gè)不同團(tuán)隊(duì)的同事負(fù)責(zé)維護(hù),當(dāng)時(shí)我剛進(jìn)入京東的時(shí)候在 UED 部門,負(fù)責(zé)頁(yè)面腳本整體的維護(hù)工作和 pshow的開發(fā)。那時(shí)候我自己維護(hù)的 pshow.js 腳本壓縮后只有 80 kb,所有的代碼都是過(guò)程式的,沒有任何使用模式和代碼技巧,JS 最多也只被用來(lái)做個(gè)判斷渲染 DOM。那時(shí)候的前端工作內(nèi)容只在 UI 層面,寫樣式和一些交互腳本。

這個(gè)階段給我最深刻的感覺是單品頁(yè)后端模板很少維護(hù)(后端架構(gòu)是最老的 aspx 版本)。大多數(shù)的改動(dòng)都要用 JavaScript 去動(dòng)態(tài)渲染。因?yàn)楹蠖隧?yè)面是一個(gè)生成器生成的。如果頁(yè)面后端模板有改動(dòng)那么就需要全量的生成一次,過(guò)程可能需要幾個(gè)小時(shí)。

初見端倪

當(dāng)我接手這個(gè)項(xiàng)目時(shí)剛好有一次大改版,就在這時(shí)候老大說(shuō)頁(yè)面上的腳本都要放在我們手里維護(hù)。然后就是一大波的重構(gòu)、重寫?;旧?pshow 被重寫了大概 80% 其它的因?yàn)闃I(yè)務(wù)邏輯的問(wèn)題并沒有完全重寫,只是做了些代碼層面的優(yōu)化。

有一個(gè)模板引擎叫 trimPath,知道這個(gè)的估計(jì)都算老前端的了。最早的客戶端 JavaScript MVC 模式代表作品,只到現(xiàn)在還是使用。這個(gè)階段像評(píng)價(jià)這種完全異步加載的模塊特別適合使用模板引擎來(lái)減少維護(hù)的工作量。這個(gè)時(shí)候雖然頁(yè)面上的代碼并不都是我們寫的,但基本上前端對(duì)頁(yè)面的 JavaScript 有了控制權(quán),接下來(lái)的事情就是尋找機(jī)會(huì)逐個(gè)優(yōu)化。

這段時(shí)間是最痛苦的時(shí)候,維護(hù)的工作統(tǒng)一到前端。然后后端幾乎沒有變化,只是在一段時(shí)間將后臺(tái)的架構(gòu)從 aspx 過(guò)渡到了 java。本質(zhì)上并沒有什么改變。前端卻做了比以前更多的事情,也是在這個(gè)時(shí)候我接手了大量的維護(hù)工作(包含全站公共庫(kù)的維護(hù))使得我意識(shí)到了一些自動(dòng)化、工程化方面的重要性,后文會(huì)主要講解,順便說(shuō)下,那時(shí)候前端自動(dòng)化工具 Grunt 剛面世,但是我自己卻用的是 apache ant,不過(guò)不久就切換到了 Grunt 來(lái)構(gòu)建項(xiàng)目。

撥云見日

單品頁(yè)不僅重系統(tǒng)邏輯,也重維護(hù)。

在這段時(shí)間里一方面有正常的維護(hù)類需求要做,一方面自己也不斷的學(xué)習(xí)新知識(shí)為以后的改版做鋪墊。不過(guò)就在這時(shí)單品頁(yè)有歷史意義的一次技改出現(xiàn)了 —— 單品頁(yè)動(dòng)態(tài)化技改。關(guān)于后端部分的改造細(xì)節(jié)可以去 開濤的文章 了解。

總的來(lái)說(shuō)這次的改版后很多數(shù)據(jù)直接從后端讀取,不再?gòu)那岸水惒将@取而且我們也做過(guò)一些異步加載的優(yōu)化,多接口 combo 從統(tǒng)一服務(wù)吐出給前端使用。這時(shí)前端就不用再為異步接口的加載時(shí)苦腦了,只需要專注系統(tǒng)接口的邏輯。

隨著這次技改,前端的代碼也迎來(lái)了模塊化的時(shí)代。我們把所有的前端代碼都進(jìn)行了模塊化然后基于 SeaJS 重寫,配合 Nginx concat 功能實(shí)現(xiàn)了本地模塊化開發(fā),線上服務(wù)端合并。

單品頁(yè)前端模塊的結(jié)構(gòu)與劃分 概覽

上圖可以看出,基本上最核心的模塊都在首屏。每個(gè)模塊都有多帶帶的一/多個(gè)腳本。代碼行數(shù)(LOC)由 230+ ~ 1200+ 不等。通常來(lái)說(shuō)代碼行數(shù)越多代碼復(fù)雜性就越高,邏輯越復(fù)雜。很難想象「購(gòu)買方式」這種只有一行屬性選擇功能的代碼行數(shù)卻 高達(dá) 1200 多行。其主要原因就在于購(gòu)買方式所在的系統(tǒng)和其它首屏核心系統(tǒng)(庫(kù)存、促銷、地址選擇、白條)都有邏輯上的耦合。

看著不錯(cuò),然而在一個(gè)前端工程師眼里至少應(yīng)該是這樣的(我只取了一些典型的模塊,并不是全部):

這就可以解釋為什么有的時(shí)候只是加一個(gè)很小的東西我們都為考慮再三然后通過(guò) AB 測(cè)試提取相關(guān)數(shù)據(jù),最后后再進(jìn)行決策。單品頁(yè)的首屏可以說(shuō)是寸土寸金。

按什么維度劃分模塊

起初我按模塊的屬性劃分,比如:核心、公共腳本、模塊腳本。但用了一段時(shí)候以后發(fā)現(xiàn)這樣劃分在單品這種大型系統(tǒng)中并不科學(xué),因?yàn)檫@樣劃分出來(lái)的代碼只有劃分的人知道是什么規(guī)則,其它人接手代碼很難快速掌握代碼架構(gòu),而且尤其在模塊比較多的時(shí)候不方便維護(hù)。

后來(lái)我嘗試完全以功能模塊在頁(yè)面上出現(xiàn)的位置維度劃分。這樣以來(lái)維護(hù)起來(lái)方便多了,需要修改某個(gè)模塊代碼只需要對(duì)照著圖里面標(biāo)識(shí)的模塊信息就能輕易找到代碼。

整體核心模塊

我們按頁(yè)面上的模塊結(jié)構(gòu)首屏劃分出來(lái)這幾個(gè)核心模塊:

curmb - 面包屑

concat - 聯(lián)系咨詢相關(guān)店鋪信息

prom - 價(jià)格促銷信息

address - 地區(qū)庫(kù)存選擇,配送服務(wù)

color - 顏色尺碼

buytype - 合約機(jī)購(gòu)買方式

suits - 套裝購(gòu)買

jdservice - 增值服務(wù)

baitiao - 白條支付

buybtn - 購(gòu)買按鈕

info - 地區(qū)提示信息

項(xiàng)目的整體樹形結(jié)構(gòu)是這樣的:

模塊內(nèi)部結(jié)構(gòu)

比如下面這個(gè)大圖預(yù)覽的功能,我全部放在一個(gè)文件夾里面維護(hù),但是邏輯上的 JavaScript 模塊是分離的,只是說(shuō)文件夾(preview)就代表頁(yè)面上的某一部分功能集合。

注意文件夾的命名有一定的規(guī)則:

模塊腳本與樣式名必須一樣

需要制作 sprite 的圖片統(tǒng)一放在 module/i 目錄下面,生成的 sprite 圖片也在其中

生成的 mixin 在模塊根目錄下,便于其它樣式文件調(diào)用

我們?cè)賮?lái)看下自動(dòng)生成生成的 __sprite.scss 是什么內(nèi)容:

/* __sprite.scss 自動(dòng)生成 */
@mixin sprite-arrow-next {
    width: 22px;
    height: 32px;
    background-image: url(i/__sprite.png);
    background-position: -0px -30px;
}

/* preview.scss 手動(dòng)添加 */
@import "./__sprite";
.sprite-arrow-next {
    @include sprite-arrow-next;
}

注意引用的 mixin 名稱和我們需要手動(dòng)添加的樣式類名一致。當(dāng)然也可以直接生成一個(gè)類名對(duì)應(yīng)的樣式,但是靈活性不好。比如 hover 的時(shí)候是另外一張圖片就沒法自動(dòng)生成了。

前端技能樹 HTML DOM 節(jié)點(diǎn)數(shù)

與重業(yè)務(wù)邏輯的頁(yè)面不同,重展示的頁(yè)面一般具有很高的 DOM 節(jié)點(diǎn)數(shù)。比如京東首頁(yè),正常情況加載完頁(yè)面一共有 3500 多個(gè) DOM 節(jié)點(diǎn),基本上全部用于展示商品信息、廣告圖和內(nèi)容布局,頁(yè)面上的三方異步服務(wù)也比較少。尤其像頻道頁(yè)基本上沒有什么業(yè)務(wù)上的邏輯,全部是靜態(tài)頁(yè)面。這種頁(yè)面的特點(diǎn)是更新?lián)Q代頻率高,一年兩三次改版很正常,CMS 做模塊化后兩天換個(gè)皮膚都是沒問(wèn)題的。但是這種思路并不適合單品頁(yè)。單品頁(yè)更重業(yè)務(wù)邏輯,同時(shí)展示層 UI 邏輯也有很多關(guān)系。

我自己的經(jīng)驗(yàn)是:頁(yè)面上的 DOM 節(jié)點(diǎn)數(shù)絕對(duì)不能超過(guò) 5000 個(gè),否則頁(yè)面滾動(dòng)的時(shí)候就會(huì)出現(xiàn)卡頓的情況,尤其是移動(dòng)端。

同步渲染還是異步加載

理論情況下最好做法是后端同步動(dòng)態(tài)渲染頁(yè)面,但是由于 Web 應(yīng)用中很多功能都是用戶行為驅(qū)動(dòng)的。同步加載不可避免的消耗了后端服務(wù)資源。比如:非首屏模塊(公共頭尾、評(píng)價(jià))、點(diǎn)擊事件觸發(fā)的 DOM 內(nèi)容(異步 tab)。

所以我的經(jīng)驗(yàn)是:能放到后端做判斷渲染的 DOM 就盡量放在后端(尤其是首屏)。這樣做的好處有四點(diǎn)好處

后端渲染頁(yè)面相對(duì)穩(wěn)定,不像前端 JavaScript 動(dòng)態(tài)渲染 DOM,可能因?yàn)槟_本報(bào)錯(cuò)或者不可用造成模塊都無(wú)法展示

可訪問(wèn)性、SEO 及用戶體驗(yàn)也比較好。不會(huì)產(chǎn)生腳本的渲染抖動(dòng)問(wèn)題

一定程度上減少了前端渲染頁(yè)面的復(fù)雜性,減少前端代碼復(fù)雜度

邏輯統(tǒng)一到一個(gè)地方維護(hù)起來(lái)也方便,而且后端應(yīng)該為業(yè)務(wù)邏輯負(fù)責(zé),前端應(yīng)該為展示UI 交互負(fù)責(zé)

對(duì)于異步渲染的模塊來(lái)說(shuō),后端通常需要判斷 「頁(yè)面有什么元素」,以及元素之間的依賴對(duì)應(yīng)關(guān)系;而前端需要專注于 「元素應(yīng)該怎么展示」,UI 層面的交互以及模塊與模塊之前的邏輯關(guān)系。

其實(shí)更多的時(shí)候 異步是一種沒有辦法的辦法,也就是說(shuō)異步是其它方案都解決不了的情況下才考慮的。

外鏈靜態(tài)資源

盡量使用外鏈 CSS 和 JavaScript 資源,一方面便于緩存,減少服務(wù)同步輸出的資源浪費(fèi)。IE 6 里面會(huì)有一些可怪的 bug,比如有內(nèi)聯(lián)樣式 style 標(biāo)簽的頁(yè)面 A 如果在另外一個(gè)頁(yè)面 B 中的 link 標(biāo)簽中引用,那么這段 style 會(huì)在 B 頁(yè)面也起作用。

使用雙協(xié)議的 URL

使用 // 來(lái)代替http:https: 瀏覽器會(huì)自動(dòng)適應(yīng)兩種協(xié)議的資源訪問(wèn),兼容性較好。注意 IE 8 下使用腳本更新 src 為雙協(xié)議時(shí)會(huì)出現(xiàn) bug,建議使用 location.protocol 來(lái)判斷然后做兼容處理。

刪除元素默認(rèn)屬性

比如 script 標(biāo)簽?zāi)J(rèn)的 type 就是 text/javascript,如果 script 里面的內(nèi)容是 JavaScript 時(shí)可以不用寫 type。另外如果要在頁(yè)面里面插入一段不需要瀏覽器解析的 HTML 片段時(shí)可以將 type 寫成 text/x-template(任意不存在的 type) 用于放置模板文件,通常用來(lái)在腳本中獲取其 innerHTML 而無(wú)任何負(fù)作用。

給腳本控制元素加上類鉤子

在腳本中取頁(yè)面元素使用 J- 前綴類名,與普通樣式類分離。這樣做會(huì)生成很多冗余的類名,但卻很好的降低了樣式和腳本的耦合,并且在重構(gòu)和腳本職位分開團(tuán)隊(duì)里會(huì)是一條最佳實(shí)踐。

CSS 樣式分類

所有頁(yè)面只共享一個(gè) sass Mixin,里面包含了基礎(chǔ)的 sass 語(yǔ)法糖、常用類(清浮動(dòng)、頁(yè)面整體顏色字體等)。

模塊級(jí)的樣式分為兩類:

與腳本無(wú)關(guān)的公共樣式,多帶帶在模塊文件夾中組織。比如:按鈕、標(biāo)簽頁(yè)。全部放在 common 模塊中維護(hù)

與腳本相關(guān)的模塊級(jí)樣式,與對(duì)應(yīng)模塊腳本放在一起,可以引用 common 中的公共樣式,但不可以被其它模塊引用

雪碧圖

關(guān)于雪碧圖 我經(jīng)驗(yàn)是:永遠(yuǎn)不要想把所有的圖標(biāo)拼合在一起。按模塊而不是按頁(yè)面去拼 sprite 更合理,更方便維護(hù),然后配合構(gòu)建工具自動(dòng)接合生成樣式文件才是最好的解決方案。當(dāng)然如果你的頁(yè)面比較簡(jiǎn)單,那這條規(guī)則并不適用。說(shuō)到這個(gè)問(wèn)題我就得把珍藏多年的圖片拿出來(lái) show 一把,用事實(shí)來(lái)說(shuō)明為什么把所有圖片都拼在一張圖上就一定是對(duì)的。

早期由于年輕篤信將所有的 icon 拼在一張圖上才是完美的(圖 1)

后來(lái)維護(hù)起來(lái)實(shí)在不方便,就把按鈕全部多帶帶接合起來(lái)。注意,當(dāng)時(shí)的按鈕都是圖片,設(shè)計(jì)方面要求的很嚴(yán)格。加入購(gòu)物車按鈕做的也非常漂亮(圖 2)

然后這些都不是最典型的,下面這個(gè) promise icon 才是 (圖 3)

從圖里面可以看到,這個(gè)功能在第一個(gè)版本的時(shí)候只有 7 個(gè) icon,后來(lái)不斷增加,最多的時(shí)候達(dá)到 77 個(gè)。以至于當(dāng)時(shí)每周都會(huì)添加兩個(gè)的頻率。

同時(shí)這個(gè) icon 當(dāng)時(shí)接合的時(shí)候技術(shù)上也有問(wèn)題:不應(yīng)該把文字也切到圖片里面,主要原因是早期 icon 比較少加上外邊框樣式對(duì)齊的問(wèn)題綜合選擇了直接使用圖片。

后來(lái)我就覺得這樣是不對(duì)的。然后通過(guò)和產(chǎn)品的溝通,說(shuō)明我的考慮以及新的解決方案后得到了認(rèn)同。結(jié)果就是對(duì)圖片不進(jìn)行拼合,后臺(tái)上傳經(jīng)過(guò)審核的不帶文字 icon,文字由接口輸出,然后在產(chǎn)品上做了約定:icon 最多不能超過(guò) 4 個(gè),代碼里也做了相應(yīng)限制。這樣就能保證頁(yè)面上的請(qǐng)求數(shù)不會(huì)太多同時(shí)方便系統(tǒng)維護(hù),問(wèn)題得到了解決。

適當(dāng)使用 DataURI

這個(gè)在一些小圖片場(chǎng)景方面特別適合,比如 1*1 的占位圖、loading 圖等,不過(guò) IE 6 并不支持這種寫法,需要的時(shí)候可以加上一些兼容寫法:

.ELazy-loading {
    background: url(data:image/gif;base64,R0lGODlhKwAeAJEAAP///93d3Xq9VAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFFAAAACwDAA0AJQADAAACEpSPAhDtHxacqcr5Lm416f1hBQAh+QQJFAAAACwDAA0AJQADAAACFIyPAcLtDKKcMtn1Mt3RJpw53FYAACH5BAkUAAAALAMADQAlAAMAAAIUjI8BkL0CoxQtrYrenPjcrgDbVAAAOw==) center center no-repeat;
    *background-image: url(//misc.360buyimg.com/lib/skin/e/i/loading-jd.gif);
}
關(guān)于兼容性

兼容性可以說(shuō)是前端工程師在平常開發(fā)中花費(fèi)很大量無(wú)意義工作的地方。關(guān)于兼容性我想說(shuō)的是 如果你不愿意去說(shuō)服周圍的人放棄或者讓他們意識(shí)到兼容性是個(gè)不可能完全解決的問(wèn)題,那么你就得為那些低級(jí)瀏覽器給你帶來(lái)的痛苦埋單。

其實(shí)更好的辦法是你和設(shè)計(jì)、產(chǎn)品溝通然后給出一種分級(jí)支持的方案。把每種瀏覽器定義一個(gè)級(jí)別。然后在開發(fā)功能的時(shí)候以「漸進(jìn)增強(qiáng)」的方式。通常來(lái)講我們的解決方案是在低級(jí)瀏覽器里面保證流程正常進(jìn)行、模塊可以使用,但忽略一些無(wú)關(guān)緊要的錯(cuò)位、不透明等問(wèn)題,在高級(jí)瀏覽器里面需要對(duì)設(shè)計(jì)稿進(jìn)行精確還原,適當(dāng)?shù)募由弦恍┚咸砘ㄔ诩?xì)節(jié)。比如微小的動(dòng)畫、邏輯細(xì)節(jié)上的處理等。

舉個(gè)例子吧,下面這個(gè)進(jìn)度條表示預(yù)約的人數(shù),它是接口異步加載完才展示的。如果加載完就立即設(shè)置進(jìn)度條寬度會(huì)顯得生硬無(wú)趣,但是如果加上一點(diǎn)動(dòng)畫效果的話就好多了。然而問(wèn)題又來(lái)了,如果加上動(dòng)畫那么邏輯上這個(gè)進(jìn)度條應(yīng)該是一點(diǎn)點(diǎn)的增加,對(duì)應(yīng)的人數(shù)也應(yīng)該是逐個(gè)增加。于是我就做了個(gè)優(yōu)化,讓人數(shù)在這段時(shí)間內(nèi)均勻的增加。這個(gè)細(xì)節(jié)并不是很容易被人發(fā)現(xiàn),但是這種設(shè)計(jì)會(huì)讓用戶感覺很用心而且有意思。

JavaScript

單品頁(yè)的腳本加載/執(zhí)行順序:

等待頁(yè)面準(zhǔn)備就緒(DOM Ready)

準(zhǔn)備就緒后加載入口腳本(main.js),腳本負(fù)責(zé)其它功能模塊的調(diào)度,動(dòng)態(tài)接合模塊通過(guò) seajs 的 require.async 方法異步調(diào)用

公共模塊(common.js)負(fù)責(zé)加初始化全局變量并掛載到 pageConfig 命名空間

動(dòng)態(tài)模塊數(shù)組,這個(gè)是后端通過(guò)程序判斷處理生成的一個(gè)模塊名列表。一般只包含首屏需要加載的模塊

后加載模塊(lazyinit.js)初始化,這個(gè)腳本只做一些頁(yè)面滾動(dòng)才加載的模塊事件綁定。當(dāng)模塊出現(xiàn)在視口內(nèi)再使用 require.async 異步加載模塊的資源及初始化

入口腳本

大致代碼如下

/**
* 模塊入口(1. 公共腳本 2. 首屏模塊資源 3. 非首屏「后加載模塊」)
*/
var entries = [];

// 頁(yè)面公共腳本樣式
entries.push("common");
// 頁(yè)面使用到的首屏模塊(后端開發(fā)根據(jù)頁(yè)面不同配置需要調(diào)用的模塊)
entries = entries.concat(config.modules);
// 非首屏「后加載模塊」
entries.push("lazyinit");

for (var i = 0; i < entries.length; i++) {
    entries[i] = "MOD_ROOT/" + entries[i] + "/" + entries[i];
}

if (/debug=show_modules/.test(location.href)) console.log(entries);

require.async(entries, function() {
    var modules = Array.prototype.slice.call(arguments);
    var len = modules.length;

    for (var i = 0; i < len; i++) {
        var module = modules[i];

        if (module && typeof module.init === "function") {
            module.init(config);
        } else {
            console.warn("Module[%s] must be exports a init function.", entries[i]);
        }
    }
});

注意模塊路徑中的 MOD_ROOT 是提前在頁(yè)面定義好的一個(gè) seajs path。目的是為了把前端版本號(hào)更新的控制權(quán)釋放給后端,從而解決了前后端依賴上線不同步造成的緩存延遲問(wèn)題,配置腳本中只有幾個(gè)定義好的路徑:

seajs.config({
    paths: {
        "MISC" : "http://misc.360buyimg.com",
        "MOD_ROOT" : "http://static.360buyimg.com/item/default/1.0.12/components",
        "PLG_ROOT" : "http://static.360buyimg.com/item/default/1.0.12/components/common/plugins",
        "JDF_UI"   : "http://misc.360buyimg.com/jdf/1.0.0/ui",
        "JDF_UNIT" : "http://misc.360buyimg.com/jdf/1.0.0/unit"
    }
});

還有一點(diǎn),在測(cè)試環(huán)境的頁(yè)面中版本號(hào)(上面代碼中的 1.0.12 是一個(gè)全量的版本號(hào))是后端從 URL 上動(dòng)態(tài)讀取的(使用參數(shù)訪問(wèn)就可以命中對(duì)應(yīng)版本 item.jd.com/sku.html?version=1.0.12)。這樣以來(lái)測(cè)試環(huán)境上就可以并行測(cè)試不同版本的需求,而且互不影響。當(dāng)然如果不同版本的后端代碼也有修改的話這樣是不行的,因?yàn)楹蠖舜a也需要有個(gè)對(duì)應(yīng)的版本號(hào)。

不過(guò)我們已經(jīng)解決了這個(gè)問(wèn)題。后端會(huì)在測(cè)試環(huán)境里 動(dòng)態(tài)加載后端模板 并且可以做到版本號(hào)與前端一致。這樣以來(lái)配合 git 方便的分支策略就可以同時(shí)并行開發(fā)測(cè)試多個(gè)需求,不用多帶帶配多個(gè)測(cè)試環(huán)境。什么?你還在使用 SVN!哦。那當(dāng)我沒說(shuō)過(guò)。

事件處理模型

客戶端的 JavaScript 代碼基本上都是事件驅(qū)動(dòng)的,代碼的加載解析依賴于瀏覽器提供的 DOM 事件。比如 onload, mouseover, scroll 等。

事件驅(qū)動(dòng)的的模型特別適用于異步編程,而 JavaScript 天生就是異步,所有的異步操作行為都最終會(huì)在一個(gè)回調(diào)函數(shù)(callback)中觸發(fā)。

比如單品頁(yè)中價(jià)格接口,加載完成后需要更新 DOM 元素來(lái)展示實(shí)時(shí)價(jià)格;地區(qū)選擇接口加載完成后會(huì)更新配送信息、庫(kù)存/商品狀態(tài)等,偽代碼如下:

/* onPriceReady 和 onAreaChange 可以認(rèn)為都是一個(gè) Ajax 異步函數(shù)調(diào)用
 * code 1 和 code 2 執(zhí)行到的時(shí)間是不確定先后順序的
 */
/* prom.js */
onPriceReady(function(price) {
    // code 1
    $("#price").html(price);
});

/* address.js */
onAreaChange(function(area) {
    // code 2
    $("#stock").html(area.stockInfo);
});

上面的兩段代碼分別在兩個(gè)腳本中維護(hù),因?yàn)樗麄兊倪壿嬒鄬?duì)獨(dú)立。早期并沒有關(guān)聯(lián)關(guān)系。后來(lái)需求有變,他們之間需要共享一些對(duì)方的數(shù)據(jù)(切換地區(qū)后需要重新獲取價(jià)格數(shù)據(jù)并展示)。但是物理上又不能放在一起通過(guò)使用全局變量的方式共享,而且它們都是異步加載接口后才取到數(shù)據(jù)的,并不好確定誰(shuí)先誰(shuí)后(非要做到那就只能用全局變量雙向判斷)。所以這樣并不能很好的解決問(wèn)題,而且代碼的耦合度會(huì)成倍增加。

這時(shí)候我們引入了一種設(shè)計(jì)模式來(lái)解決這種問(wèn)題 —— 發(fā)布者/訂閱者,我們把這種模式抽象成了自定義事件代碼來(lái)解決這一問(wèn)題。這段代碼是由 YUI 核心開發(fā)者 Nicholas C. Zakas 實(shí)現(xiàn)的。代碼很簡(jiǎn)單,事件對(duì)象主要有兩個(gè)方法 addListener(type, listener)fire(event)

于是我們重構(gòu)了上面的偽代碼:

/* prom.js */
// 在代碼中注冊(cè)一個(gè)地區(qū)變化事件,獲取變化后的地區(qū) id
// 然后重新請(qǐng)求價(jià)格接口并展示
Event.addListener("onAreaChange", function(data) {
    getAreaPrice(data.areaIds)
});

onPriceReady(function(price) {
    $("#price").html(price);

    Event.fire({
        type: "onPriceReady",
        data: "Any data you want"
    })
});

/* address.js */
onAreaChange(function(area) {
    $("#stock").html(area.stockInfo);

    // 在地區(qū)變化后除了做自己該做的事情以外
    // 觸發(fā)一個(gè)名為 onAreaChange 的事件,用來(lái)
    // 通知其它訂閱者事件完成,并傳遞地區(qū)相關(guān)參數(shù)
    // 這個(gè)時(shí)候在 onAreaChange Ajax 回調(diào)函數(shù)
    // 中就只需要關(guān)心自己的邏輯,其它模塊的耦合關(guān)系
    // 交給它們自己通過(guò)訂閱事件來(lái)處理
    Event.fire({
        type: "onAreaChange",
        data: area.ids
    })
});

需要注意的一點(diǎn)是,必須確保事件先注冊(cè)后觸發(fā)執(zhí)行,也就是說(shuō)先 addListener, 再 fire。

一些典型的性能優(yōu)化點(diǎn)

基本上客戶端的 JavaScript 性能問(wèn)題都來(lái)自于 DOM 查找和遍歷,在用于的時(shí)候一定要小心,可能不經(jīng)意的一個(gè)操作就會(huì)損失很多性能,尤其在低端瀏覽器中。順便多說(shuō)一點(diǎn),現(xiàn)代的 JavaScript 解釋器本身是很快的,語(yǔ)言層面的性能問(wèn)題很少遇到。DOM 查找慢是因?yàn)?瀏覽器給 JavaScript 訪問(wèn)頁(yè)面提供的一套 DOM API 本身慢。

緩存 DOM 查找,同時(shí) DOM 查找不要超過(guò) 2000 個(gè),低級(jí)瀏覽器會(huì)卡頓

不要使用鏈?zhǔn)秸{(diào)用 find,如:find("li").find("a") 而是 find("li a")

在切換元素顯示狀態(tài)的時(shí)候,如果元素很多。優(yōu)先使用 show()/hide() 方法,而不是 css("display", "block/none") 前者有緩存,后者會(huì)強(qiáng)制觸發(fā) reflow

給節(jié)點(diǎn)添加 data-xx 屬性在存放一些數(shù)據(jù),通過(guò)使用 jQuery 的 data("xx") 方法取更高效,減少 DOM 屬性訪問(wèn)

高密度事件(scroll, mousemove)觸發(fā)場(chǎng)景請(qǐng)使用節(jié)流方法

使用事件代理,而不是直接綁定。如果不確定代碼被調(diào)用次數(shù),可以先解除綁定再綁定具有命名空間的事件處理函數(shù)

盡量少用 DOM 動(dòng)畫,使用 CSS 3 動(dòng)畫代替

前端工程化 原由

前端工程化其實(shí)并不是最近兩年才有的概念。大約在 2013 年的時(shí)候 Grunt 問(wèn)世的時(shí)候就已經(jīng)有所涉及。這類打包工具主要的目的是自動(dòng)化一些開發(fā)流程,我最早使用 Grunt 來(lái)構(gòu)建代碼的時(shí)候只解決了三個(gè)問(wèn)題:

合并壓縮優(yōu)化樣式腳本

上線完自動(dòng)備份

單個(gè)文件打包到多目錄(歷史原因一個(gè)文件線上的路徑有兩種,需要傳兩個(gè)目錄)

當(dāng)時(shí)我還在組內(nèi)做過(guò)一個(gè)分享,有興趣的可以去圍觀一下 Best Workflow With Grunt。

其實(shí)這些工具出現(xiàn)的原因是:當(dāng)時(shí)前端領(lǐng)域的各種基礎(chǔ)設(shè)施很缺乏,而前端的工作內(nèi)容又相對(duì)零散。工作時(shí)需要開很多的軟件。再加上 JavaScript 語(yǔ)言本身也很弱,就連包管理這種基礎(chǔ)的東西也沒有內(nèi)置,以至于模塊化要通過(guò)一些第三方類庫(kù)來(lái)實(shí)現(xiàn),比如:RequireJS, SesJS。

工具的重要性可以在我之前的一個(gè)分享中找到 前端開發(fā)工具系列。

現(xiàn)狀

如今前端工程的生態(tài)環(huán)境由于 NodeJS 的出現(xiàn)已經(jīng)變得很好了。你可以根據(jù)自己的需求選一個(gè)合適的直接用到項(xiàng)目里面。像 Grunt, Gulp, browserify, webpack 等。不過(guò)要明白這些工具的出現(xiàn)從另一方面證明了前端開發(fā)天生存在很多的問(wèn)題:

HTML 從誕生到 HTML 5 之前幾乎沒有任何變化,DOM 性能天生缺失。所以才有了 Virtual DOM 這種東西

CSS 只是一門描述型的語(yǔ)言,沒有變量、邏輯控制、語(yǔ)句。所以才出現(xiàn)了 Sass, Less 這種預(yù)編譯工具

JavaScript 號(hào)稱「高階的(high-level)、動(dòng)態(tài)的(dynamic)、弱類型的(untyped)解釋型(interpreted)編程語(yǔ)言,適合面向?qū)ο螅╫op)和函數(shù)式的(functional)編程風(fēng)格」的編程語(yǔ)言,但是語(yǔ)言本身有很多問(wèn)題(ES 6 之前)。不適合大型項(xiàng)目的開發(fā)、沒有一些高級(jí)特性的支持、同時(shí)被其它語(yǔ)言詬病的 callback 風(fēng)格、單線程執(zhí)行等。所以才出現(xiàn)了像 TypeScript, Babel 這種編譯成 JavaScript 代碼的語(yǔ)言

這些問(wèn)題幾乎都是歷史性的原因和兼容性因素造成的。作為一名好的前端工程師要看清楚現(xiàn)狀,然后按自己項(xiàng)目的需求去定制一些前端工程化的方案,而不是隨波逐流。

選擇

其實(shí)現(xiàn)在自己開發(fā)一套前端工程化/自動(dòng)化流程的成本已經(jīng)很低了,你只需要學(xué)習(xí)一些 NodeJS 的知識(shí),配合 NPM 包管理機(jī)制,隨手就搞出一個(gè)構(gòu)建工具出來(lái)。因?yàn)椴⒉恍枰闳?shí)現(xiàn)什么東西,所有的東西都有現(xiàn)成的包。腳本壓縮有 UglifyJS,CSS 優(yōu)化有 CSS-min,圖片壓縮優(yōu)化有 PNG-quant 等等。你只需要想清楚自己要達(dá)到什么目的,解決什么問(wèn)題就可以抄家伙自己寫一套工作流出來(lái)。

我自己的經(jīng)歷也從 Grunt, GulpJS 到現(xiàn)在自造輪子。自己根據(jù)需求開發(fā)出來(lái)一套集成的打包工具,有興趣的可以去圍觀一下 Wooo。

當(dāng)然你也可以不用任何打包工具,自己寫一些 NPM Script 來(lái)完全定制化項(xiàng)目開發(fā)/測(cè)試/打包流程。我猜這也是為什么現(xiàn)在類似 Grunt 不再那么火,Gulp 遲遲沒有發(fā)布 4.0 版本的原因。寫一個(gè)構(gòu)建工具的成本太低了,而且這種集成的工具很難滿足差異的開發(fā)需求。君不知已有人意識(shí)到了這一點(diǎn)么why-i-left-gulp-and-grunt-for-npm-scripts。

程序、設(shè)計(jì)、產(chǎn)品

我始終認(rèn)為程序、設(shè)計(jì)是為了產(chǎn)品服務(wù)的。好的產(chǎn)品是要重視設(shè)計(jì)的,好的(前端)工程師是要有一些審美素養(yǎng)。

其實(shí)很多時(shí)候技術(shù)解決方案都是要根據(jù)產(chǎn)品的定位來(lái)設(shè)計(jì)的,了解產(chǎn)品需求以后才能定制出真正合適的高效的解決方案。好比前面講到的那個(gè) sprite 案例,如果一開始就和產(chǎn)品討論好方案后來(lái)也不可能有那種失控的情況發(fā)生。在產(chǎn)品形成/上線前期能發(fā)現(xiàn)問(wèn)題比上線后發(fā)現(xiàn)問(wèn)題更容易解決。

這部分內(nèi)容和代碼無(wú)關(guān),就不多說(shuō)了。然而早年我還有一次分享關(guān)于前端、改變。

總結(jié)

關(guān)于單品頁(yè)的前端開發(fā)本篇文章只是冰山一角,還有很多沒有提及,每個(gè)小東西都可以多帶帶寫一篇文章來(lái)分享。隨后希望可以有更多的總結(jié)和分享。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/90902.html

相關(guān)文章

  • node實(shí)現(xiàn)文件下載不得不說(shuō)那些事兒

    摘要:如果像本例中這樣的場(chǎng)景會(huì)遇到這樣一個(gè)問(wèn)題,詳見鏈接當(dāng)請(qǐng)求參數(shù)過(guò)長(zhǎng)或?yàn)榱税踩?,就需要用到下載。寫到這里自己都忍不住想錘自己,給自己挖坑不說(shuō),這樣來(lái)回請(qǐng)求下載,流量,真的是敗家。 這幾天一直在做遠(yuǎn)程文件下載的事,現(xiàn)在總算有了解決,特來(lái)記錄一下踩過(guò)的坑和想揍自己的心 需求 應(yīng)用場(chǎng)景是這樣的,底層邏輯數(shù)據(jù)請(qǐng)求接口是由Java寫的,也就是說(shuō)原始文件存在Java服務(wù)端,返回時(shí)有加密措施 由于工作...

    Coly 評(píng)論0 收藏0
  • “大促”背后技術(shù) | 當(dāng)我們說(shuō)促銷時(shí)候,我們?cè)谡勈裁矗?/b>

    摘要:郭理靖表示,在京東商城的實(shí)踐中,針對(duì)線上系統(tǒng)選擇構(gòu)建兩個(gè)機(jī)房,分別是生產(chǎn)環(huán)境以及在災(zāi)備環(huán)境。在監(jiān)控引擎方面,京東云的嘗試也是比較細(xì)致的,其中包括監(jiān)控服務(wù)報(bào)警服務(wù)等。進(jìn)一步,根據(jù)不同的報(bào)警,我們可以定位到 showImg(https://segmentfault.com/img/bVbtNqp?w=688&h=113); showImg(https://segmentfault.com/...

    20171112 評(píng)論0 收藏0
  • “大促”背后技術(shù) | 當(dāng)我們說(shuō)促銷時(shí)候,我們?cè)谡勈裁矗?/b>

    摘要:郭理靖表示,在京東商城的實(shí)踐中,針對(duì)線上系統(tǒng)選擇構(gòu)建兩個(gè)機(jī)房,分別是生產(chǎn)環(huán)境以及在災(zāi)備環(huán)境。在監(jiān)控引擎方面,京東云的嘗試也是比較細(xì)致的,其中包括監(jiān)控服務(wù)報(bào)警服務(wù)等。進(jìn)一步,根據(jù)不同的報(bào)警,我們可以定位到 showImg(https://segmentfault.com/img/bVbtNqp?w=688&h=113); showImg(https://segmentfault.com/...

    張巨偉 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<