深入聊聊 JavaScript 框架
作者簡介: 陳敏亮,字節跳動前端工程師。
一、前言
關於 JS 框架部分能聊的內容比較多,我相信大家對某個框架的使用、原理等知識是比較容易找到資料來學習的,鑑於此這部分內容將會從另一個視角出發:通過回顧 JS 框架的發展歷程,和大家一起探討框架的本質以及 JS 框架不斷變化背後的驅動力。正所謂「鑑往知來」,希望大家能對 JS 框架有更全面的認知,能夠把握變化背後不變的邏輯,更好的應對未來新的變化。
二、什麼是框架
在講清楚一件事情之前,我們需要先準確定義它,這一部分我們先定義清楚什麼是框架以及什麼是 JS 框架。
先看一個通用意義上「框架」的的定義:
框架(framework)是一個框子——指其約束性,也是一個架子——指其支撐性。是一個基本概念上的結構,用於去解決或者處理複雜的問題。
個人非常喜歡這個定義,簡潔而準確,框架的核心就是「約束性」和「支撐性」。
如何理解「約束性」
框架是解決複雜領域問題的系統性方法,對於複雜問題而言,往往缺失「銀彈」,大家看問題的角度會有差異,每個框架背後都有自己的理念,當你選擇某個框架就需要遵循它的邏輯和規則,在規則之內解決問題,這是「約束性」。
如何理解「支撐性」
複雜問題通常可以拆解成一系列小問題,問題的整體解法則是如何解決它們中的所有或者部分,框架的「支撐性」體現在它提供了問題的定義和一系列解決問題的方法,基於它能夠將針對各個子問題的解法更加有序的組織起來,最終形成整體的解法,「支撐性」即有序組織。
如何定義 JS 框架
解決前端特定領域問題的代碼組織框架,約束性體現爲需要基於約定的結構來組織代碼,支撐性體現爲基於框架內置的機制或能力高效的實現功能。
按這個定義 JS 框架覆蓋了方方面面,例如解決 Web 整體實現的前端應用框架、遊戲引擎框架、後端服務框架等等,但需要指出的是大家通常認爲的 JS 框架指的是前端應用框架,這也是這部分文章介紹的重點。再順道說一下框架和庫的區別,JS 庫通常是解決系統構建中某個子問題的代碼實現,它和框架的關係往往是被調用者(庫)和調用者(框架)的關係。
JS 框架要解決什麼問題
一直以來 Web 前端技術發展非常迅速,新技術更是層出不窮,但這些技術發展的背後始終圍繞着一條主線——如何更高效的構建 Web 人機交互界面。複雜 Web 應用的構建首先要考慮的是如何更合理的組織代碼,這關係到協作效率、可維護性、可擴展性等等,本質是對生產效率的持續追求。再進一步拆解來看,我們的問題域主要包括視圖構建、視圖狀態管理、用戶交互、服務端交互等,在複雜場景下,隨着表達內容和人機交互多樣性的增加,如何兼具效率、體驗等具有較大的挑戰,而這也恰恰是 JS 框架所要解決的問題。
三、JavaScript 框架的發展
從 Tim Berners-Lee 在 1989 年的提議開始到 2019 年 3 月 12 日,WWW 迎來了它的 30 歲生日, 這 30 年無疑是人類科技進步最快的 30 年,Web 也從最初的提議變成了人類社會的基礎設施。從技術角度看,Web 在滿足人們越來越多線上化需求的同時,Web 前端也在變得越來越複雜:更強大的功能、更豐富多樣的內容、更復雜的運行環境等等,從最初的小製作到現在的大規模複雜應用,不斷推動開發者們尋求更高效的前端構建方式,這也正是前端技術快速發展背後的動力。
從 Web 前端的形式看,大致可以將 30 年劃分爲三個時期:只讀 Web 時期、可交互 Web 時期、Web 應用時期。
只讀 Web 時期(1990~2000),Web 的核心功能是圖文內容的線上化,只有少數用戶使用並且只能瀏覽內容。從技術角度看,這個時代是 Web 標準的起源階段,同時由於單一的內容表現需求,尚不存在誕生 JS 框架的土壤。
可交互 Web 時期(2000~2010),越來越多的人通過 Web 滿足需求,Web 內容形態也從單一的只讀圖文展示演變成功能豐富的可交互形式,這種演變也促使 Web 開發領域演化出新的社會分工角色 —— Web 前端工程師。與此同時,有了 JS 框架的土壤之後,形形色色的前端庫和框架也逐漸誕生,但此時它們更多專注於解決某個方向的問題:例如兼容性或者組件化等等;
Web 應用時期(2010 至今),對於很多行業而言,Web 服務已經屬於基礎設施,大多數人已經依賴 Web 能力來滿足需求。爲了追求更好的體驗以及更高的效率,Web 服務的構建也像客戶端應用靠齊,JS 框架的發展則逐步覆蓋到高效構建 Web 前端相關的整個領域。
只讀 Web 時期
1989 年,爲了讓學校和科研機構間能夠更高效的共享信息,Tim Berners-Lee 提出了「WWW」提議並且在次年發明了第一個 web 瀏覽器 - WorldWideWeb。而在隨後的 1991 年,HTML 的第一個版本 - 「HTML Tags」誕生,目標是文本、圖片等信息的在線展示以及互聯互通。第一個商業化瀏覽器的誕生則是在 1993 年,來自 Netscape 的 Netscape Navigator。W3C 的誕生則要到 1994 年,由 Tim Berners-Lee 創辦。
直至 1995 年,Web 上所有內容仍然是純靜態,但隨着 Web 需求的快速增長,逐步有些場景需要用戶交互的能力,因此在 1995 年 9 月,Netscape 發佈了 Javascript 的第一個版本 - LiveScript,並在 3 個月後改名爲 JavaScript。
這一時期除了 WWW 的起源之外,另一件值得一提的事是史上第一次「瀏覽器大戰」,在這場大戰中,Netscape 在前期以先發優勢拿下 80% 的市場份額,微軟 IE 則後來居上,以和 Windows 捆綁的方式逐步贏回優勢,到 2000 年微軟 IE 市場份額佔比超過 80%。有意思的是在第二次「瀏覽器大戰」中,IE 又敗下陣來,後起之秀 Chrome 藉着更高的性能、更好的標準兼容性以及更快的迭代速度攻城掠地,至 2019 年佔領 70% 以上的市場份額,IE 市場份額則降至 10% 左右。對開發者而言,「瀏覽器大戰」中由於各方對標準的支持程度有差異,甚至爲了實現差異化會特意引入新特性,因此開發者需要投入大量的工作來解決兼容性問題,這也爲後續的 Javascript 庫 / 框架的產生埋下伏筆。
可交互 Web 時期
進入 2000 年,互聯網加速發展,隨着電商、社交等新平臺的推出,越來越多用戶加入互聯網,此時的互聯網用戶羣已經逐步由最初的小衆用戶發展爲大衆用戶,各大平臺也越來越重視 Web 用戶體驗,開始在 Web 用戶交互方面增加資源投入。另一方面,瀏覽器通過持續的升級也具備更多的能力來支撐更好的用戶交互,例如異步請求能力的引入等。以上需求和供給兩側爲 JavaScript 庫 / 框架的發展提供了土壤。
這個階段,Web 主流的渲染方式是後端渲染,JavaScript 的重點工作是對頁面實現局部交互能力,例如表單驗證、異步提交、圖片輪播、Tab 切換等等,這一時期的 JavaScript 庫 / 框架圍繞完善用戶交互相關基礎設施發展,主要包括組件化、兼容性以及工具庫。
組件化
與其他編程語言中的人機交互界面構建能力相比,在 Web 中缺乏豐富、標準化的 UI 組件庫。因此在這個階段湧現出一批以組件化爲主的框架,其中比較有代表性的是 DoJo(2005)、ExtJS(2007)。這些框架的特點是提供了 All in One 的組件庫,組件類型豐富,當然交互形式也非常厚重,典型的桌面客戶端風格。
兼容性
「瀏覽器大戰」的大背景下,瀏覽器廠商各自爲戰,於是不論是 BOM、DOM 還是 JavaScript 層面都需要做大量的兼容性工作,於是 jQuery(2006) 便在這一背景下誕生。jQuery 一方面解決了大量兼容性問題,另一方面提供了非常便捷的基於 CSS 選擇符的 DOM 獲取方式以及 DOM 操作能力,此外還重新封裝了異步請求的 API,這些能力在以命令式(imperative)編程範式爲主的時期是非常高頻的操作,因此 jQuery 的出現大大提升了前端編程體驗和效率。
<script>
$( "div span:first-child" )
.css( "text-decoration", "underline" )
.hover(function() {
$( this ).addClass( "sogreen" );
}, function() {
$( this ).removeClass( "sogreen" );
});
</script>
當然,隨着 jQuery 很多能力從事實標準演進爲正式標準,瀏覽器開始提供原生支持,再者前端編程模式從命令式向聲明式(declarative)演進,針對 DOM 的獲取與操作大大減少,jQuery 的使用率也隨之快速下降。此外,隨着 Chrome 在「瀏覽器大戰」中勝出,兼容性問題已經有了非常大的改善,當然移動端除外。
工具庫
這一時期原生 JavaScript 僅支持一些較「原始」的編碼能力,對比其他成熟的編程語言而言在編程效率和體驗上都存在巨大差異,各類工具庫基於「原始」能力進行再封裝,能較大程度縮小差異。工具庫類的代表有 PrototypeJS(2005)、YUI(2006)、Mootools(2007),它們主要針對面向對象、事件、異步請求、數組操作等方面進行了封裝。
/*PrototypeJS 代碼示例*/
// 面向對象
var FirstClass = Class.create( {
// The initialize method serves as a constructor
initialize: function () {
this.data = "Hello World";
}
});
// 異步請求
Ajax.Request = Class.create( Ajax.Base, {
// Override the initialize method
initialize: function(url, options) {
this.transport = Ajax.getTransport();
this.setOptions(options);
this.request(url);
},
// ...more methods add ...
});
// DOM 與事件
$$('#items li').each( function(item) {
item.observe('click', function(event) {
doSomethingWith(event.target);
});
});
// 數組遍歷
myArray.each(function(item) {
// Your code working on item here...
});
當然,按上述三個方面並不是能夠特別準確的去給衆多 JavaScript 庫 / 框架進行歸類,很多采用分層以及可拆解的設計,能夠同時兼顧各個方面的需求,就如以下 YUI 的整體架構。
Web 應用時期
最近 10 年,隨着前端投入的持續增加,前端團隊的話語權也越來越重,同時大家也在持續探索更高效的前端研發模式,例如這期間逐步發展的前後端分離、前端工程化等,尤其是前後端分離的協作模式給前端開發帶來了更高的自由度和空間,前端的工作職責從最初的局部動態化、局部可交互逐步擴張到整站的前端構建,這種變化直接帶來了至少兩方面的挑戰,一是如何解決多人協作問題,針對規模越來越大的前端工程,多人協作是必然,而協作的關鍵是明確的分工和合作;二是代碼的可維護性問題,在前端框架出現之前,局部動態 UI 通常採用前端模板加命令式編程範式來實現,這種方式對於輕量的 UI 構建是可接受的,但在應對大規模前端功能構建時,很容易寫出可維護性極差的代碼。
這些是構建 GUI 應用時的典型問題,在 Web 前端出現之前的其他平臺的 GUI 構建中也有類似的問題,解決的方式是引入合適的架構模式,將看似亂做一團的代碼分門別類,各自關注各自職責範圍內的工作,這些架構模式包括 MVC、MVP、MVVM 等等,背後的核心理念是「關注點分離」。在 Web 前端面臨類似的挑戰時,自然而然的是借鑑傳統 GUI 構建的經驗,通過框架的形式引入新的架構模式來解決,在這一背景下前端也逐步出現了真正意義上的「框架」。
2010 年發佈的 Backbone.js 引入了視圖和數據模型的概念,但它並不是標準的 MVC 實現,沒有明確的控制器概念,而是由視圖承擔部分控制器職責,但這些並不重要,重要的是前端 GUI 構建有了新的思路和選擇。另外一點值得一提的是,Backbone.js 仍然採用了命令式編程範式,這在前端 GUI 構建中與後續逐漸流行的聲明式編程範式而言,編程效率上會有明顯的差距。
/*Backbone.js 代碼示例(部分)詳細前往 https://backbonejs.org/docs/todos.html*/
var AppView = Backbone.View.extend({
el: $("#todoapp"),
statsTemplate: _.template($('#stats-template').html()),
events: {
"keypress #new-todo": "createOnEnter",
},
initialize: function() {
this.input = this.$("#new-todo");
this.listenTo(Todos, 'add', this.addOne);
this.footer = this.$('footer');
this.main = $('#main');
Todos.fetch();
},
render: function() {
var done = Todos.done().length;
var remaining = Todos.remaining().length;
if (Todos.length) {
this.main.show();
this.footer.show();
this.footer.html(this.statsTemplate({done: done, remaining: remaining}));
} else {
this.main.hide();
this.footer.hide();
}
this.allCheckbox.checked = !remaining;
},
addOne: function(todo) {
var view = new TodoView({model: todo});
this.$("#todo-list").append(view.render().el);
},
clearCompleted: function() {
_.invoke(Todos.done(), 'destroy');
return false;
}
});
var App = new AppView;
});
同年 AngularJS 發佈第一個版本,和 Backbone 的最大不同,一方面它充分借鑑了微軟 WPF 的 MVVM 架構模式,引入 VM 概念,支持視圖和數據的雙向綁定;另一方面它採用了聲明式的視圖構建模式,大大減少了原生 DOM 操作。此外,AngularJS 提供的功能非常全面(路由管理、組件化、視圖模板、狀態管理、後端交互、事件管理、動畫等),基本達到開箱即用的狀態,使用它能夠非常高效的構建 SPA 類應用。當然,它的缺點也非常明顯,引入了過多的概念導致上手成本非常高,此外它的「大而全」是優點的同時也是缺點,對於非 SPA 類 Web 場景而言過於臃腫。總體而言,AngularJS 對於解決特定場景中的前端架構問題是非常有效的,能夠非常顯著的提升前端協作效率。
在 AngularJS 發佈後的第四年和第五年 React 以及 Vue 陸續發佈,React 發佈時的目標是優化視圖構建,但隨着整個生態的完善,React 已經有充分的能力支撐大規模的前端應用開發。Vue 則借鑑了 AngularJS 的架構模式,並且同樣採用了聲明式的視圖構建方式。相比 AngularJS,React 和 Vue 的最大優勢是更加的輕量和靈活,能夠適應更多的場景,此外上手成本也要低得多,尤其是 Vue,這也讓後兩者在最近幾年能夠碾壓 AngularJS。
縱觀最近 10 年前端框架的變遷,有兩點得以驗證,一是聲明式編程範式相比命令式在前端 GUI 構建中的成功,背後的關鍵邏輯是研發效率的顯著優勢;二是漸進式框架相比 All in One 「全家桶」式框架的成功,漸進式框架有更好的靈活性,面對前端場景的變化有更強的適應性。
四、鑑往知來
以上走馬觀花的概覽了最近 30 年前端框架的發展,整體演進脈絡是清晰的,Web 前端從無到有,從有到逐步走向專業化,技術上的關注點和挑戰也在不斷變化,從解決最初的 DOM 操作效率、兼容性、組件化等問題,再到提升大規模前端的協作效率等,那些能夠準確把握並且有效解決問題的框架 / 庫都獲得了成功,至少是階段性的成功。順着這條脈絡往前看,前端框架的後續發展仍然取決於前端領域在未來將面臨何種挑戰。
Web vs Native
在智能手機普及的初期,各平臺的移動端也曾經以 Web 爲主,當時各種 Web 服務的 m 站是必備之一。隨着智能手機的普及,移動 APP 具備明顯的路徑、性能、能力等方面的優勢,迅速成爲移動化的首選。在這個過程中,Web 前端很大程度上淡出移動應用的核心研發工作,後來隨着 hybrid 開發模式的成熟,Web 前端又逐步參與到一些移動應用的開發中。再看未來,逐步由流量運營到以用戶運營爲核心的時期,以 Native 爲主導的模式預計仍然會是主流,而且用戶體驗變得更加重要,尤其是用戶長停留的應用。在這一背景下,Web 和 Native 如何更好的協作,如何取長補短,在效率和體驗上能夠進一步突破天花板將是長期的挑戰。
智能終端
前面更多談論的是 PC 端和移動端,隨着 5G 的完善和普及,IoT 會迎來加速發展,屆時在移動端和 PC 端兩端之外將會出現更多形式各異的終端,這類終端的 GUI 需求如何解決,是否可以採用 Web 技術來提效,現有 Web 框架在 IoT 設備上是否是最佳選擇?隨着 IoT 的爆發,這些問題會是前端框架不得不考慮的問題。
低代碼化
另外一個看得見的趨勢是低代碼化(low/no code)的普及,不久的將來大量的模式化的 Web 開發工作極大可能將通過低代碼化的方式來解決,例如運營、管理類的平臺就是典型的模式化 Web 平臺,這類平臺在當前的前端研發工作中佔有很大的比例,也是當前各類前端框架的重要應用場景,一旦這類需求由低代碼化的方式來實現,那麼具體的實現使用何種框架將變得不那麼重要。在這個背景下,很可能會誕生一些專有框架來更高效的解決低代碼化的領域問題。而通用類框架的場景會更多的集中在非模式化的場景中,在這類場景中對框架的靈活性、定製化等會有更高的要求。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/ZiB5PXZRJeeN9fqtKdsi3Q