国产av日韩一区二区三区精品,成人性爱视频在线观看,国产,欧美,日韩,一区,www.成色av久久成人,2222eeee成人天堂

??
1. 為什么組件化這么難做
2. 標(biāo)準(zhǔn)的變革
3. 當(dāng)下最時髦的前端組件化框架/庫
4. 前端組件的復(fù)用性
5. 為什么MVVM是一種很好的選擇
6. 組件的長期積累
7. 我們需要關(guān)注什么
7.1 模塊化
7.2 Web Components
7.3 變更檢測
7.4 Immutable Data
7.6 Promise與異步
7.7 Isomorphic JavaScript
8. 小結(jié)
? ? ????? HTML ???? 2015前端組件化框架之路_html/css_WEB-ITnose

2015前端組件化框架之路_html/css_WEB-ITnose

Jun 24, 2016 am 11:45 AM

https://github.com/xufei/blog/issues/19

1. 為什么組件化這么難做

Web應(yīng)用的組件化是一個很復(fù)雜的話題。

在大型軟件中,組件化是一種共識,它一方面提高了開發(fā)效率,另一方面降低了維護(hù)成本。但是在Web前端這個領(lǐng)域,并沒有很通用的組件模式,因為缺少一個大家都能認(rèn)同的實現(xiàn)方式,所以很多框架/庫都實現(xiàn)了自己的組件化方式。

前端圈最熱衷于造輪子了,沒有哪個別的領(lǐng)域能出現(xiàn)這么混亂而欣欣向榮的景象。這一方面說明前端領(lǐng)域的創(chuàng)造力很旺盛,另一方面卻說明了基礎(chǔ)設(shè)施是不完善的。

