瀏覽器:一個(gè)家族的奮斗
我是你們每天都要使用的瀏覽器,自從90年代誕生以來(lái),我們這個(gè)大家族變得非常的繁榮,在過(guò)去的幾十年中,我們一直兢兢業(yè)業(yè)地幫助你們?nèi)祟?lèi)去探索外部的互聯(lián)網(wǎng)世界。隨著互聯(lián)網(wǎng)和移動(dòng)互聯(lián)網(wǎng)的發(fā)展,我們家族終于登上了成功的巔峰:幾乎占據(jù)了全球每一臺(tái)電腦和手機(jī)。
大家只看到表面的光鮮,可是誰(shuí)知道我們家族在背后充滿血和淚的不懈努力呢?
1起步:從簡(jiǎn)單到混亂
最初的時(shí)候,我們?yōu)g覽器家族的工作很簡(jiǎn)單,就是從遠(yuǎn)端的服務(wù)器下載HTML頁(yè)面到本機(jī),然后按照和你們?nèi)祟?lèi)的約定,把充滿標(biāo)簽的HTML文本轉(zhuǎn)換成你們?nèi)菀组喿x的頁(yè)面。例如當(dāng)我們遇到<table>這樣的標(biāo)簽,我們就展示一個(gè)表格,遇到<p>我們就會(huì)換行顯示一個(gè)新的段落,看到<img>就知道要顯示一個(gè)圖片等等。
由于HTML的結(jié)構(gòu)很簡(jiǎn)單,我們展示起來(lái)也不難,工作相對(duì)比較輕松。
可是你們?nèi)祟?lèi)很快就不滿足了,嚷嚷著說(shuō)要看漂亮的頁(yè)面,漂亮的布局,就像報(bào)紙雜志那樣,分欄、圖文混排......
于是有不少人盯上了<table>這個(gè)標(biāo)簽,這個(gè)標(biāo)簽本來(lái)的目的是為了顯示數(shù)據(jù)用的,像這樣:
但是這些人發(fā)現(xiàn),既然表格能把頁(yè)面分成一塊一塊的,那完全可以來(lái)做頁(yè)面布局??!比如這個(gè)單元格可以作為頁(yè)面的頭,那個(gè)做左邊欄,右邊欄......等等。
這個(gè)口子一開(kāi),立刻成星火燎原之勢(shì),再加上那些所見(jiàn)即所得的網(wǎng)頁(yè)編輯器(如DreamWeaver,FrontPge)推波助瀾,表格布局大行其道。
這可苦了我們?yōu)g覽器家族,我們經(jīng)常發(fā)現(xiàn)HTML中那些令人恐怖的表格,一個(gè)套著一個(gè),我見(jiàn)過(guò)最極端的有上百個(gè)表格套在一起,組成超級(jí)無(wú)敵的頁(yè)面。為了把這些表格解析、展示出來(lái),我們都是戰(zhàn)戰(zhàn)兢兢、如履薄冰,生怕出什么差錯(cuò)。
有時(shí)候,程序員會(huì)忘記寫(xiě)一些標(biāo)簽的閉合標(biāo)簽,例如</a></td>,我們還得猜測(cè)、推理、論證,最后展示出來(lái)的東西你們還不滿意:這是什么玩意兒,連個(gè)HTML都顯示不對(duì)!
我們家族的IE同學(xué)在這方面的研究可以說(shuō)是登峰造極,他非常擅長(zhǎng)處理這種亂七八糟的情形,他的縱容成功地讓你們把HTML越寫(xiě)越亂!
2CSS來(lái)拯救
終于有一天這樣的HTML頁(yè)面連你們?nèi)祟?lèi)都看不懂了,你們吵了半天,放了一個(gè)“損招”出來(lái):把結(jié)構(gòu)和展示分開(kāi)!
HTML只負(fù)責(zé)文檔的結(jié)構(gòu),一個(gè)叫做CSS的東西來(lái)控制這個(gè)結(jié)構(gòu)到底展示成什么樣子。
在CSS中,程序員只需要用聲明的方式說(shuō),這個(gè)表格寬度是1px,字體是11px,邊框顏色是#666666,然后你們就不管了,具體怎么實(shí)現(xiàn)全部扔給了我們:
- table.gridtable {
- font-family: verdana,arial,sans-serif;
- font-size:11px;
- color:#333333;
- border-width: 1px;
- border-color: #666666;
- border-collapse: collapse;
- }
對(duì)于你們來(lái)說(shuō)變簡(jiǎn)單了,可是對(duì)我們?yōu)g覽器來(lái)說(shuō)就太難了。我們得老老實(shí)實(shí)地去解析HTML(這HTML有可能還不規(guī)范),形成一顆叫做DOM(DocumentObjectModel)的樹(shù),這還不算,還要去解析CSS,形成另外一個(gè)叫做CSSOM(CSSObjectModel)的樹(shù),然后把兩棵樹(shù)合并成一個(gè)“渲染樹(shù)”這才能在界面上繪制出來(lái)。
現(xiàn)在的CSS都已經(jīng)出到第三個(gè)版本了,可是我們家族還有不少人沒(méi)法完整的支持這個(gè)規(guī)范,尤其是一些老舊的瀏覽器,可見(jiàn)這是多么不容易啊。
3誤入歧途
我們家族也走過(guò)一段彎路,那是一段不堪回首的瀏覽器插件的歷史。
當(dāng)時(shí)有人提出HTML+CSS太簡(jiǎn)單了,瀏覽器中的頁(yè)面沒(méi)法和當(dāng)時(shí)的桌面應(yīng)用相媲美,能不能用擴(kuò)展一下???
最早的嘗試來(lái)自于JavaApplet,這也是Java賴以成名,從此走上第一大編程語(yǔ)言的絕技。那幫瘋狂的家伙們竟然在我們這單薄的小身板里安裝了一個(gè)Java虛擬機(jī),用于從服務(wù)器端下載Java代碼、運(yùn)行,展示出漂亮的動(dòng)畫(huà)效果。
剛開(kāi)始的時(shí)候我們努力地支持,但是它運(yùn)行起來(lái)慢如蝸牛、丑陋無(wú)比。我們那些可愛(ài)的用戶也不答應(yīng),不去安裝或者禁止使用這傻大笨粗的Java虛擬機(jī),于是JavaApplet就慢慢地銷(xiāo)聲匿跡了。
與此同時(shí),一個(gè)叫做流氓插件的毒瘤也迅速地向我們襲來(lái),在你瀏覽網(wǎng)頁(yè)的時(shí)候,這些流氓靜悄悄地潛入我們的身體,他們傳播病毒、彈出廣告、窺探用戶隱私,并且難于卸載,讓我們痛不欲生。后來(lái)數(shù)字公司推出了一個(gè)保鏢,殺了一個(gè)回馬槍?zhuān)@次把流氓給清除了出去。
當(dāng)然不能一棍子打死,插件中也有良民,比如Flash,這小子可是風(fēng)光了好多年,收到了人民群眾的廣泛喜愛(ài),當(dāng)年出現(xiàn)了很多的Flash小動(dòng)畫(huà),小視頻,小游戲,我至今還記得有好事者竟然用Flash去做《鹿鼎記》動(dòng)畫(huà),也不知道他做完沒(méi)有。
Flash極為火爆,我們讓他在我們家族寄宿了20多年,直到移動(dòng)互聯(lián)網(wǎng)時(shí)代的崛起,尤其是喬布斯的推波助瀾,這才慢慢退出歷史舞臺(tái)。
4重裝上陣:JavaScript
向我們家族里安裝插件的嘗試并不成功,但是我們還是想和桌面應(yīng)用競(jìng)爭(zhēng),讓瀏覽器應(yīng)用有著桌面應(yīng)用的體驗(yàn),這時(shí)候有個(gè)叫JavaScript的屌絲自告奮勇,說(shuō)自己可以在瀏覽器中執(zhí)行代碼,提高頁(yè)面的動(dòng)態(tài)性。
剛開(kāi)始的時(shí)候我們并不待見(jiàn)他,本來(lái)解析HTML,CSS就夠我們受的了,我們竟然還要去執(zhí)行JavaScript代碼。尤其煩人的是,JavaScript還可能對(duì)DOM進(jìn)行修改,比如讓某個(gè)元素隱藏。唉,我們只好把畫(huà)好的元素重新渲染,重新繪制。
不僅是控制界面,JavaScript還要執(zhí)行邏輯,剛開(kāi)始的時(shí)候我們覺(jué)得這小子沒(méi)啥用處,無(wú)非就是做點(diǎn)簡(jiǎn)單的判斷,像什么表單中用戶名不能為空了,密碼和確認(rèn)密碼兩個(gè)輸入框的值必須得相等之類(lèi)。
后來(lái)有人搞了一個(gè)叫做AJAX的東西出來(lái),從此發(fā)展出一堆類(lèi)似JQuery、Bootstrap、ExtJS這樣的類(lèi)庫(kù),瀏覽器中的頁(yè)面可用性越來(lái)越高,體驗(yàn)越來(lái)越好。
最后隨著前端框架(Angular,Backbone,React等)出現(xiàn),我們家族在瀏覽器中完全實(shí)現(xiàn)了界面的展示和頁(yè)面的調(diào)整,成功地做了前后端分離,把端服務(wù)器做的生意給搶過(guò)來(lái)一大部分,把他們氣得夠嗆。
5替人類(lèi)保存數(shù)據(jù)
HTML來(lái)負(fù)責(zé)結(jié)構(gòu),CSS負(fù)責(zé)展示,JavaScript負(fù)責(zé)行為,這是我們每天要做的三件大事,還有一件重要的事情就是存儲(chǔ)數(shù)據(jù)。
想當(dāng)年,人類(lèi)把HTTP設(shè)計(jì)成一個(gè)沒(méi)有狀態(tài)的協(xié)議,即使是同一個(gè)人,在極短的時(shí)間內(nèi)訪問(wèn)同一個(gè)服務(wù)器,服務(wù)器也會(huì)認(rèn)為這是完全兩個(gè)不同的請(qǐng)求,根本不知道是同一個(gè)人發(fā)出的。
當(dāng)有成千上萬(wàn)的人都來(lái)訪問(wèn)同一個(gè)服務(wù)器的時(shí)候,服務(wù)器怎么才能把大家區(qū)分開(kāi)呢?要是不區(qū)分開(kāi),服務(wù)器端怎么才能實(shí)現(xiàn)購(gòu)物車(chē)的功能?服務(wù)器怎么才能記住這個(gè)褲子是張三買(mǎi)的,那個(gè)羽絨服是李四買(mǎi)的?
后來(lái)人類(lèi)出了一個(gè)損招:當(dāng)用戶使用我們?cè)L問(wèn)服務(wù)器的時(shí)候,會(huì)給每個(gè)用戶都建立一個(gè)會(huì)話,每個(gè)會(huì)話都有一個(gè)獨(dú)一無(wú)二的ID。這個(gè)ID服務(wù)器端會(huì)保存,同時(shí)也會(huì)發(fā)回到我們?yōu)g覽器這邊,讓我們暫存起來(lái),美名其曰小餅干(cookie),聽(tīng)起來(lái)不錯(cuò),但是這個(gè)小餅干卻不能吃。
當(dāng)用戶訪問(wèn)同一網(wǎng)站的時(shí)候,我們還得把這個(gè)cookie向服務(wù)器發(fā)送過(guò)去,服務(wù)器通過(guò)cookie取到那個(gè)會(huì)話ID,于是就知道是誰(shuí)在訪問(wèn)它了。
我們保存了這個(gè)小餅干,但是大家覺(jué)得這個(gè)小餅干能保存的數(shù)據(jù)量太小,于是我們就在瀏覽器中又開(kāi)辟了兩塊巨大的空間,給JavaScript那小子提供了接口,讓他把字符串存儲(chǔ)到瀏覽器中。
這兩塊空間一個(gè)叫l(wèi)ocalStorage,一個(gè)叫sessionStorage。他們倆的用法非常相似,例如:
- localStorage.setItem("username","Andy");
- alert("username="+localStorage.getItem("username"));
這樣以來(lái)用戶就可以放心地在我們這里存儲(chǔ)大量的數(shù)據(jù)了。只不過(guò)這個(gè)sessionStorage和cookie類(lèi)似,表示的是一個(gè)會(huì)話的概念,所以一般來(lái)說(shuō),頁(yè)面一旦關(guān)閉,存儲(chǔ)的數(shù)據(jù)就會(huì)消失。
但是這個(gè)localStorage就不同了,只要你不把數(shù)據(jù)刪除,它會(huì)一直存在。這引起了我們家族有些人的強(qiáng)烈抗議:如果JavaScript那小子忘記刪除,那垃圾豈不越來(lái)越多?
沒(méi)辦法,只有拜托各位程序員了,對(duì)于無(wú)用的數(shù)據(jù),及時(shí)刪除吧。
6向多媒體和游戲進(jìn)軍
要讓瀏覽器變成一個(gè)和桌面應(yīng)用相媲美的平臺(tái),僅僅有展示頁(yè)面和存儲(chǔ)數(shù)據(jù)的能力是不夠的。我們決定增加對(duì)視頻和音頻的支持,這一次直接撕去了插件的偽裝,“赤裸裸”地在HTML中加了<video>和<audio>這兩個(gè)新標(biāo)簽,從此以后,家族的每個(gè)成員都得想辦法去播放音頻和視頻了:
- <videosrc="movie.mp4"width="320"height="240"controls="controls">
你的瀏覽器不支持video標(biāo)簽
- </video>
- <audiosrc="song.mp3"controls="controls">你的瀏覽器不支持audio標(biāo)簽</audio>
我們?cè)谑謾C(jī)上的瀏覽器兄弟們更加地努力,他們還支持直接調(diào)用手機(jī)的攝像頭去拍攝照片,這實(shí)在是太方便了。
既然都能支持音頻、視頻、攝像頭了,何不更進(jìn)一步,在瀏覽器中搞一把實(shí)時(shí)通信?
不用額外安裝插件或者第三方軟件,在兩個(gè)瀏覽器之間就能進(jìn)行語(yǔ)音或者視頻的聊天,這個(gè)想法確實(shí)是挺激動(dòng)人心的,我們把它稱(chēng)為WebRTC。不過(guò)這個(gè)想法的實(shí)現(xiàn)比較困難,雖說(shuō)我們家族的Chrome再不遺余力地推動(dòng),但是要讓我們家族在所有的平臺(tái)上(Windows,Android,iOS,mac)都實(shí)現(xiàn)了還需要繼續(xù)努力。
此外我們還一直惦記著游戲這個(gè)令人垂涎三尺的大市場(chǎng),絕對(duì)不能放過(guò)!于是我們添加了<canvas>的標(biāo)簽,意思是畫(huà)布,這樣JavaScript那小子就可以在畫(huà)布上去繪制路徑、各種形狀、添加圖片......總而言之,程序員終于可以寫(xiě)出一個(gè)只依賴瀏覽器就可以運(yùn)行的游戲了,完全不用什么Flash。
看看下面這兩個(gè)簡(jiǎn)單的小游戲,如果我不告訴你,估計(jì)你都不知道這完全是HTML+JavaScript做出來(lái)的吧:
水果忍者
中國(guó)象棋
7讓服務(wù)器從被動(dòng)變?yōu)橹鲃?dòng)
你們?nèi)祟?lèi)原來(lái)指定的HTTP協(xié)議都是從我們這里向服務(wù)器端發(fā)出請(qǐng)求,然后服務(wù)器響應(yīng),一個(gè)請(qǐng)求,一個(gè)響應(yīng),嚴(yán)格對(duì)應(yīng)。
后來(lái)大家發(fā)現(xiàn)這樣不好,服務(wù)器一直處于被動(dòng)的地位,服務(wù)器端的數(shù)據(jù)有了變化(例如股票又漲了)后,瀏覽器如果不主動(dòng)訪問(wèn)的話是不知道的,所以瀏覽器只好一遍遍不厭其煩地發(fā)送請(qǐng)求:
瀏覽器:股票漲了沒(méi)有? 服務(wù)器:沒(méi)有
瀏覽器:股票漲了沒(méi)有? 服務(wù)器:沒(méi)有
瀏覽器:股票漲了沒(méi)有? 服務(wù)器:沒(méi)有
......
瀏覽器:股票漲了沒(méi)有? 服務(wù)器:漲了
這種方式叫做輪詢,效率極為低下。
能不能讓服務(wù)器向我們主動(dòng)地推送數(shù)據(jù)呢?股票一漲,立刻就向?yàn)g覽器推送,瀏覽器即刻展示。
我們和服務(wù)器做了多次談判,終于建立了這么一個(gè)辦法:雙方建立連接以后就不要斷開(kāi)了,一直保持住,我們可以通過(guò)這個(gè)通道向服務(wù)器發(fā)送數(shù)據(jù),服務(wù)器也可以主動(dòng)向我們推送數(shù)據(jù)。
這個(gè)技術(shù),我們把它稱(chēng)為Websocket。
8總結(jié)
CSS,JavaScript,本地存儲(chǔ),多媒體和游戲,WebSocket......這只是我們家族提供的一部分服務(wù),還有很多其他的服務(wù)例如HTML標(biāo)簽的語(yǔ)義化:我們提供了很多諸如<article>,<section>,<nav>,<aside>,<header>,<footer>這樣的標(biāo)簽來(lái)幫助你們?nèi)祟?lèi)更好的定義網(wǎng)頁(yè)的結(jié)構(gòu)。
這樣的東西非常非常的多,我想你應(yīng)該明白我們家族的不斷努力了吧,如果你一邊聽(tīng)我講一邊在思考,也許會(huì)悟出我們的使命:不斷提升應(yīng)用程序在Web端的體驗(yàn)。
也許有一天,所有的桌面應(yīng)用都消失了,全部變成了在瀏覽器中運(yùn)行的程序,那將是我們?yōu)g覽器家族榮耀的頂峰,我們期待著這一天早日到來(lái)。
【本文為51CTO專(zhuān)欄作者“劉欣”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)通過(guò)作者微信公眾號(hào)coderising獲取授權(quán)】