聊聊前端領域那些“門面”
本文轉載自微信公眾號「神光的編程秘籍」,作者神說要有光zxg。轉載本文請聯系神光的編程秘籍公眾號。
門面模式(Facade)是 23 種經典設計模式之一,也叫外觀模式,是通過在客戶端和子系統之間引入一個中間層,將內部復雜度隱藏,暴露出一個簡單易用的接口。
引入門面模式之后,對客戶端來說,使用起來會簡單很多,不再需要了解具體的細節。
比如,沒用門面模式之前,可能是這樣的調用關系,客戶端需要了解每一個內部細節
而用了門面模式之后,客戶端不再需要了解具體每一個模塊,只需要把自己的需求告訴 Facade,然后它去調用內部模塊
加了一個門面,有改變功能么?并沒有,只是使得對外的接口更易用了。而這,就是門面模式的意義:封裝內部細節,簡化調用
其實在軟件領域這種門面太多了,各種 DSL(領域特定語言) 包括 html、css 還有 vue 的 template、react 的 jsx 都算是門面,babel 和 eslint 的 preset 也是門面。
下面我們分別來分析一下。
簡化 dom 創建的門面:html
瀏覽器提供了 dom api,基于 dom api 我們就可以構建 dom 樹,那為什么需要 html 呢?不用 html 可以么?
比如 dom api 創建 dom:
- const div = document.createElement('div');
- div.className = "a";
- const img = document.createElement('img');
- img.src="./b.jpg"
- div.appendChild(img);
和 html 描述 dom:
- <div class="a">
- <img src="./b.jpg">
- </div>
兩者并沒有區別。
所以,html 并沒有增加功能,它只是簡化了 dom 操作,這種明顯就是一種門面模式,不過是通過 DSL 的方式。
DSL 是指領域特定語言,設計一種語法來簡化邏輯的描述,然后通過解析該語言來專程描述的目標。瀏覽器就是通過解析 html 來構建 dom 樹的。
簡化樣式描述的門面:css
css 同樣也是一種 dsl,為了簡化樣式信息的描述。
比如直接給 dom 添加樣式會比較麻煩:
- const div = document.querySelector('.a');
- div.style.backgroundColor = 'blue';
- const img = div.querySelector('img');
- img.style.border = '2px';
而通過 css 來添加就簡單很多:
- .a {
- background-color: blue;
- }
- .a img {
- border: 2px;
- }
css 有增加新功能么?沒有,它只是使得樣式描述更簡單,同樣,css 這種 dsl 也是由瀏覽器來解析的。
vue 的 template
我們知道操作視圖基于 dom api 就足夠了,前端框架就是把數據映射到視圖,而映射的目標也是 dom api(當然,中間有了一層虛擬 dom,那么 api 也是先創建虛擬 dom),但是 dsl 就沒必要選擇用 html 了,完全可以用其他的更適合自己特點的方式來描述。
vue 選擇了 template:
如果直接用 api 描述視圖,不夠直觀:
- render: function (createElement) {
- return createElement('h1', this.blogTitle)
- }
vue 選擇了 template 的方式,類似 html:
- <h1>{{ blogTitle }}</h1>
這樣并沒有增加功能,只是讓開發者使用框架描述視圖的時候更簡單,而且 vue 還支持過濾器、指令、插值語法等功能。
但是引入了 template 的 dsl,也就需要編譯了,不像 html 是瀏覽器解析的,這個自定義 dsl 需要用自己的編譯器來解析,所以 vue 內部有一個 template compiler 來把模版轉為 render 函數。
react 的 jsx
react 同樣也是要把對視圖的描述映射成真正的 dom(中間有層虛擬 dom),首先會提供 api 的方式,但是為了簡化使用,會提供了描述視圖的方式:jsx。
直接用 api 的方式比較麻煩:
- const title = React.createElement("h1", {className: "main"}, "Hello React");
jsx 的方式就簡單很多:
- const title = (
- <h1>Hello React</h1>
- );
vue 和 react 選擇了不同的 dsl。我們知道 dsl 是需要編譯成具體的 api 調用的,vue 是在框架內部實現的,而 react 的 jsx 則是 babel 實現(因為是 js 語法的擴充)。
可以看到不管是 vue 的 template,還是 react 的 jsx 都沒有增加新的功能,增加這樣一層 dsl 只是為了簡化開發者對視圖的描述,和 html 的設計目的一致,都是門面模式的思想。
babel 的 preset
babel 是做代碼轉換的,把 es next 的語法,轉成目標環境支持的 js 語法,具體完成轉換的是一個個插件。但是如果開發者直接去指定插件太過麻煩,比如 es2015 就有一系列插件,而 es2016 又有一堆,如果由開發者去指定,那使用起來太過復雜。所以 babel 設計出了 preset。
babel6 的 preset 有 preset-es2015、 preset-es2016 等,他們內部就是一系列插件。而 babel7 進一步簡化成了 preset-env,只要通過 targets 指定目標環境,那么就會自動選出一系列插件來使用。
preset 有實現什么新功能么?沒有,最終轉換還是由插件來做的。但是 preset 簡化了開發者使用 babel 的成本,所以這也是一種典型的門面模式。
總結
門面模式是軟件領域特別常見的一種模式,就是當暴露給客戶端的子系統特別復雜的時候,通過增加一層門面,由他去和具體的子系統打交道,隔離復雜度,讓軟件的使用變得簡單。通過隔離復雜度,讓復雜度得到很好的治理,不然的話可能會隨著迭代而使用起來越來越復雜。
前端領域常見的 html、css、還有 vue 的 tempalte 以及 react 的 jsx 都是 dsl,dsl 的目的就是為了簡化調用,是門面模式的典型實現。(template 和 jsx 要由自己做編譯,而 html、css 是瀏覽器做的編譯)。
此外,babel 和 eslint 等的 preset 也是為了簡化使用成本而引入的,不然用戶就要直接面對各種復雜的插件配置。
門面模式并沒有引入新的功能實現,只是為了簡化系統使用成本而引入的一個入口。如果遇到系統使用特別復雜的時候,不妨通過引入一個門面(封裝成 api 或者 dsl 的形式)來簡化吧。