我曾經(jīng)有過這么一個類比,說明某種編程技術(shù)及其生態(tài)發(fā)展的幾個階段:

  • 最初的時候人們忙著補(bǔ)全各種API,代表著他們擁有的東西還很匱乏,需要在語言跟基礎(chǔ)設(shè)施上繼續(xù)完善
  • 然后就開始各種模式,標(biāo)志他們做的東西逐漸變大變復(fù)雜,需要更好的組織了
  • 然后就是各類分層MVC,MVP,MVVM之類,可視化開發(fā),自動化測試,團(tuán)隊協(xié)同系統(tǒng)等等,說明重視生產(chǎn)效率了,也就是所謂工程化
  • 那么,對比這三個階段,看看關(guān)注這三種東西的人數(shù),覺得Web發(fā)展到哪一步了?

    細(xì)節(jié)來說,大概是模塊化和組件化標(biāo)準(zhǔn)即將大規(guī)模落地(好壞先不論),各類API也大致齊備了,終于看到起飛的希望了,各種框架幾年內(nèi)會有非常強(qiáng)力的洗牌,如果不考慮老舊瀏覽器的拖累,這個洗牌過程將大大加速,然后才能釋放Web前端的產(chǎn)能。

    但是我們必須注意到,現(xiàn)在這些即將普及的標(biāo)準(zhǔn),很多都會給之前的工作帶來改變。用工業(yè)體系的發(fā)展史來對比,前端領(lǐng)域目前正處于蒸汽機(jī)發(fā)明之前,早期機(jī)械(比如《木蘭辭》里面的機(jī)杼,主要是動力與材料比較原始)已經(jīng)普及的這么一個階段。

    所以,從這個角度看,很多框架/庫是會消亡的(專門做模塊化的AMD和CMD相關(guān)庫,專注于標(biāo)準(zhǔn)化DOM選擇器鋪墊的某些庫),一些則必須進(jìn)行革新,還有一些受的影響會比較?。〝?shù)據(jù)可視化等相關(guān)方向),可以有機(jī)會沿著自己的方向繼續(xù)演進(jìn)。

    2. 標(biāo)準(zhǔn)的變革

    對于這類東西來說,能獲得廣泛群眾基礎(chǔ)的關(guān)鍵在于:對將來的標(biāo)準(zhǔn)有怎樣的迎合程度。對前端編程方式可能造成重大影響的標(biāo)準(zhǔn)有這些:

  • module
  • Web Components
  • class
  • observe
  • promise
  • module的問題很好理解,JavaScript第一次有了語言上的模塊機(jī)制,而Web Components則是約定了基于泛HTML體系構(gòu)建組件庫的方式,class增強(qiáng)了編程體驗,observe提供了數(shù)據(jù)和展現(xiàn)分離的一種優(yōu)秀方式,promise則是目前前端最流行的異步編程方式。

    這里面只有兩個東西是繞不過去的,一是module,一是Web Components。前者是模塊化基礎(chǔ),后者是組件化的基礎(chǔ)。

    module的標(biāo)準(zhǔn)化,主要影響的是一些AMD/CMD的加載和相關(guān)管理系統(tǒng),從這個角度來看,正如seajs團(tuán)隊的@afc163 所說,不管是AMD還是CMD,都過時了。

    模塊化相對來說,遷移還比較容易,基本只是純邏輯的包裝,跟AMD或者CMD相比,包裝形式有所變化,但組件化就是個比較棘手的問題了。

    Web Components提供了一種組件化的推薦方式,具體來說,就是:

  • 通過shadow DOM封裝組件的內(nèi)部結(jié)構(gòu)
  • 通過Custom Element對外提供組件的標(biāo)簽
  • 通過Template Element定義組件的HTML模板
  • 通過HTML imports控制組件的依賴加載
  • 這幾種東西,會對現(xiàn)有的各種前端框架/庫產(chǎn)生很巨大的影響:

  • 由于shadow DOM的出現(xiàn),組件的內(nèi)部實現(xiàn)隱藏性更好了,每個組件更加獨(dú)立,但是這使得CSS變得很破碎,LESS和SASS這樣的樣式框架面臨重大挑戰(zhàn)。
  • 因為組件的隔離,每個組件內(nèi)部的DOM復(fù)雜度降低了,所以選擇器大多數(shù)情況下可以限制在組件內(nèi)部了,常規(guī)選擇器的復(fù)雜度降低,這會導(dǎo)致人們對jQuery的依賴下降。
  • 又因為組件的隔離性加強(qiáng),致力于建立前端組件化開發(fā)方式的各種框架/庫(除Polymer外),在自己的組件實現(xiàn)方式與標(biāo)準(zhǔn)Web Components的結(jié)合,組件之間數(shù)據(jù)模型的同步等問題上,都遇到了不同尋常的挑戰(zhàn)。
  • HTML imports和新的組件封裝方式的使用,會導(dǎo)致之前常用的以JavaScript為主體的各類組件定義方式處境尷尬,它們的依賴、加載,都面臨了新的挑戰(zhàn),而由于全局作用域的弱化,請求的合并變得困難得多。
  • 3. 當(dāng)下最時髦的前端組件化框架/庫

    在2015年初這個時間點看,前端領(lǐng)域有三個框架/庫引領(lǐng)時尚,那就是Angular,Polymer,React(排名按照首字母),在知乎的這篇2014 年末有哪些比較火的 Web 開發(fā)技術(shù)?里,我大致回答過一些點,其他幾位朋友的答案也很值得看。關(guān)于這三者的細(xì)節(jié)分析,侯振宇的這篇講得很好:2015前端框架何去何從

    我們可以看到,Polymer這個東西在這方面是有先天優(yōu)勢的,因為它的核心理念就是基于Web Components的,也就是說,它基本沒有考慮如何解決當(dāng)前的問題,直接以未來為發(fā)展方向了。

    React的編程模式其實不必特別考慮Web標(biāo)準(zhǔn),它的遷移成本并不算高,甚至由于其實現(xiàn)機(jī)制,屏蔽了UI層實現(xiàn)方式,所以大家能看到在native上的使用,canvas上的使用,這都是與基于DOM的編程方式大為不同的,所以對它來說,處理Web Components的兼容問題要在封裝標(biāo)簽的時候解決,反正之前也是要封裝。

    Angular 1.x的版本,可以說是跟同時代的多數(shù)框架/庫一樣,對未來標(biāo)準(zhǔn)的兼容基本沒有考慮,但是重新規(guī)劃之后的2.0版本對此有了很多權(quán)衡,變成了激進(jìn)變更,突然就變成一個未來的東西了。

    這三個東西各有千秋,在可以預(yù)見的幾年內(nèi)將會鼎足三分,也許還會有新的框架出現(xiàn),能不能比這幾個流行就難說了。

    此外,原Angular 2.0的成員Rob Eisenberg創(chuàng)建了自己的新一代框架aurelia,該框架將成為Angular 2.0強(qiáng)有力的競爭者。

    4. 前端組件的復(fù)用性

    看過了已有的一些東西之后,我們可以大致來討論一下前端組件化的一些理念。假設(shè)我們有了某種底層的組件機(jī)制,先不管它是瀏覽器原生的,或者是某種框架/庫實現(xiàn)的約定,現(xiàn)在打算用它來做一個大型的Web應(yīng)用,應(yīng)該怎么做呢?

    所謂組件化,核心意義莫過于提取真正有復(fù)用價值的東西。那怎樣的東西有復(fù)用價值呢?

  • 控件
  • 基礎(chǔ)邏輯功能
  • 公共樣式
  • 穩(wěn)定的業(yè)務(wù)邏輯
  • 對于控件的可復(fù)用性,基本上是沒有爭議的,因為這是實實在在的通用功能,并且比較獨(dú)立。

    基礎(chǔ)邏輯功能主要指的是一些與界面無關(guān)的東西,比如underscore這樣的輔助庫,或者一些校驗等等純邏輯功能。

    公共樣式的復(fù)用性也是比較容易認(rèn)可的,因此也會有bootstrap,foundation,semantic這些東西的流行,不過它們也不是純粹的樣式庫了,也帶有一些小的邏輯封裝。

    最后一塊,也就是業(yè)務(wù)邏輯。這一塊的復(fù)用是存在很多爭議的,一方面是,很多人不認(rèn)同業(yè)務(wù)邏輯也需要組件化,另一方面,這塊東西究竟怎樣去組件化,也很需要思考。

    除了上面列出的這些之外,還有大量的業(yè)務(wù)界面,這塊東西很顯然復(fù)用價值很低,基本不存在復(fù)用性,但仍然有很多方案中把它們“組件化”了,使得它們成為了“不具有復(fù)用性的組件”。為什么會出現(xiàn)這種情況呢?

    組件化的本質(zhì)目的并不一定是要為了可復(fù)用,而是提升可維護(hù)性。這一點正如面向?qū)ο笳Z言,Java要比C++純粹,因為它不允許例外情況的出現(xiàn),連main函數(shù)都必須寫到某個類里,所以Java是純面向?qū)ο笳Z言,而C++不是。

    在我們這種情況下,也可以把組件化分為:全組件化,局部組件化。怎么理解這兩個東西的區(qū)別呢,有人問過js框架和庫的區(qū)別是什么,一般來說,有某種較強(qiáng)約定的東西,稱為框架,而約定比較松散的,稱為庫。框架很多都是有全組件化理念的,比如說,很多年前就出現(xiàn)的ExtJS,它是全組件化框架,而jQuery和它的插件體系,則是局部組件化。所以用ExtJS寫東西,不管寫什么都是差不多一樣的寫法,而用jQuery的時候,大部分地方是原始HTML,哪里需要有些不一樣的東西,就只在那個地方調(diào)用插件做一下特殊化。

    對于一個有一定規(guī)模的Web應(yīng)用來說,把所有東西都“組件化”,在管理上會有較大的便利性。我舉個例子,同樣是編寫代碼,短代碼明顯比長代碼的可讀性更高,所以很多語言里會建議“一個方法一般不要超過多少行,一個類最好不要超過多少行”之類。在Web前端這個體系里,JavaScript這塊是做得相對較好的,現(xiàn)在入門水平的人,也已經(jīng)很少會有把一堆js都寫在一起的了。CSS這塊,最近在SASS,LESS等框架的引領(lǐng)下,也逐步往模塊化方面發(fā)展,否則直接編寫bootstrap那種css,會非常痛苦。

    這個時候我們再看HTML的部分,如果不考慮模板等技術(shù)的使用,某些界面光布局代碼寫起來就非常多了,像一些表單,都需要一層套一層,很多簡單的表單元素都需要套個三層左右,更不必說一些有復(fù)雜布局的東西了。尤其是整個系統(tǒng)單頁化之后,界面的header,footer,各種nav或者aside,很可能都有一定復(fù)雜性。如果這些東西的代碼不作切分,那么主界面的HTML一定比較難看。

    我們先不管用什么方式切分了,比如用某種模板,用類似Angular中的include,或者Polymer,React中的標(biāo)簽,或者直接使用原生Web Components,總之是把一塊一塊都拆開了,然后包含進(jìn)來。從這個角度看,這些拆出去的東西都像組件,但如果從復(fù)用性的角度看,很可能多數(shù)東西,每一塊都只有一個地方用,壓根沒有復(fù)用度。這個拆出去,純粹是為了使得整個工程易于管理,易于維護(hù)。

    這時候我們再來關(guān)注不同框架/庫對UI層組件化的處理方式,發(fā)現(xiàn)有兩個類型,模板和函數(shù)。

    模板是一種很常見的東西,它用HTML字符串的方式表達(dá)界面的原始結(jié)構(gòu),然后通過代入數(shù)據(jù)的方式生成真正的界面,有的是生成目標(biāo)HTML,有的還生成各種事件的自動綁定。前者是靜態(tài)模板,后者是動態(tài)模板。

    另外有一些框架/庫偏愛用函數(shù)邏輯來生成界面,早期的ExtJS,現(xiàn)在的React(它內(nèi)部還是可能使用模板,而且對外提供的是組件創(chuàng)建接口的進(jìn)一步封裝??jsx)等,這種實現(xiàn)技術(shù)的優(yōu)勢是不同平臺上編程體驗一致,甚至可以給每種平臺封裝相同的組件,調(diào)用方輕松寫一份代碼,在Web和不同Native平臺上可用。但這種方式也有比較麻煩的地方,那就是界面調(diào)整比較繁瑣。

    本文前面部分引用侯振宇的那篇文章里,他提出這些問題:

    如何能把組件變得更易重用? 具體一點:

  • 我在用某個組件時需要重新調(diào)整一下組件里面元素的順序怎么辦?
  • 我想要去掉組件里面某一個元素怎么辦? 如何把組件變得更易擴(kuò)展? 具體一點:
  • 業(yè)務(wù)方不斷要求給組件加功能怎么辦?
  • 為此,還提出了“模板復(fù)寫”方案,在這一點上我有不同意見。

    我們來看看如何把一個業(yè)務(wù)界面切割成組件。

    有這么一個簡單場景:一個雇員列表界面包括兩個部分,雇員表格和用于填寫雇員信息的表單。在這個場景下,存在哪些組件?

    對于這個問題,主要存在兩種傾向,一種是僅僅把“控件”和比較有通用性的東西封裝成組件,另外一種是整個應(yīng)用都組件化。

    對前一種方式來說,這里面只存在數(shù)據(jù)表格這么一個組件。
    對后一種方式來說,這里面有可能存在:數(shù)據(jù)表格,雇員表單,甚至還包括雇員列表界面這么一個更大的組件。

    這兩種方式,就是我們之前所說的“局部組件化”,“全組件化”。

    我們前面提到,全組件化在管理上是存在優(yōu)勢的,它可以把不同層面的東西都搞成類似結(jié)構(gòu),比如剛才的這個業(yè)務(wù)場景,很可能最后寫起來是這個樣子:

    <Employee-Panel>    <Employee-List></Employee-List>    <Employee-Form></Employee-Form></Employee-Panel>

    對于UI層,最好的組件化方式是標(biāo)簽化,比如上面代碼中就是三個標(biāo)簽表達(dá)了整個界面。但我個人堅決反對濫用標(biāo)簽,并不是把各種東西都盡量封裝就一定好。

    全標(biāo)簽化的問題主要有這些:

    第一,語義化代價太大。只要用了標(biāo)簽,就一定需要給它合適的語義,也就是命名。但實際用的時候,很可能只是為了把一堆html簡化一下而已,到底簡化出來的那東西應(yīng)當(dāng)叫什么名字,光是起名也費(fèi)不知多少腦細(xì)胞。比如你說雇員管理的表單,這個表單有heading嗎,有footer嗎,能折疊嗎,等等,很難起一個讓別人一看就知道的名字,要么就是特別長。這還算簡單的,因為我們是全組件化,所以很可能會有組合了多種東西的一個較復(fù)雜的界面,你想來想去也沒法給它起個名字,于是寫了個:

    <Panel-With-Department-Panel-On-The-Left-And-Employee-Panel-On-The-Right></Panel-With-Department-Panel-On-The-Left-And-Employee-Panel-On-The-Right>

    這尼瑪……可能我夸張了點,但很多時候項目規(guī)模夠大,你不起這么復(fù)雜的名字,最后很可能沒法跟功能類似的一個組件區(qū)分開,因為這些該死的組件都存在于同一個命名空間中。如果僅僅是當(dāng)作一個界面片段來include,就不存在這種心理負(fù)擔(dān)了。

    比如Angular里面的這種:

    <div ng-include="'aaa/bbb/ccc.html'"></div>

    就不給它什么名字,直接include進(jìn)來,用文件路徑來區(qū)分。這個片段的作用可以用其目錄結(jié)構(gòu)描述,也就是通過物理名而非邏輯名來標(biāo)識,目錄層次充當(dāng)了一個很好的命名空間。

    現(xiàn)在的一些主流MVVM框架,比如knockout,angular,avalon,vue等等,都有一種“界面模板”,但這種模板并不僅僅是模板,而是可以視為一種配置文件。某一塊界面模板描述了自身與數(shù)據(jù)模型的關(guān)系,當(dāng)它被解析之后,按照其中的各種設(shè)置,與數(shù)據(jù)建立關(guān)聯(lián),并且反過來再更新自身所對應(yīng)的視圖。

    不含業(yè)務(wù)邏輯的UI(或者是業(yè)務(wù)邏輯已分離的UI)基本不適合作為組件來看待,因為即使在邏輯不變的情況下,界面改版的可能性也太多了。比如即使是換了新的CSS實現(xiàn)方式,從float布局改成flex布局,都有可能把DOM結(jié)構(gòu)少套幾層div,因此,在使用模板的方案中,只能把界面層視為配置文件,不能看成組件,如果這么做,就會輕松很多。

    部隊行軍的時候講究“逢山開路,遇水搭橋”,這句話的重點在于只有到某些地形才開路搭橋,使用MVVM這類模式解決的業(yè)務(wù)場景,多數(shù)時候是一馬平川,橫著走都可以,不必硬要造路。所以從整個方案看的話,UI層實現(xiàn)應(yīng)該是模板與控件并存,大部分地方是模板,少數(shù)地方是需要單獨(dú)花時間搞的路和橋。

    第二,配置過于復(fù)雜。有很多東西其實不太適合封裝,不但封裝的代價大,使用的代價也會很大。有時候會發(fā)現(xiàn),調(diào)用代碼的絕大部分都是在寫各種配置。

    就像剛才的雇員表單,既然你不從標(biāo)簽的命名上去區(qū)分,那一定會在組件上加配置。比如你原來想這樣:

    <EmployeeForm heading="雇員表單"></EmployeeForm>

    然后在組件內(nèi)部,判斷有沒有設(shè)置heading,如果沒有就不顯示,如果有,就顯示。過了兩天,產(chǎn)品問能不能把heading里面的某幾個字加粗或者換色,然后碼農(nóng)開始允許這個heading屬性傳入html。沒多久之后,你會驚奇地發(fā)現(xiàn)有人用你的組件,沒跟你說,就在heading里面?zhèn)魅肓苏郫B按鈕的html,并且用選擇器給折疊按鈕加了事件,點一下之后還能折疊這個表單了……

    然后你一想,這個不行,我得給他再加個配置,讓他能很簡單地控制折疊按鈕的顯示,但是現(xiàn)在這么寫太不直觀,于是采用對象結(jié)構(gòu)的配置:

    <EmployeeForm>    <Option collapsible="true">        <Heading>            <h4><strong>雇員</strong>表單</h4>        </Heading>    </Option></EmployeeForm>

    然后又有一天,發(fā)現(xiàn)有很多面板都可以折疊,然后特意創(chuàng)建了一個可折疊面板組件,又創(chuàng)建了一種繼承機(jī)制,其他普通業(yè)務(wù)面板從它繼承,從此一發(fā)不可收拾。

    我舉這例子的意思是為了說明什么呢,我想說,在規(guī)模較大的項目中,企圖用全標(biāo)簽化加配置的方式來描述所有的普通業(yè)務(wù)界面,是一定事倍功半的,并且這個規(guī)模越大就越坑,這也正是ExtJS這類對UI層封裝過度的體系存在的最大問題。

    這個問題討論完了,我們來看看另外一個問題:如果UI組件有業(yè)務(wù)邏輯,應(yīng)該如何處理。

    比如說,性別選擇的下拉框,它是一個非常通用化的功能,照理說是很適合被當(dāng)做組件來提供的。但是究竟如何封裝它,我們就有些犯難了。這個組件里除了界面,還有數(shù)據(jù),這些數(shù)據(jù)應(yīng)當(dāng)內(nèi)置在組件里嗎?理論上從組件的封裝性來說,是都應(yīng)當(dāng)在里面的,于是就這么造了一個組件:

    <GenderSelect></GenderSelect>

    這個組件非常美好,只需直接放在任意的界面中,就能顯示帶有性別數(shù)據(jù)的下拉框了。性別的數(shù)據(jù)很自然地是放在組件的實現(xiàn)內(nèi)部,一個寫死的數(shù)組中。這個太簡單了,我們改一下,改成商品銷售的國家下拉框。

    表面上看,這個沒什么區(qū)別,但我們有個要求,本公司商品銷售的國家的信息是統(tǒng)一配置的,也就是說,這個數(shù)據(jù)來源于服務(wù)端。這時候,你是不是想把一個http請求封裝到這組件里?

    這樣做也不是不可以,但存在至少兩個問題:

  • 如果這類組件在同一個界面中出現(xiàn)多次,就可能存在請求的浪費(fèi),因為有一個組件實例就會產(chǎn)生一個請求。
  • 如果國家信息的配置界面與這個組件同時存在,當(dāng)我們在配置界面中新增一個國家了,下拉框組件中的數(shù)據(jù)并不會實時刷新。
  • 第一個問題只是資源的浪費(fèi),第二個就是數(shù)據(jù)的不一致了。曾經(jīng)在很多系統(tǒng)中,大家都是手動刷新當(dāng)前頁面來解決這問題的,但到了這個時代,人們都是追求體驗的,在一個全組件化的解決方案中,不應(yīng)再出現(xiàn)此類問題。

    如何解決這樣的問題呢?那就是引入一層Store的概念,每個組件不直接去到服務(wù)端請求數(shù)據(jù),而是到對應(yīng)的前端數(shù)據(jù)緩存中去獲取數(shù)據(jù),讓這個緩存自己去跟服務(wù)端保持同步。

    所以,在實際做方案的過程中,不管是基于Angular,React,Polymer,最后肯定都做出一層Store了,不然會有很多問題。

    5. 為什么MVVM是一種很好的選擇

    我們回顧一下剛才那個下拉框的組件,發(fā)現(xiàn)存在幾個問題:

  • 界面不好調(diào)整。剛才的那個例子相對簡單,如果我們是一個省市縣三級聯(lián)動的組件,就比較麻煩了。比如說,我們想要把水平布局改成垂直的,又或者,想要把中間的label的字改改,都會非常麻煩。按照傳統(tǒng)的做組件的方式,就要加若干配置項,然后組件里面去分別判斷,修改DOM結(jié)構(gòu)。
  • 如果數(shù)據(jù)的來源不是靜態(tài)json,而是某個動態(tài)的服務(wù)接口,那用起來就很麻煩。
  • 我們更多地需要業(yè)務(wù)邏輯的復(fù)用和純“控件”的復(fù)用,至于那些綁定業(yè)務(wù)的界面組件,復(fù)用性其實很弱。
  • 所以,從這些角度,會盡量期望在HTML界面層與JavaScript業(yè)務(wù)邏輯之間,存在一種分離。

    這時候,再看看絕大多數(shù)界面組件存在什么問題:

    有時候我們考慮一下DOM操作的類型,會發(fā)現(xiàn)其實是很容易枚舉的:

  • 創(chuàng)建并插入節(jié)點
  • 移除節(jié)點
  • 節(jié)點的交換
  • 屬性的設(shè)置
  • 多數(shù)界面組件封裝的絕大部分內(nèi)容不過是這些東西的重復(fù)。這些東西,其實是可以通過某些配置描述出來的,比如說,某個數(shù)組以什么形式渲染成一個select或者無序列表之類,當(dāng)數(shù)組變動,這些東西也跟著變動,這些都應(yīng)當(dāng)被自動處理,如果某個方案在現(xiàn)在這個時代還手動操作這些,那真的是一種落伍。

    所以我們可以看到,以Angular,Knockout,Vue,Avalon為代表的框架們在這方面做了很多事,盡管理念有所差異,但大方向都非常一致,也就是把大多數(shù)命令式的DOM操作過程簡化為一些配置。

    有了這種方式之后,我們可以追求不同層級的復(fù)用:

  • 業(yè)務(wù)模型因為是純邏輯,所以非常容易復(fù)用
  • 視圖模型基本上也是純邏輯,界面層多數(shù)是純字符串模板,同一個視圖模型搭配不同的界面模板,可以實現(xiàn)視圖模型的復(fù)用
  • 同一個界面模板與不同的視圖模型組合,也能直接組合出完全不同的東西
  • 所以這么一來,我們的復(fù)用粒度就非常靈活了。正因為這樣,我一直認(rèn)為Angular這樣的框架戰(zhàn)略方向是很正確的,雖然有很多戰(zhàn)術(shù)失誤。我們在很多場景下,都是需要這樣的高效生產(chǎn)手段的。

    6. 組件的長期積累

    我們做組件化這件事,一定是一種長期打算,為了使得當(dāng)前的很多東西可以作為一種積累,在將來還能繼續(xù)使用,或者僅僅作較小的修改就能使用,所以必須考慮對未來標(biāo)準(zhǔn)的兼容。主要需要考慮的方面有這幾點:

  • 盡可能中立于語言和框架,使用瀏覽器的原生特性
  • 邏輯層的模塊化(ECMAScript module)
  • 界面層的元素化(Web Components)
  • 之前有很多人對Angular 2.0的激進(jìn)變更很不認(rèn)同,但它的變更很大程度上是對標(biāo)準(zhǔn)的全面迎合。這不僅僅是它的問題,其實是所有前端框架的問題。不面對這些問題,不管現(xiàn)在多么好,將來都是死路一條。這個問題的根源是,這幾個已有的規(guī)范約束了模塊化和元素化的推薦方式,并且,如果要對當(dāng)前和未來兩邊做適配的話,基本就沒法干了,導(dǎo)致以前的都不得不做一定的遷移。

    模塊化的遷移成本還比較小,無論是之前AMD還是CMD的,都可以根據(jù)一些規(guī)則轉(zhuǎn)換過來,但組件化的遷移成本太大了,幾乎每種框架都會提出自己的理念,然后有不同的組件化理念。

    還是從三個典型的東西來說:Polymer,React,Angular。

    Polymer中的組件化,其實就是標(biāo)簽化。這里的標(biāo)簽,并不只是界面元素,甚至邏輯組件也可以這樣,比如這個代碼:

    <my-panel>    <core-ajax id="ajax" url="http://url" params="{{formdata}}" method="post"></core-ajax></my-panel>

    注意到這里的core-ajax標(biāo)簽,很明顯這已經(jīng)是純邏輯的了,在大多數(shù)前端框架或者庫中,調(diào)用ajax肯定不是這樣的,但在瀏覽器端這么干也不是它獨(dú)創(chuàng),比如flash里面的WebService,比如早期IE中基于htc實現(xiàn)的webservice.htc等等,都是這么干的。在Polymer中,這類東西稱為非可見元素(non-visual-element)。

    React的組件化,跟Polymer略有不同,它的界面部分是標(biāo)簽化,但如果有單純的邏輯,還是純JavaScript模塊。

    既然大家的實現(xiàn)方式都那么不一致,那我們怎么搞出盡量可復(fù)用的組件呢?問題到最后還是要繞到Web Components上。

    在Web Components與前端組件化框架的關(guān)系上,我覺得是這么個樣子:

    各種前端組件化框架應(yīng)當(dāng)盡可能以Web Components為基石,它致力于組織這些Components與數(shù)據(jù)模型之間的關(guān)系,而不去關(guān)注某個具體Component的內(nèi)部實現(xiàn),比如說,一個列表組件,它究竟內(nèi)部使用什么實現(xiàn),組件化框架其實是不必關(guān)心的,它只應(yīng)當(dāng)關(guān)注這個組件的數(shù)據(jù)存取接口。

    然后,這些組件化框架再去根據(jù)自己的理念,進(jìn)一步對這些標(biāo)準(zhǔn)Web Components進(jìn)行封裝。換句話說,業(yè)務(wù)開發(fā)人員使用某個組件的時候,他是應(yīng)當(dāng)感知不到這個組件內(nèi)部究竟使用了Web Components,還是直接使用傳統(tǒng)方式。(這一點有些理想化,可能并不是那么容易做到,因為我們還要管理像import之類的事情)。

    7. 我們需要關(guān)注什么

    目前來看,前端框架/庫仍然處于混戰(zhàn)期,可比中國歷史上的春秋戰(zhàn)國,百家齊放,作為跟隨者來說,這是很痛苦的,因為無所適從,很可能你作為一個企業(yè)的前端架構(gòu)師或者技術(shù)經(jīng)理,需要做一些選型工作,但選哪個能保證幾年后不被淘汰呢?基本沒有。

    雖然我們不知道將來什么框架會流行,但我們可以從一些細(xì)節(jié)方面去關(guān)注,某個具體的方面,將來會有什么,也可以了解一下在某個具體領(lǐng)域存在什么樣的方案。一個完整的框架方案,無非是以下多個方面的綜合。

    7.1 模塊化

    這塊還是不講了,支付寶seajs還有百度ecomfe這兩個團(tuán)隊的人應(yīng)該都能比我講得好得多。

    7.2 Web Components

    本文前面討論過一些,也不深入了。

    7.3 變更檢測

    我們知道,現(xiàn)代框架的一個特點是自動化,也就是把原有的一些手動操作提取。在前端編程中,最常見的代碼是在干什么呢?讀寫數(shù)據(jù)和操作DOM。不少現(xiàn)代的框架/庫都對這方面作了處理,比如說通過某種配置的方式,由框架自動添加一些關(guān)聯(lián),當(dāng)數(shù)據(jù)變更的時候,把DOM進(jìn)行相應(yīng)修改,又比如,當(dāng)DOM發(fā)生變動的時候,也更新對應(yīng)的數(shù)據(jù)。

    這個關(guān)聯(lián)過程可能會用到幾種技術(shù)。首先我們看怎么知道數(shù)據(jù)在變化,這里面有三種途徑:

    一、存取器的封裝。這個的意思也就是對數(shù)據(jù)進(jìn)行一層包裝,比如:

    var data = {    name: "aaa",    getName: function() {        return this.name;    },    setName: function(value) {        this.name = value;    }}

    這樣,不允許用戶直接調(diào)用data.name,而是調(diào)用對應(yīng)的兩個函數(shù)。Backbone就是通過這樣的機(jī)制實現(xiàn)數(shù)據(jù)變動觀測的,這種方式適用于幾乎所有瀏覽器,缺點就是比較麻煩,要對每個數(shù)據(jù)進(jìn)行包裝。

    這個機(jī)制在稍微新一點的瀏覽器中,也有另外一種實現(xiàn)方式,那就是defineProperty相關(guān)的一些方法,使用更優(yōu)雅的存取器,這樣外界可以不用調(diào)用函數(shù),而是直接用data.name這樣進(jìn)行屬性的讀寫。

    國產(chǎn)框架avalon使用了這個機(jī)制,低版本IE中沒有defineProperty,但在低版本IE中不止有JavaScript,還存在VBScript,那里面有存取器,所以他巧妙地使用了VBS做了這么一個兼容封裝。

    基于存取器的機(jī)制還有個麻煩,就是每次動態(tài)添加屬性,都必須再添加對應(yīng)的存取器,否則這個屬性的變更就無法獲取。

    二、臟檢測。

    以Angular 1.x為代表的框架使用了臟檢測來獲知數(shù)據(jù)變更,這個機(jī)制的大致原理是:

    保存數(shù)據(jù)的新舊值,每當(dāng)有一些DOM或者網(wǎng)絡(luò)、定時器之類的事件產(chǎn)生,用這個事件之后的數(shù)據(jù)去跟之前保存的數(shù)據(jù)進(jìn)行比對,如果相同,就不觸發(fā)界面刷新,否則就刷新。

    這個方式的理念是,控制所有可能導(dǎo)致數(shù)據(jù)變更的來源(也就是各種事件),在他們可能對數(shù)據(jù)進(jìn)行操作之后,判斷新舊數(shù)據(jù)是否有變化,忽略所有中間變更,也就是說,如果你在同一個事件中,把某個數(shù)據(jù)任意修改了很多次,但最后改回來了,框架會認(rèn)為你什么都沒干,也就不會通知界面去刷新了。

    不可否認(rèn)的是,臟檢測的效率是比較低的,主要是不能精確獲知數(shù)據(jù)變更的影響,所以當(dāng)數(shù)據(jù)量更大的情況下,浪費(fèi)更嚴(yán)重,需要手動作一些優(yōu)化。比如說一個很大的數(shù)組,生成了一個界面上的列表,當(dāng)某個項選中的時候,改變顏色。在這種機(jī)制下,每次改變這個項的數(shù)據(jù)狀態(tài),就需要把所有的項都跟原來比較一遍,然后,還要再全部比較一次發(fā)現(xiàn)沒有關(guān)聯(lián)引起的變化了,才能對應(yīng)刷新界面。

    三、觀察機(jī)制。

    在ES7里面,引入了Object的observe方法,可以用于監(jiān)控對象或數(shù)組的變動。

    這是目前為止最合理的觀測方案。這個機(jī)制很精確高效,比如說,連長跟士兵說,你去觀察對面那個碉堡里面的動靜。這個含義很復(fù)雜,包括什么呢?

  • 是不是加人了
  • 是不是有人離開了
  • 誰跟誰換崗了
  • 上面的旗子從太陽旗換成青天白日了
  • 所謂觀察機(jī)制,也就是觀測對象屬性的變更,數(shù)組元素的新增,移除,位置變更等等。我們先思考一下界面和數(shù)據(jù)的綁定,這本來就應(yīng)當(dāng)是一個外部的觀察,你是數(shù)據(jù),我是界面,你點頭我微笑,你伸手我打人。這種綁定本來就應(yīng)當(dāng)是個松散關(guān)系,不應(yīng)當(dāng)因為要綁定,需要破壞原有的一些東西,所以很明顯更合理。

    除了數(shù)據(jù)的變動可以被觀察,DOM也是可以的。但是目前絕大多數(shù)雙向同步框架都是通過事件的方式把DOM變更同步到數(shù)據(jù)上。比如說,某個文本框綁定了一個對象的屬性,那很可能,框架內(nèi)部是監(jiān)控了這個文本框的鍵盤輸入、粘貼等相關(guān)事件,然后取值去往對象里寫。

    這么做可以解決大部分問題,但是如果你直接myInput.value="111",這個變更就沒法獲取了。這個不算大問題,因為在一個雙向綁定框架中,一個既被監(jiān)控,又手工賦值的東西,本身也比較怪,不過也有一些框架會嘗試從HTMLInputELement的原型上去覆蓋value賦值,嘗試把這種東西也納入框架管轄范圍。

    另外一個問題,那就是我們只考慮了特定元素的特定屬性,可以通過事件獲取變更,如何獲得更廣泛意義上的DOM變更?比如說,一般屬性的變更,或者甚至子節(jié)點的增刪?

    DOM4引入了MutationObserver,用于實現(xiàn)這種變更的觀測。在DOM和數(shù)據(jù)之間,是否需要這么復(fù)雜的觀測與同步機(jī)制,目前尚無定論,但在整個前端開發(fā)逐步自動化的大趨勢下,這也是一種值得嘗試的東西。

    復(fù)雜的關(guān)聯(lián)監(jiān)控容易導(dǎo)致預(yù)期之外的結(jié)果:

  • 慕容復(fù)要復(fù)國,每天讀書練武,各種謀劃
  • 王語嫣觀察到了這種現(xiàn)象,認(rèn)為表哥不愛自己了
  • 段譽(yù)看到神仙姐姐悶悶不樂,每天也茶飯不思
  • 鎮(zhèn)南王妃心疼愛子,到處調(diào)查這件事的原委,意外發(fā)現(xiàn)段正淳還跟舊愛有聯(lián)系
  • ……
  • 總之這么下來,最后影響到哪里了都不知道,誰讓丘處機(jī)路過牛家村呢?

    所以,變更的關(guān)聯(lián)監(jiān)控是很復(fù)雜的一個體系,尤其是其中產(chǎn)生了閉環(huán)的時候。搭建整個這么一套東西,需要極其精密的設(shè)計,否則熟悉整套機(jī)制的人只要用特定場景輕輕一推就倒了。靈智上人雖然武功過人,接連碰到歐陽鋒,周伯通,黃藥師,全部都是上來就直接被抓了后頸要害,大致就是這意思。

    polymer實現(xiàn)了一個observe-js,用于觀測數(shù)組、對象和路徑的變更,有興趣的可以關(guān)注。

    在有些框架,比如aurelia中,是混合使用了存取器和觀察模式,把存取器作為觀察模式的降級方案,在瀏覽器不支持observe的情況下使用。值得一提的是,在臟檢測方式中,變更是合并后批量提交的,這一點常常被另外兩種方案的使用者忽視。其實,即使用另外兩種方式,也還是需要一個合并與批量提交過程。

    怎么理解這個事情呢?數(shù)據(jù)的綁定,最終都是要體現(xiàn)到界面上的,對于界面來說,其實只關(guān)注你每一次操作所帶來的數(shù)據(jù)變更的始終,并不需要關(guān)心中間過程。比如說,你寫了這么一個循環(huán),放在某個按鈕的點擊中:

    for (var i=0; i<10000; i++) {    obj.a += 1;}

    界面有一個東西綁定到這個a,對框架來說,絕對不應(yīng)當(dāng)把中間過程直接應(yīng)用到界面上,以剛才這個例子來說,合理的情況只應(yīng)當(dāng)存在一次對界面DOM的賦值,這個值就是對obj.a進(jìn)行了10000次賦值之后的值。盡管用存取器或者觀察模式,發(fā)現(xiàn)了對obj上a屬性的這10000次賦值過程,這些賦值還是都必須被舍棄,否則就是很可怕的浪費(fèi)。

    React使用虛擬DOM來減少中間的DOM操作浪費(fèi),本質(zhì)跟這個是一樣的,界面只應(yīng)當(dāng)響應(yīng)邏輯變更的結(jié)束狀態(tài),不應(yīng)當(dāng)響應(yīng)中間狀態(tài)。這樣,如果有一個ul,其中的li綁定到一個1000元素的數(shù)組,當(dāng)首次把這個數(shù)組綁定到這個ul上的時候,框架內(nèi)部也是可以優(yōu)化成一次DOM寫入的,類似之前常用的那種DocumentFragment,或者是innerHTML一次寫入整個字符串。在這個方面,所有優(yōu)化良好的框架,內(nèi)部實現(xiàn)機(jī)制都應(yīng)當(dāng)類似,在這種方案下,是否使用虛擬DOM,對性能的影響都是很小的。

    7.4 Immutable Data

    Immutable Data是函數(shù)式編程中的一個概念,在前端組件化框架中能起到一些很獨(dú)特的作用。

    它的大致理念是,任何一種賦值,都應(yīng)當(dāng)被轉(zhuǎn)化成復(fù)制,不存在指向同一個地方的引用。比如說:

    var a = 1;var b = a;b = 2;console.log(a==b);

    這個我們都知道,b跟a的內(nèi)存地址是不一致的,簡單類型的賦值會進(jìn)行復(fù)制,所以a跟b不相等。但是:

    var a = {    counter : 1};var b = a;b.counter++;console.log(a.counter==b.counter);

    這時候因為a和b指向相同的內(nèi)存地址,所以只要修改了b的counter,a里面的counter也會跟著變。

    Immutable Data的理念是,我能不能在這種賦值情況下,直接把原來的a完全復(fù)制一份給b,然后以后大家各自變各自的,互相不影響。光憑這么一句話,看不出它的用處,看例子:

    對于全組件化的體系,不可避免會出現(xiàn)很多嵌套的組件。嵌套組件是一個很棘手的問題,在很多時候,是不太好處理的。嵌套組件所存在的問題主要在于生命周期的管理和數(shù)據(jù)的共享,很多已有方案的上下級組件之間都是存在數(shù)據(jù)共享的,但如果內(nèi)外層存在共享數(shù)據(jù),那么就會破壞組件的獨(dú)立性,比如下面的一個列表控件:

    <my-list list-data="{arr}">    <my-listitem></my-listitem>    <my-listitem></my-listitem>    <my-listitem></my-listitem></my-list>

    我們在賦值的時候,一般是在外層整體賦值一個類似數(shù)組的數(shù)據(jù),而不是自己挨個在每個列表項上賦值,不然就很麻煩。但是如果內(nèi)外層持有相同的引用,對組件的封裝性很不利。

    比如在剛才這個例子里,假設(shè)數(shù)據(jù)源如下:

    var arr = [    {name: "Item1"},     {name: "Item2"},     {name: "Item3"}];

    通過類似這樣的方式賦值給界面組件,并且由它在內(nèi)部給每個子組件分別進(jìn)行數(shù)據(jù)項的賦值:

    list.data = arr;

    賦值之后會有怎樣的結(jié)果呢?

    console.log(list.data == arr);console.log(listitem0.data == arr[0]);console.log(listitem1.data == arr[1]);console.log(listitem2.data == arr[2]);

    這種方案里面,后面那幾個log輸出的結(jié)果都會是true,意思就是內(nèi)層組件與外層共享數(shù)據(jù),一旦內(nèi)層組件對數(shù)據(jù)進(jìn)行改變,外層中的也就改變了,這明顯是違背組件的封裝性的。

    所以,有一些方案會引入Immutable Data的概念。在這些方案里,內(nèi)外層組件的數(shù)據(jù)是不共享的,它們的引用不同,每個組件實際上是持有了自己的數(shù)據(jù),然后引入了自動的賦值機(jī)制。

    這時候再看看剛才那個例子,就會發(fā)現(xiàn)兩層的職責(zé)很清晰:

  • 外層持有一個類似數(shù)組的東西arr,用于形成整個列表,但并不關(guān)注每條記錄的細(xì)節(jié)
  • 內(nèi)層持有某條記錄,用于渲染列表項的界面
  • 在整個列表的形成過程中,list組件根據(jù)arr的數(shù)據(jù)長度,實例化若干個listitem,并且把a(bǔ)rr中的各條數(shù)據(jù)賦值給對應(yīng)的listitem,而這個賦值,就是immutable data起作用的地方,其實是把這條數(shù)據(jù)復(fù)制了一份給里面,而不是把外層這條記錄的引用賦值進(jìn)去。內(nèi)層組件發(fā)現(xiàn)自己的數(shù)據(jù)改變之后,就去進(jìn)行對應(yīng)的渲染
  • 如果arr的條數(shù)變更了,外層監(jiān)控這個數(shù)據(jù),并且根據(jù)變更類型,添加或者刪除某個列表項
  • 如果從外界改變了arr中某一條記錄的內(nèi)容,外層組件并不直接處理,而是給對應(yīng)的內(nèi)層進(jìn)行了一次賦值
  • 如果列表項中的某個操作,改變了自身的值,它首先是把自己持有的數(shù)據(jù)進(jìn)行改變,然后,再通過immutable data把數(shù)據(jù)往外同步一份,這樣,外層組件中的數(shù)據(jù)也就更新了。
  • 所以我們再看這個過程,真是非常清晰明了,而且內(nèi)外層各司其職,互不干涉。這是非常有利于我們打造一個全組件化的大型Web應(yīng)用的。各級組件之間存在比較松散的聯(lián)系,而每個組件的內(nèi)部則是封閉的,這正是我們所需要的結(jié)果。

    說到這里,需要再提一個容易混淆的東西,比如下面這個例子:

    <outer-component>    <inner-component></inner-component></outer-component>

    如果我們?yōu)榱私oinner-component做一些樣式定位之類的事情,很可能在內(nèi)外層組件之間再加一些額外的布局元素,比如變成這樣:

    <outer-component>    <div>        <inner-component></inner-component>    </div></outer-component>

    這里中間多了一級div,也可能是若干級元素。如果有用過Angular 1.x的,可能會知道,假如這里面硬造一級作用域,搞個ng-if之類,就可能存在多級作用域的賦值問題。在上面這個例子里,如果在最外層賦值,數(shù)據(jù)就會是outer -> div -> inner這樣,那么,從框架設(shè)計的角度,這兩次賦值都應(yīng)當(dāng)是immutable的嗎?

    不是,第一次賦值是非immutable,第二次才需要是,immutable賦值應(yīng)當(dāng)僅存在于組件邊界上,在組件內(nèi)部不是特別有必要使用。剛才的例子里,依附于div的那層變量應(yīng)當(dāng)還是跟outer組件在同一層面,都屬于outer組件的人民內(nèi)部矛盾。

    這里是facebook實現(xiàn)的immutable-js庫

    7.6 Promise與異步

    前端一般都習(xí)慣于用事件的方式處理異步,但很多時候純邏輯的“串行化”場景下,這種方式會讓邏輯很難閱讀。在新的ES規(guī)范里,也有yield為代表的各種原生異步處理方案,但是這些方案仍然有很大的理解障礙,流行度有限,很大程度上會一直停留在基礎(chǔ)較好的開發(fā)人員手中。尤其是在瀏覽器端,它的受眾應(yīng)該會比node里面還要狹窄。

    前端里面,處理連續(xù)異步消息的最能被廣泛接受的方案是promise,我這里并不討論它的原理,也不討論它在業(yè)務(wù)中的使用,而是要提一下它在組件化框架內(nèi)部所能起到的作用。

    現(xiàn)在已經(jīng)沒有哪個前端組件化框架可以不考慮異步加載問題了,因為,在前端這個領(lǐng)域,加載就是一個繞不過去的坎,必須有了加載,才能有執(zhí)行過程。每個組件化框架都不能阻止自己的使用者規(guī)模膨脹,因此也應(yīng)當(dāng)在框架層面提出解決方案。

    我們可能會動態(tài)配置路由,也可能在動態(tài)加載的路由中又引入新的組件,如何控制這些東西的生命周期,值得仔細(xì)斟酌,如果在框架層面全異步化,對于編程體驗的一致性是有好處的。將各類接口都promise化,能夠在可維護(hù)性和可擴(kuò)展性上提供較多便利。

    我們之前可能熟知XMLHTTP這樣的通信接口,這個東西雖然被廣為使用,但是在優(yōu)雅性等方面,存在一些問題,所以最近出來了替代方案,那就是fetch。

    細(xì)節(jié)可以參見月影翻譯的這篇【翻譯】這個API很“迷人”??(新的Fetch API)

    在不支持的瀏覽器上,也有g(shù)ithub實現(xiàn)的一個polyfill,雖然不全,但可以湊合用window.fetch polyfill

    大家可以看到,fetch的接口就是基于promise的,這應(yīng)當(dāng)是前端開發(fā)人員最容易接受的方案了。

    7.7 Isomorphic JavaScript

    這個東西的意思是前后端同構(gòu)的JavaScript,也就是說,比如一塊界面,可以選擇在前端渲染,也可以選擇在后端渲染,值得關(guān)注,可以解決像seo之類的問題,但現(xiàn)在還不能處理很復(fù)雜的狀況,持續(xù)關(guān)注吧。

    8. 小結(jié)

    很感謝能看到這里,以上這些是我近一年的一些思考總結(jié)。從技術(shù)選型的角度看,做大型Web應(yīng)用的人會很痛苦,因為這是一個青黃不接的年代,目前已有的所有框架/庫都存在不同程度的缺陷。當(dāng)你向未來看去,發(fā)現(xiàn)它們都是需要被拋棄,或者被改造的,人最痛苦的是在知道很多東西不好,卻又要從中選取一個來用。@嚴(yán)清 跟@寸志 @題葉討論過這個問題,認(rèn)為現(xiàn)在這個階段的技術(shù)選型難做,不如等一陣,我完全贊同他們的觀點。

    選型是難,但是從學(xué)習(xí)的角度,可真的是挺好的時代,能學(xué)的東西太多了,我每天路上都在努力看有可能值得看的東西,可還是看不完,只能努力去跟上時代的步伐。

    以下一段,與諸位共勉:

    It was the best of times, it was the worst of times, it was the age of wisdom, it was the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, it was the season of Light, it was the season of Darkness, it was the spring of hope, it was the winter of despair, we had everything before us, we had nothing before us, we were all going direct to Heaven, we were all going direct the other way--in short, the period was so far like the present period, that some of its noisiest authorities insisted on its being received, for good or for evil, in the superlative degree of comparison only.

    ? ????? ??
    ? ?? ??? ????? ???? ??? ??????, ???? ?????? ????. ? ???? ?? ???? ?? ??? ?? ????. ???? ??? ???? ???? ??? ?? admin@php.cn?? ?????.

    ? AI ??

    Undresser.AI Undress

    Undresser.AI Undress

    ???? ?? ??? ??? ?? AI ?? ?

    AI Clothes Remover

    AI Clothes Remover

    ???? ?? ???? ??? AI ?????.

    Video Face Swap

    Video Face Swap

    ??? ??? AI ?? ?? ??? ???? ?? ???? ??? ?? ????!

    ???

    ??? ??

    ???++7.3.1

    ???++7.3.1

    ???? ?? ?? ?? ???

    SublimeText3 ??? ??

    SublimeText3 ??? ??

    ??? ??, ???? ?? ????.

    ???? 13.0.1 ???

    ???? 13.0.1 ???

    ??? PHP ?? ?? ??

    ???? CS6

    ???? CS6

    ??? ? ?? ??

    SublimeText3 Mac ??

    SublimeText3 Mac ??

    ? ??? ?? ?? ?????(SublimeText3)

    ???

    ??? ??

    ??? ????
    1601
    29
    PHP ????
    1502
    276
    ???
    HTML ?? ??? ???? ?? ??? ?? ?? HTML ?? ??? ???? ?? ??? ?? ?? Jul 07, 2025 am 02:31 AM

    HTML ?? ??? ???? ?? ??? ??? ???? ?? ?? ?? ? ???? ?? ??? ????????. 1. ????? ??? ??? ????? ?? ? ?? ?? (? : ??, ??, ???)? ?? ??? ?????. 2. JavaScript? ?? ?? ? ??? ???? ID? ?? ??? ?? ??? ??? ???? ?? ? ????. 3. CSS? ???? ???, ???, ?? ??? ? ??/?? ?? ??? ???? ???? ??? ???? ??? ??? ??????. 4. ???? ????????? : ???? ? ??? ????? ??? ???? JS ???? ???? ????? ???? ??? ???? ??? ??? ??? ???? ??? ?????. ??? ???????

    HTML ?? ?? ??? ?? ?? ??? ?? HTML ?? ?? ??? ?? ?? ??? ?? Jul 09, 2025 am 02:30 AM

    HTMLHEAD? ?? ???? SEO, ?? ?? ? ???? ??? ?????. 1. ??? ??? ??? ????, ???? ???? ???? ??????. 2. OpenGraph ? Twitter ?? ??? ???? ?? ?? ??? ????? ??? ?????? ???? ??? ??? ???? ???????. 3. ?? ?? ? ??? ??? ???? ??? ??? ??? ???? ????????. 4. ?? ???, ?? ?? ? ?? ?? ?? ???? ?? ??? ??? ????? ???????.

    2025 ? ?????? ??? HTML ???? 2025 ? ?????? ??? HTML ???? Jul 08, 2025 am 12:25 AM

    Tolearnhtmlin2025, chooSeatUtoriorialThatthatthath and-practicewithmoderndardAndardsandegratescssandjavaScriptBasics.1.

    ??? ??? ? HTML ???? ??? ??? ? HTML ???? Jul 10, 2025 pm 02:01 PM

    ???? ??? HTML ?? ???? ??? ??? ?????? ?? Div Flex ?? ??? ????? ???? ??? ?????? ??? ???????. ??, ?? ???? ?????? ?? CSS? ?? ? ?? ????. ?? ?? ??? ALT ??? ?? ???? ?? URL? ?????? ??? ???? ??? ?? TD? ??????????. ????? ?? ?????? ?? ??? ????? ???????.

    HTML ?? ? ?? ??? ???? ??? ??? ?? ???? ???? ??? ?????? HTML ?? ? ?? ??? ???? ??? ??? ?? ???? ???? ??? ?????? Jul 07, 2025 am 02:30 AM

    HTML ??? ???? ????? ?? ?? ???? ??? ?? ???? ?? ???? ?? ? ? ????. 1. ??, ??? ?? ?? ??? ?? ??? ? ??? ???? ???? ? ?????. 2. ?? ???? ???? ??? ? ?? ??? ?? ? ? ????. 3. ??? ??? ??? ???? ?????? ??? ??? ? SEO ??? ??????. 4. ??? ??? ?, ??? ??? ??? ????? ??????, ???? ?? ????? ???? ??? ?? ???? ???? ???????. 5. ??? ??? ALT ??? ??? ????. 6. ??? ???? ??? ?? ??? ?? ?? ??? ?? ? ? ????. ? ? ?? ??? ???? ???? ?? ???? ???? ?? ? ???? ???? ? ??????.

    ???? HTML?? ?? ??? ???? ??? ?????? ???? HTML?? ?? ??? ???? ??? ?????? Jul 09, 2025 am 01:14 AM

    ??? ??? ??? HTML ?? ??? ??? ????? ?? ?? ?? ?? ???? ?? ?? ? ? ????. ?? ???? ??? ?????. 1. JavaScript? ???? ?? ?? ? ??? ???? ???? ?? ?? ??? ?? ?? ? ???? ???? ????. 2. Formspree? ?? ?? ???? ? ???? ???? ???? ???? ??? ?? ? ???? ??? ?????. 3. LocalStorage? ???? ??? ?? ??? ????? ?? ??? ?? ???? ??? ???? ? ????? ??? ??? ?? ???? ???? ????.

    HTML?? ?? ????? ???? ??? ??? ?????? HTML?? ?? ????? ???? ??? ??? ?????? Jul 10, 2025 am 10:58 AM

    ???, ID, ???, ??? ? ??? HTML?? ?? ????? ???? ??? ?????. ???? ??? ?? ? JavaScript ??? ?????? ?? ?? ??? ??? ??? ???? ? ?????. ID? ?? ?? ? JavaScript ???? ??? ??? ?? ?? ???? ?????. ???? ???? ??? ???? ?? ? ? ??? ?? ???? ????? ??? ???? ???? ????. ??? ??? ??? ?? ???? ???? ? ????, ?? ??? ?? ? ??? ?? ??? ?????. ??? ??? ?? ????? ???? ? ????? ???? ??? ????? ?? ?????. ??? ??? ????? ???? ?? ???? ??? ??? ???? ? ????.

    HTML?? ???? ?? ?? ??? ?? ?? HTML?? ???? ?? ?? ??? ?? ?? Jul 12, 2025 am 12:48 AM

    Native Lazy Loading? ?? ???? ????,?? = "???"??? ??? ???? ??? ????? ? ? ????. 1. JavaScript ?? ?? ?????? ???? ??? HTML?? ?? ?????. 2. ??? ??? ? ?? ??? ???? ?? ??, ?? ??? ??? ??? ? ? ?? ???? ?????. 3. ? ?? ?? ?? ???????? ???? ???? ????. 4. ??? ??? ?, ??? ?? ???? ???? ??? ??? ?? ???????. 5. SRCSET ? ?? ??? ?? ?? ? ??? ??? ????????. 6. ??? ??? ???????. ?? ??? ??????? ???? ????. ?? ??? ?? ??? ? ??? JavaScript ???? ?? ? ? ????.

    See all articles