整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          聊聊JS模塊化發(fā)展歷程

          聊聊JS模塊化發(fā)展歷程

          引言

          如今,Javascript 模塊化規(guī)范非常方便、自然,但這個(gè)新規(guī)范僅執(zhí)行了2年,就在 4 年前,js 的模塊化還停留在運(yùn)行時(shí)支持,10 年前,通過后端模版定義、注釋定義模塊依賴。對(duì)經(jīng)歷過來的人來說,歷史的模塊化方式還停留在腦海中,反而新上手的同學(xué)會(huì)更快接受現(xiàn)代的模塊化規(guī)范。

          但為什么要了解 Javascript 模塊化發(fā)展的歷史呢?因?yàn)榉彩露加袃擅嫘裕私?Javascript 模塊化規(guī)范,有利于我們思考出更好的模塊化方案,縱觀歷史,從 1999 年開始,模塊化方案最多維持兩年,就出現(xiàn)了新的替代方案,比原有的模塊化更清晰、強(qiáng)壯,我們不能被現(xiàn)代模塊化方式限制住思維,因?yàn)楝F(xiàn)在的 ES2015 模塊化方案距離發(fā)布也僅僅過了兩年。

          2 內(nèi)容概要

          直接定義依賴 (1999): 由于當(dāng)時(shí) js 文件非常簡單,模塊化方式非常簡單粗暴 —— 通過全局方法定義、引用模塊。這種定義方式與現(xiàn)在的 commonjs 非常神似,區(qū)別是 commonjs 以文件作為模塊,而這種方法可以在任何文件中定義模塊,模塊不與文件關(guān)聯(lián)。

          閉包模塊化模式 (2003): 用閉包方式解決了變量污染問題,閉包內(nèi)返回模塊對(duì)象,只需對(duì)外暴露一個(gè)全局變量。

          模版依賴定義 (2006): 這時(shí)候開始流行后端模版語法,通過后端語法聚合 js 文件,從而實(shí)現(xiàn)依賴加載,說實(shí)話,現(xiàn)在 go 語言等模版語法也很流行這種方式,寫后端代碼的時(shí)候不覺得,回頭看看,還是掛在可維護(hù)性上。

          注釋依賴定義 (2006): 幾乎和模版依賴定義同時(shí)出現(xiàn),與 1999 年方案不同的,不僅僅是模塊定義方式,而是終于以文件為單位定義模塊了,通過 lazyjs 加載文件,同時(shí)讀取文件注釋,繼續(xù)遞歸加載剩下的文件。

          外部依賴定義 (2007): 這種定義方式在 cocos2d-js 開發(fā)中普遍使用,其核心思想是將依賴抽出單獨(dú)文件定義,這種方式不利于項(xiàng)目管理,畢竟依賴抽到代碼之外,我是不是得兩頭找呢?所以才有通過 webpack 打包為一個(gè)文件的方式暴力替換為 commonjs 的方式出現(xiàn)。

          Sandbox模式 (2009): 這種模塊化方式很簡單,暴力,將所有模塊塞到一個(gè) sanbox 變量中,硬傷是無法解決明明沖突問題,畢竟都塞到一個(gè) sandbox 對(duì)象里,而 Sandbox 對(duì)象也需要定義在全局,存在被覆蓋的風(fēng)險(xiǎn)。模塊化需要保證全局變量盡量干凈,目前為止的模塊化方案都沒有很好的做到這一點(diǎn)。

          依賴注入 (2009): 就是大家熟知的 angular1.0,依賴注入的思想現(xiàn)在已廣泛運(yùn)用在 react、vue 等流行框架中。但依賴注入和解決模塊化問題還差得遠(yuǎn)。

          CommonJS (2009): 真正解決模塊化問題,從 node 端逐漸發(fā)力到前端,前端需要使用構(gòu)建工具模擬。

          Amd (2009): 都是同一時(shí)期的產(chǎn)物,這個(gè)方案主要解決前端動(dòng)態(tài)加載依賴,相比 commonJs,體積更小,按需加載。

          Umd (2011): 兼容了 CommonJS 與 Amd,其核心思想是,如果在 commonjs 環(huán)境(存在 module.exports,不存在 define),將函數(shù)執(zhí)行結(jié)果交給 module.exports 實(shí)現(xiàn) Commonjs,否則用 Amd 環(huán)境的 define,實(shí)現(xiàn) Amd。

          Labeled Modules (2012): 和 Commonjs 很像了,沒什么硬傷,但生不逢時(shí),碰上 Commonjs 與 Amd,那只有被人遺忘的份了。

          YModules (2013): 既然都出了 Commonjs Amd,文章還列出了此方案,一定有其獨(dú)到之處。其核心思想在于使用 provide 取代 return,可以控制模塊結(jié)束時(shí)機(jī),處理異步結(jié)果;拿到第二個(gè)參數(shù) module,修改其他模塊的定義(雖然很有拓展性,但用在項(xiàng)目里是個(gè)攪屎棍)。

          ES2015 Modules (2015): 就是我們現(xiàn)在的模塊化方案,還沒有被瀏覽器實(shí)現(xiàn),大部分項(xiàng)目已通過 babel 或 typescript 提前體驗(yàn)。

          3 精讀

          從語言層面到文件層面的模塊化

          從 1999 年開始,模塊化探索都是基于語言層面的優(yōu)化,真正的革命從 2009 年 CommonJS 的引入開始,前端開始大量使用預(yù)編譯。

          這篇文章所提供的模塊化歷史的方案都是邏輯模塊化,從 CommonJS 方案開始前端把服務(wù)端的解決方案搬過來之后,算是看到標(biāo)準(zhǔn)物理與邏輯統(tǒng)一的模塊化。但之后前端工程不得不引入模塊化構(gòu)建這一步。正是這一步給前端開發(fā)無疑帶來了諸多的不便,尤其是現(xiàn)在我們開發(fā)過程中經(jīng)常為了優(yōu)化這個(gè)工具帶了很多額外的成本。

          從 CommonJS 之前其實(shí)都只是封裝,并沒有一套模塊化規(guī)范,這個(gè)就有些像類與包的概念。我在10年左右用的最多的還是 YUI2,YUI2 是用 namespace 來做模塊化的,但有很多問題沒有解決,比如多版本共存,因此后來 YUI3 出來了。

          YUI().use('node', 'event', function (Y) {
           // The Node and Event modules are loaded and ready to use.
           // Your code goes here!
          });
          

          YUI3 的 sandbox 像極了差不多同時(shí)出現(xiàn)的 AMD 規(guī)范,但早期 yahoo 在前端圈的影響力還是很大的,而 requirejs 到 2011 年才誕生,因此圈子不是用著 YUI 要不就自己封裝一套 sandbox,內(nèi)部使用 jQuery。

          為什么模塊化方案這么晚才成型,可能早期應(yīng)用的復(fù)雜度都在后端,前端都是非常簡單邏輯。后來 Ajax 火了之后,web app 概念的開始流行,前端的復(fù)雜度也呈指數(shù)級(jí)上漲,到今天幾乎和后端接近一個(gè)量級(jí)。工程發(fā)展到一定階段,要出現(xiàn)的必然會(huì)出現(xiàn)。

          前端三劍客的模塊化展望

          從 js 模塊化發(fā)展史,我們還看到了 css html 模塊化方面的嚴(yán)重落后,如今依賴編譯工具的模塊化增強(qiáng)在未來會(huì)被標(biāo)準(zhǔn)所替代。

          原生支持的模塊化,解決 html 與 css 模塊化問題正是以后的方向。

          再回到 JS 模塊化這個(gè)主題,開頭也說到是為了構(gòu)建 scope,實(shí)則提供了業(yè)務(wù)規(guī)范標(biāo)準(zhǔn)的輸入輸出的方式。但文章中的 JS 的模塊化還不等于前端工程的模塊化,Web 界面是由 HTML、CSS 和 JS 三種語言實(shí)現(xiàn),不論是 CommonJS 還是 AMD 包括之后的方案都無法解決 CSS 與 HTML 模塊化的問題。

          對(duì)于 CSS 本身它就是 global scope,因此開發(fā)樣式可以說是喜憂參半。近幾年也涌現(xiàn)把 HTML、CSS 和 JS 合并作模塊化的方案,其中 react/css-modules 和 vue 都為人熟知。當(dāng)然,這一點(diǎn)還是非常依賴于 webpack/rollup 等構(gòu)建工具,讓我們意識(shí)到在 browser 端還有很多本質(zhì)的問題需要推進(jìn)。

          對(duì)于 css 模塊化,目前不依賴預(yù)編譯的方式是 styled-component,通過 js 動(dòng)態(tài)創(chuàng)建 class。而目前 css 也引入了與 js 通信的機(jī)制 與 原生變量支持。未來 css 模塊化也很可能是運(yùn)行時(shí)的,所以目前比較看好 styled-component 的方向。

          對(duì)于 html 模塊化,小尤最近爆出與 chrome 小組調(diào)研 html Modules,如果 html 得到了瀏覽器,編輯器的模塊化支持,未來可能會(huì)取代 jsx 成為最強(qiáng)大的模塊化、模板語言。

          對(duì)于 js 模塊化,最近出現(xiàn)的 <script type="module"> 方式,雖然還沒有得到瀏覽器原生支持,但也是我比較看好的未來趨勢(shì),這樣就連 webpack 的拆包都不需要了,直接把源代碼傳到服務(wù)器,配合 http2.0 完美拋開預(yù)編譯的枷鎖。

          上述三中方案都不依賴預(yù)編譯,分別實(shí)現(xiàn)了 html、css、js 模塊化,相信這就是未來。

          模塊化標(biāo)準(zhǔn)推進(jìn)速度仍然緩慢

          2015 年提出的標(biāo)準(zhǔn),在 17 年依然沒有得到實(shí)現(xiàn),即便在 nodejs 端。

          這幾年 TC39 對(duì)語言終于重視起來了,慢慢有動(dòng)作了,但針對(duì)模塊標(biāo)準(zhǔn)制定的速度,與落實(shí)都非常緩慢,與 javascript 越來越流行的趨勢(shì)逐漸脫節(jié)。nodejs 至今也沒有實(shí)現(xiàn) ES2015 模塊化規(guī)范,所有 jser 都處在構(gòu)建工具的陰影下。

          Http 2.0 對(duì) js 模塊化的推動(dòng)

          js 模塊化定義的再美好,瀏覽器端的支持粒度永遠(yuǎn)是瓶頸,http 2.0 正是考慮到了這個(gè)因素,大力支持了 ES 2015 模塊化規(guī)范。

          幸運(yùn)的是,模塊化構(gòu)建將來可能不再需要。隨著 HTTP/2 流行起來,請(qǐng)求和響應(yīng)可以并行,一次連接允許多個(gè)請(qǐng)求,對(duì)于前端來說宣告不再需要在開發(fā)和上線時(shí)再做編譯這個(gè)動(dòng)作。

          幾年前,模塊化幾乎是每個(gè)流行庫必造的輪子(YUI、Dojo、Angular),大牛們自己爽的同時(shí)其實(shí)造成了社區(qū)的分裂,很難積累。有了 ES2015 Modules 之后,JS 開發(fā)者終于可以像 Java 開始者十年前一樣使用一致的方式愉快的互相引用模塊。

          不過 ES2015 Modules 也只是解決了開發(fā)的問題,由于瀏覽器的特殊性,還是要經(jīng)過繁瑣打包的過程,等 Import,Export 和 HTTP 2.0 被主流瀏覽器支持,那時(shí)候才是徹底的模塊化。

          Http 2.0 后就不需要構(gòu)建工具了嗎?

          看到大家基本都提到了 HTTP/2,對(duì)這項(xiàng)技術(shù)解決前端模塊化及資源打包等工程問題抱有非常大的期待。很多人也認(rèn)為 HTTP/2 普及后,基本就沒有 Webpack 什么事情了。

          不過 Webpack 作者 @sokra 在他的文章 webpack & HTTP/2 里提到了一個(gè)新的 Webpack 插件 AggressiveSplittingPlugin。簡單的說,這款插件就是為了充分利用 HTTP/2 的文件緩存能力,將你的業(yè)務(wù)代碼自動(dòng)拆分成若干個(gè)數(shù)十 KB 的小文件。后續(xù)若其中任意一個(gè)文件發(fā)生變化,可以保證其他的小 chunck 不需要重新下載。

          可見,即使不斷的有新技術(shù)出現(xiàn),也依然需要配套的工具來將前端工程問題解決方案推向極致。

          模塊化是大型項(xiàng)目的銀彈嗎?

          只要遵循了最新模塊化規(guī)范,就可以使項(xiàng)目具有最好的可維護(hù)性嗎? Js 模塊化的目的是支持前端日益上升的復(fù)雜度,但絕不是唯一的解決方案。

          分析下 JavaScript 為什么沒有模塊化,為什么又需要模塊化:這個(gè) 95 年被設(shè)計(jì)出來的時(shí)候,語言的開發(fā)者根本沒有想到它會(huì)如此的大放異彩,也沒有將它設(shè)計(jì)成一種模塊化語言。按照文中的說法,99 年也就是 4 年后開始出現(xiàn)了模塊化的需求。如果只有幾行代碼用模塊化是扯,初始的 web 開發(fā)業(yè)務(wù)邏輯都寫在 server 端,js 的作用小之又小。而現(xiàn)在 spa 都出現(xiàn)了,幾乎所有的渲染邏輯都在前端,如果還是沒有模塊化的組織,開發(fā)過程會(huì)越來越難,維護(hù)也是更痛苦。

          本文中已經(jīng)詳細(xì)說明了模塊化的發(fā)展和優(yōu)劣,這里不準(zhǔn)備做過多的贅述。但還有一個(gè)問題需要我們?nèi)リP(guān)注,那就是在模塊化之后還有一個(gè)模塊間耦合的問題,如果模塊間耦合度大也會(huì)降低代碼的可重用性或者說復(fù)用性。所以也出現(xiàn)了降低耦合的觀察者模式或者發(fā)布/訂閱模式。這對(duì)于提升代碼重用,復(fù)用性和避免單點(diǎn)故障等都很重要。說到這里,還想順便提一下最近流行起來的響應(yīng)式編程(RxJS),響應(yīng)式編程中有一個(gè)很核心的概念就是 observable,也就是 Rx 中的流(stream)。它可以被 subscribe,其實(shí)也就是觀察者設(shè)計(jì)模式。

          總結(jié)

          未來前端復(fù)雜度不斷增加已成定論,隨著后端成熟,自然會(huì)將焦點(diǎn)轉(zhuǎn)移到前端領(lǐng)域,而且服務(wù)化、用戶體驗(yàn)越來越重要,前端體驗(yàn)早不是當(dāng)初能看就行,任何網(wǎng)頁的異常、視覺的差異,或文案的模糊,都會(huì)導(dǎo)致用戶流失,支付中斷。前端對(duì)公司營收的影響,漸漸與后端服務(wù)宕機(jī)同等嚴(yán)重,所以前端會(huì)越來越重,異常監(jiān)控,性能檢測(cè),工具鏈,可視化等等都是這幾年大家逐漸重視起來的。

          我們?cè)缫巡荒軐?javascript 早期玩具性質(zhì)的模塊化方案用于現(xiàn)代越來越重要的系統(tǒng)中,前端界必然出現(xiàn)同等重量級(jí)的模塊化管理方案,感謝 TC39 制定的 ES2015 模塊化規(guī)范,我們已經(jīng)離不開它,哪怕所有人必須使用 babel。

          話說回來,標(biāo)準(zhǔn)推進(jìn)的太慢,我們還是把編譯工具當(dāng)作常態(tài),抱著哪怕支持了 ES2015 所有特性,babel 依然還有用的心態(tài),將預(yù)編譯進(jìn)行到底。一句話,模塊化仍在路上。js 模塊化的矛頭已經(jīng)對(duì)準(zhǔn)了 css 與 html,這兩位元老也該向前衛(wèi)的 js 學(xué)習(xí)學(xué)習(xí)了。

          未來 css、html 的模塊化會(huì)自立門戶,還是賦予 js 更強(qiáng)的能力,讓兩者的模塊化依附于 js 的能力呢?目前 html 有自立門戶的苗頭(htmlModules),而 css 遲遲沒有改變,社區(qū)出現(xiàn)的 styled-component 已經(jīng)用 js 將 css 模塊化得很好了,最新 css 規(guī)范也支持了與 js 的變量通信,難道希望依附于 js 嗎?這里希望得到大家更廣泛的討論。

          我也認(rèn)同,畢竟壓縮、混淆、md5、或者利用 nonce 屬性對(duì) script 標(biāo)簽加密,都離不開本地構(gòu)建工具。

          據(jù)說 http2 的優(yōu)化中,有個(gè)最佳文件大小與數(shù)量的比例,那么還是脫離不了構(gòu)建工具,前端未來會(huì)越來越復(fù)雜,同時(shí)也越來越美好。

          至此,對(duì)于 javascript 模塊化討論已接近尾聲,對(duì)其優(yōu)缺點(diǎn)也基本達(dá)成了一致。前端復(fù)雜度不斷提高,促使著模塊化的改進(jìn),代理(瀏覽器、node) 的支持程度,與前端特殊性(流量、緩存)可能前端永遠(yuǎn)也離不開構(gòu)建工具,新的標(biāo)準(zhǔn)會(huì)讓這些工作做的更好,同時(shí)取代、增強(qiáng)部分特征,前端的未來是更加美好的,復(fù)雜度也更高。

          avaScript 可以說是交互之王,它作為腳本語言加上許多 Web Api 進(jìn)一步擴(kuò)展了它的特性集,更加豐富界面交互的可操作性。這類 API 的例子包括WebGL API、Canvas API、DOM API,還有一組不太為人所知的 CSS API

          由于JSX和無數(shù)JS框架的出現(xiàn),使通過JS APIDOM交互的想法真正流行起來,但是在 CSS 中使用類似技術(shù)似乎并沒有很多。當(dāng)然,存在像CSS-in-JS這類解決方案,但是最流行的解決方案還是基于轉(zhuǎn)譯(transpilation),無需額外運(yùn)行即可生成 CSS。這肯定對(duì)性能有好處,因?yàn)?strong>CSS API的使用可能導(dǎo)致額外的重繪,這與DOM API的使用一樣。但這不是咱們想要的。如果哪天公司要求咱們,既要操縱 DOM 元素的樣式和 CSS 類,還要像使用 HTML 一樣使用 JS 創(chuàng)建完整的樣式表,該怎么辦?

          內(nèi)聯(lián)樣式

          在咱們深入一些復(fù)雜的知識(shí)之前,先回來顧一下一些基礎(chǔ)知識(shí)。例如,咱們可以通過修改它的.style屬性來編輯給定的HTMLElement的內(nèi)聯(lián)樣式。

          const el=document.createElement('div')
          
          el.style.backgroundColor='red'
          // 或者 
          el.style.cssText='background-color: red'
          // 或者
          el.setAttribute('style', 'background-color: red')
          

          直接在.style對(duì)象上設(shè)置樣式屬性將需要使用駝峰式命名作為屬性鍵,而不是使用短橫線命名。如果咱們需要設(shè)置更多的內(nèi)聯(lián)樣式屬性,則可以通過設(shè)置.style.cssText屬性,以更加高效的方式進(jìn)行設(shè)置 。

          我自己是一名從事了多年開發(fā)的web前端老程序員,目前辭職在做自己的web前端私人定制課程,今年年初我花了一個(gè)月整理了一份最適合2019年學(xué)習(xí)的web前端學(xué)習(xí)干貨,各種框架都有整理,送給每一位前端小伙伴,想要獲取的可以關(guān)注我的頭條號(hào)并在后臺(tái)私信我:前端,即可免費(fèi)獲取

          請(qǐng)記住,給cssText設(shè)置后原先的css樣式被清掉了,因此,要求咱們一次死一堆樣式 。

          如果這種設(shè)置內(nèi)聯(lián)樣式過于繁瑣,咱們還可以考慮將.style與Object.assign()一起使用,以一次設(shè)置多個(gè)樣式屬性。

          // ...
          Object.assign(el.style, {
           backgroundColor: "red",
           margin: "25px"
          })
          

          這些“基本知識(shí)”比咱們想象的要多得多。.style對(duì)象實(shí)現(xiàn)CSSStyleDeclaration接口。這說明它帶還有一些有趣的屬性和方法,這包括剛剛使用的.cssText,還包括.length(設(shè)置屬性的數(shù)量),以及.item()、.getPropertyValue()和.setPropertyValue()之類的方法:

          // ...
          const propertiesCount=el.style.length
          for(let i=0; i < propertiesCount; i++) {
           const name=el.style.item(i) // 'background-color'
           const value=el.style.getPropertyValue(name) // 're'
           const priority=el.style.getPropertyPriority(name) // 'important'
          
           if(priority==='important') {
           el.style.removeProperty()
           }
          }
          

          這里有個(gè)小竅門-在遍歷過程中.item()方法具有按索引訪問形式的備用語法。

          // ...
          el.style.item(0)===el.style[0]; // true
          

          CSS 類

          接著,來看看更高級(jí)的結(jié)構(gòu)——CSS類,它在檢索和設(shè)置時(shí)具有字符串形式是.classname。

          // ...
          el.className="class-one class-two";
          el.setAttribute("class", "class-one class-two");
          

          設(shè)置類字符串的另一種方法是設(shè)置class屬性(與檢索相同)。但是,就像使用.style.cssText屬性一樣,設(shè)置.className將要求咱們?cè)谧址邪ńo定元素的所有類,包括已更改和未更改的類。

          當(dāng)然,可以使用一些簡單的字符串操作來完成這項(xiàng)工作,還有一種就是使用較新的.classList屬性,這個(gè)屬性,IE9 不支持它,而 IE10 和 IE11 僅部分支持它。

          classlist屬性實(shí)現(xiàn)了DOMTokenList,有一大堆有用的方法。例如.add()、.remove()、.toggle()和.replace()允許咱們更改當(dāng)前的 CSS 類集合,而其他的,例如.item()、.entries()或.foreach()則可以簡化這個(gè)索引集合的遍歷過程。

          // ...
          const classNames=["class-one", "class-two", "class-three"];
          classNames.forEach(className=> {
           if(!el.classList.contains(className)) {
           el.classList.add(className);
           }
          });
          

          Stylesheets

          一直以來,Web Api 還有一個(gè)StyleSheetList接口,該接口由document.styleSheets屬性實(shí)現(xiàn)。document.styleSheets 只讀屬性,返回一個(gè)由 StyleSheet 對(duì)象組成的 StyleSheetList,每個(gè) StyleSheet 對(duì)象都是一個(gè)文檔中鏈接或嵌入的樣式表。

          for(styleSheet of document.styleSheets){
           console.log(styleSheet);
          }
          

          通過打印結(jié)果咱們可以知道,每次循環(huán)打印的是 CSSStyleSheet 對(duì)象,每個(gè) CSSStyleSheet 對(duì)象由下列屬性組成:

          屬性描述media獲取當(dāng)前樣式作用的媒體。disabled打開或禁用一張樣式表。href返回 CSSStyleSheet 對(duì)象連接的樣式表地址。title返回 CSSStyleSheet 對(duì)象的title值。type返回 CSSStyleSheet 對(duì)象的type值,通常是text/css。parentStyleSheet返回包含了當(dāng)前樣式表的那張樣式表。ownerNode返回CSSStyleSheet對(duì)象所在的DOM節(jié)點(diǎn),通常是<link>或<style>。cssRules返回樣式表中所有的規(guī)則。ownerRule如果是通過@import導(dǎo)入的,屬性就是指向表示導(dǎo)入的規(guī)則的指針,否則值為null。IE不支持這個(gè)屬性。

          ** CSSStyleSheet對(duì)象方法: **

          方法描述insertRule()在當(dāng)前樣式表的 cssRules 對(duì)象插入CSS規(guī)則。deleteRule()在當(dāng)前樣式表刪除 cssRules 對(duì)象的CSS規(guī)則。

          有了StyleSheetList的全部內(nèi)容,咱們來CSSStyleSheet本身。在這里就有點(diǎn)意思了, CSSStyleSheet擴(kuò)展了StyleSheet接口,并且只有這種只讀屬性,如.ownerNode,.href,.title或.type,它們大多直接從聲明給定樣式表的地方獲取?;叵胍幌录虞d外部CSS文件的標(biāo)準(zhǔn)HTML代碼,咱們就會(huì)明白這句話是啥意思:

          <head>
          <link rel="stylesheet" type="text/css" href="style.css" title="Styles">
          </head>
          

          現(xiàn)在,咱們知道HTML文檔可以包含多個(gè)樣式表,所有這些樣式表都可以包含不同的規(guī)則,甚至可以包含更多的樣式表(當(dāng)使用@import時(shí))。CSSStyleSheet有兩個(gè)方法:、.insertrule()和.deleterule() 來增加和刪除 Css 規(guī)則。

          // ...
          const ruleIndex=styleSheet.insertRule("div {background-color: red}");
          styleSheet.deleteRule(ruleIndex);
          

          .insertRule(rule,index):此函數(shù)可以在cssRules規(guī)則集合中插入一個(gè)指定的規(guī)則,參數(shù)rule是標(biāo)示規(guī)則的字符串,參數(shù)index是值規(guī)則字符串插入的位置。

          deleteRule(index):此函數(shù)可以刪除指定索引的規(guī)規(guī)則,參數(shù)index規(guī)定規(guī)則的索引。

          CSSStyleSheet也有自己的兩個(gè)屬性:.ownerRule和.cssRule。雖然.ownerRule與@import相關(guān),但比較有趣的是.cssRules。簡單地說,它是CSSRuleList的CSSRule,可以使用前面提到的.insertrule()和.deleterule()方法修改它。請(qǐng)記住,有些瀏覽器可能會(huì)阻止咱們從不同的來源(域)訪問外部CSSStyleSheet的.cssRules屬性。

          那么什么是 CSSRuleList?

          CSSRuleList是一個(gè)數(shù)組對(duì)象包含著一個(gè)有序的CSSRule對(duì)象的集合。每一個(gè)CSSRule可以通過rules.item(index)的形式訪問, 或者rules[index]。這里的rules是一個(gè)實(shí)現(xiàn)了CSSRuleList接口的對(duì)象, index是一個(gè)基于0開始的,順序與CSS樣式表中的順序是一致的。樣式對(duì)象的個(gè)數(shù)是通過rules.length表達(dá)。

          對(duì)于CSSStyleRule對(duì)象:

          每一個(gè)樣式表CSSStyleSheet對(duì)象可以包含若干CSSStyleRule對(duì)象,也就是css樣式規(guī)則,如下:

          <style type="text/css">
           h1{color:red}
           div{color:green}
          </style>
          

          在上面的代碼中style標(biāo)簽是一個(gè)CSSStyleSheet樣式表對(duì)象,這個(gè)樣式表對(duì)象包含兩個(gè)CSSStyleRule對(duì)象,也就是兩個(gè)css樣式規(guī)則。

          CSSStyleRule對(duì)象具有下列屬性:

          1.type:返回0-6的數(shù)字,表示規(guī)則的類型,類型列表如下:

          0:CSSRule.UNKNOWN_RULE。

          1:CSSRule.STYLE_RULE (定義一個(gè)CSSStyleRule對(duì)象)。

          2:CSSRule.CHARSET_RULE (定義一個(gè)CSSCharsetRule對(duì)象,用于設(shè)定當(dāng)前樣式表的字符集,默認(rèn)與當(dāng)前網(wǎng)頁相同)。

          3:CSSRule.IMPORT_RULE (定義一個(gè)CSSImportRule對(duì)象,就是用@import引入其他的樣式表)

          4:CSSRule.MEDIA_RULE (定義一個(gè)CSSMediaRule對(duì)象,用于設(shè)定此樣式是用于顯示器,打印機(jī)還是投影機(jī)等等)。

          5:CSSRule.FONT_FACE_RULE (定義一個(gè)CSSFontFaceRule對(duì)象,CSS3的@font-face)。

          6:CSSRule.PAGE_RULE (定義一個(gè)CSSPageRule對(duì)象)。

          2.cssText:返回一個(gè)字符串,表示的是當(dāng)前規(guī)則的內(nèi)容,例如:

          div{color:green}
          

          3.parentStyleSheet:返回所在的CSSStyleRule對(duì)象。

          4.parentRule:如果規(guī)則位于另一規(guī)則中,該屬性引用另一個(gè)CSSRule對(duì)象。

          5.selectorText:返回此規(guī)則的選擇器,如上面的div就是選擇器。

          6.style:返回一個(gè)CSSStyleDeclaration對(duì)象。

          // ...
          const ruleIndex=styleSheet.insertRule("div {background-color: red}");
          const rule=styleSheet.cssRules.item(ruleIndex);
          
          rule.selectorText; // "div"
          rule.style.backgroundColor; // "red"
          

          實(shí)現(xiàn)

          現(xiàn)在,咱們對(duì) CSS 相關(guān)的 JS Api有了足夠的了解,可以創(chuàng)建咱們自己的、小型的、基于運(yùn)行時(shí)的CSS-in-JS實(shí)現(xiàn)。咱們的想法是創(chuàng)建一個(gè)函數(shù),它傳遞一個(gè)簡單的樣式配置對(duì)象,生成一個(gè)新創(chuàng)建的CSS類的哈希名稱供以后使用。

          實(shí)現(xiàn)流程很簡單,咱們需要一個(gè)能夠訪問某種樣式表的函數(shù),并且只需使用.insertrule()方法和樣式配置就可以運(yùn)行了。先從樣式表部分開始:

          function createClassName(style) {
           // ...
           let styleSheet;
           for (let i=0; i < document.styleSheets.length; i++) {
           if (document.styleSheets[i].CSSInJS) {
           styleSheet=document.styleSheets[i];
           break;
           }
           }
           if (!styleSheet) {
           const style=document.createElement("style");
           document.head.appendChild(style);
           styleSheet=style.sheet;
           styleSheet.CSSInJS=true;
           }
           // ...
          }
          

          如果你使用的是ESM或任何其他類型的JS模塊系統(tǒng),則可以在函數(shù)外部安全地創(chuàng)建樣式表實(shí)例,而不必?fù)?dān)心其他人對(duì)其進(jìn)行訪問。但是,為了演示例,咱們將stylesheet上的.CSSInJS屬性設(shè)置為標(biāo)志的形式,通過標(biāo)志來判斷是否要使用它。

          現(xiàn)在,如果如果還需要?jiǎng)?chuàng)建一個(gè)新的樣式表怎么辦? 最好的選擇是創(chuàng)建一個(gè)新的<style/>標(biāo)記,并將其附加到HTML文檔的<head/>上。這會(huì)自動(dòng)將新樣式表添加到document.styleSheets列表,并允許咱們通過<style/>標(biāo)記的.sheet屬性對(duì)其進(jìn)行訪問,是不是很機(jī)智?

          function createRandomName() {
           const code=Math.random().toString(36).substring(7);
           return `css-${code}`;
          }
          
          function phraseStyle(style) {
           const keys=Object.keys(style);
           const keyValue=keys.map(key=> {
           const kebabCaseKey=key.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
           const value=`${style[key]}${typeof style[key]==="number" ? "px" : ""}`;
          
           return `${kebabCaseKey}:${value};`;
           });
          
           return `{${keyValue.join("")}}`;
          }
          

          除了上面的小竅門之外。自然,咱們首先需要一種為CSS類生成新的隨機(jī)名稱的方法。然后,將樣式對(duì)象正確地表達(dá)為可行的CSS字符串的形式。這包括駝峰命名和短橫線全名之間的轉(zhuǎn)換,以及可選的像素單位(px)轉(zhuǎn)換的處理。

          function createClassName(style) {
           const className=createRandomName();
           let styleSheet;
           // ...
           styleSheet.insertRule(`.${className}${phraseStyle(style)}`);
           return className;
          }
          

          完整代碼如下:

          HTML

          <div id="el"></div>
          

          JS

          function createRandomName() {
           const code=Math.random().toString(36).substring(7);
           return `css-${code}`;
          }
          
          function phraseStyle(style) {
           const keys=Object.keys(style);
           const keyValue=keys.map(key=> {
           const kebabCaseKey=key.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
           const value=`${style[key]}${typeof style[key]==="number" ? "px" : ""}`;
           return `${kebabCaseKey}:${value};`;
           });
           return `{${keyValue.join("")}}`;
          }
          
          function createClassName(style) {
           const className=createRandomName();
           let styleSheet;
           for (let i=0; i < document.styleSheets.length; i++) {
           if (document.styleSheets[i].CSSInJS) {
           styleSheet=document.styleSheets[i];
           break;
           }
           }
           if (!styleSheet) {
           const style=document.createElement("style");
           document.head.appendChild(style);
           styleSheet=style.sheet;
           styleSheet.CSSInJS=true;
           }
           styleSheet.insertRule(`.${className}${phraseStyle(style)}`);
           return className;
          }
          
          const el=document.getElementById("el");
          
          const redRect=createClassName({
           width: 100,
           height: 100,
           backgroundColor: "red"
          });
          
          el.classList.add(redRect);
          

          運(yùn)行效果:

          總結(jié)

          正如本文咱們所看到的,使用 JS 操作CSS 是一件非常有趣的事,咱們可以挖掘很多好用的 API,上面的例子只是冰山一角,在CSS API(或者更確切地說是API)中還有更多方法,它們正等著被揭開神秘面紗。



          原文:https://css-tricks.com/an-introduction-and-guide-to-the-css-object-model-cssom/

          雨青工作站發(fā)文地址:js html input file 類型 實(shí)現(xiàn)圖片上傳-白雨青工作站

          js html input file 類型 實(shí)現(xiàn)圖片上傳

          這里只單獨(dú)講如何用JS 把選中的文件轉(zhuǎn)成base64 ,然后輸出到前臺(tái)顯示

          直接上代碼:

          AddArticle.jsp

          <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
          <%
          String path=request.getContextPath();
          String basePath=request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
          %>
          
          <!DOCTYPE HTML>
          <html>
            <head>
              <base href="<%=basePath%>">
              
              <title>寫文章-白雨青工作站</title>
              
          	<meta http-equiv="pragma" content="no-cache">
          	<meta http-equiv="cache-control" content="no-cache">
          	<meta http-equiv="expires" content="0">    
          	<meta http-equiv="keywords" content="小說,我有一劍,Java,HTML,Java小工具,白雨青工作站,http://www.byqws.com:8080/byqws">
          	<meta name="description" content="小說,我有一劍,Java,HTML,Java小工具,白雨青工作站,http://www.byqws.com:8080/byqws/">
          	<!---->
          	
          	<link rel="shortcut icon" href="img/byqws-apple-touch-icon-16X16.png" />
          	<link rel="apple-touch-icon" href="img/byqws-apple-touch-icon-57X57.png" />
          	<link rel="apple-touch-icon" sizes="72x72" href="img/byqws-apple-touch-icon-72X72.png" />
          	<link rel="apple-touch-icon" sizes="114x114" href="img/byqws-apple-touch-icon-144X144.png" />
          	
          	<link rel="stylesheet" type="text/css" href="Background/css/addArticle.css">
          	
          	
          	<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
          	<script type="text/javascript" src="js/jquery-1.4.min.js"></script>
          	
          	
          	<script src="Background/js/tinymce/tinymce.min.js" type="text/javascript" charset="utf-8"></script>
          	<script src="Background/js/tinymce/langs/zh-Hans.js" type="text/javascript" charset="utf-8"></script> <!--漢化-->
          	
          	<script type="text/javascript" src="Background/js/AddArticle.js"></script>
          	<script type="text/javascript" src="Background/js/mytinymce.js"></script>
          	<script type="text/javascript" src="Background/js/uploadImg.js"></script>
          	
            </head>
            
            <body>
            <div class='main'>
          	<div class='title'>
          		<label >添加一篇文章</label>
          	</div>
          	
          	
          	
          	<div class='FTitle_div'>
          		<input id="FTitle" class='FTitle' placeholder='請(qǐng)輸入標(biāo)題'></input>
          	</div>
          	
          	<div class='FAbstract_div'>
          		<input id="FAbstract" class='FAbstract' placeholder='請(qǐng)輸入摘要'></input>
          	</div>
          	<div class='content_div'>
          		<textarea id="content" class='content'></textarea>
          	</div>
          	<div class="FCover">封面圖片</div>
          	<div class="upload-piclist">
          		
                 	<div class="upload-file">
                     <input type="file" id="file" accept="image/*" multiple onchange="imgChange()"/>
                 	</div>
              </div>
          	<input id="submitbutton" class='submitbutton' type='button' value='提交'></input>
          </div>
            </body>
          </html>
          

          <script type="text/javascript" src="Background/js/uploadImg.js"></script>

          引入轉(zhuǎn)base64后前臺(tái)顯示JS文件

          uploadImg.js

          let picmax=20; //限制上傳數(shù)量 
          function imgChange() {
          	let file=document.getElementById('file').files;
          	let imglist=document.querySelectorAll('.upload-Picitem');
          	let piclist=document.getElementsByClassName('upload-piclist')[0];
          	let filelist=file.length + imglist.length > picmax ? 9 - imglist.length : file.length + imglist.length;
          	if (file.length + imglist.length >=9) {
          		let uploadfile=document.getElementsByClassName('upload-file')[0]
          		uploadfile.style.display="none"
          	}
          	for (let i=0; i < filelist; i++) {
          		readerfile(file[i]).then(e=> {
          			let html=document.createElement('div');
          			html.className='upload-Picitem'
          			html.innerHTML='<img src=' + e + ' alt="pic">'
          			piclist.appendChild(html);
          		})
          	}
          }
          
          function readerfile(file) {
          	return new Promise((resolve, reject)=> {
          		let reader=new FileReader();
          		reader.addEventListener("load", function() {
          			resolve(reader.result);
          		}, false)
          		if (file) {
          			reader.readAsDataURL(file)
          		}
          	})
          }
          
          //提交
          function submit() {
          	let imglist=[]
          	//let text=document.getElementsByClassName('upload-textarea')[0].value
          	let piclist=document.querySelectorAll('.upload-Picitem');
          	for (let i=0; i < piclist.length; i++) {
          		imglist.push(piclist[i].lastChild.src)
          	}
          	//console.log("發(fā)布內(nèi)容:", text)
          	console.log("圖片列表:", imglist)
          }
          
          
          
          

          直接看結(jié)果


          主站蜘蛛池模板: 丰满岳乱妇一区二区三区| 合区精品久久久中文字幕一区| 中文字幕一区二区精品区| 中文人妻无码一区二区三区| 中文字幕在线视频一区 | 成人精品一区二区户外勾搭野战| 亚洲一区在线观看视频| 中文字幕日韩欧美一区二区三区| 精品国产日韩亚洲一区在线| 国产精品被窝福利一区 | 国产精品一区电影| 成人精品视频一区二区三区| 少妇人妻精品一区二区三区| 糖心vlog精品一区二区三区| 亚洲欧洲一区二区三区| 3d动漫精品一区视频在线观看 | 国产精品一区在线播放| 国产精品va一区二区三区| 亚洲乱码日产一区三区| 亚洲国产AV一区二区三区四区 | 无码人妻精品一区二区三区66| 欧美日本精品一区二区三区 | 国产精品免费综合一区视频| 伊人精品视频一区二区三区| 另类国产精品一区二区| 中文字幕在线一区二区三区| 亚洲变态另类一区二区三区| 国产一区二区三区高清在线观看| 99精品国产高清一区二区麻豆| 一区二区三区免费视频观看| 国产精品夜色一区二区三区| 韩日午夜在线资源一区二区 | 午夜DV内射一区二区| 波多野结衣一区在线| 无码欧精品亚洲日韩一区| 在线精品一区二区三区| 国产精品高清一区二区三区| 无码欧精品亚洲日韩一区夜夜嗨| 在线精品国产一区二区| 国产一区二区三区在线2021| 视频在线观看一区二区|