關(guān)于本系列
HTML 5 是一項(xiàng)被大肆宣揚(yáng)的技術(shù),但是它實(shí)至名歸。它有望成為一個(gè)技術(shù)引爆點(diǎn),將桌面應(yīng)用程序功能引向?yàn)g覽器。它不僅適用于傳統(tǒng)瀏覽器,甚至也針對(duì)移動(dòng)瀏覽器。更好的是,最流行的移動(dòng)瀏覽器已經(jīng)采納和實(shí)現(xiàn) HTML 5 規(guī)范的很多重要部分。
在這個(gè)五部分的系列中,我們將詳細(xì)了解幾個(gè)新技術(shù),它們都是 HTML 5 的一部分,可以大大影響移動(dòng) Web
應(yīng)用程序開發(fā)。在每一部分中,都將開發(fā)一個(gè)可以工作的移動(dòng) Web 應(yīng)用程序,展示一個(gè)可以用于現(xiàn)代移動(dòng) Web 瀏覽器(比如 iPhone 和基于
Android 的設(shè)備上的瀏覽器)的 HTML 5 特性。
回頁首
先決條件
在本文中,您將使用最新 Web 技術(shù)開發(fā) Web 應(yīng)用程序。這里的大多數(shù)代碼只是 HTML、JavaScript 和 CSS —
任何 Web
開發(fā)人員的核心技術(shù)。需要的最重要的東西是用于測(cè)試代碼的瀏覽器。本文中的大多數(shù)代碼將運(yùn)行在最新的桌面瀏覽器上,例外的情況會(huì)指出來。當(dāng)然,還必須在移
動(dòng)瀏覽器上進(jìn)行測(cè)試,您肯定希望最新的 iPhone 和 Android SDK 支持這些代碼。本文中使用的是 iPhone SDK 3.1.3 和
Android SDK 2.1。參見 參考資料 中的鏈接。
回頁首
本地存儲(chǔ)基礎(chǔ)
Web 開發(fā)人員多年來一直在嘗試將數(shù)據(jù)存儲(chǔ)在客戶機(jī)上。HTTP Cookies 被濫用于此目的。開發(fā)人員將大量數(shù)據(jù)擠放在 HTTP
規(guī)范分配的 4KB 上。原因很簡(jiǎn)單。出于各種原因,交互式 Web
應(yīng)用程序需要存儲(chǔ)數(shù)據(jù),并且將這些數(shù)據(jù)存儲(chǔ)在服務(wù)器上通常效率低下、不安全或者不適當(dāng)。多年來,這個(gè)問題有了好幾種備選方法。各種各樣的瀏覽器已經(jīng)引入了
專有存儲(chǔ) API。開發(fā)人員也利用了 Flash Player 中的擴(kuò)展存儲(chǔ)功能(通過 JavaScript 實(shí)現(xiàn))。類似地,Google
為各種瀏覽器創(chuàng)建了 Gears 插件,并且它包含了存儲(chǔ) API。毫不奇怪的是,一些 JavaScript
庫試圖抹平這些差異。換句話說,這些庫提供一個(gè)簡(jiǎn)單的 API,然后檢查有哪些存儲(chǔ)功能(可能是一個(gè)專有瀏覽器 API 或者是一個(gè)諸如 Flash
的插件)。
對(duì) Web 開發(fā)人員來說幸運(yùn)的是,HTML 5
規(guī)范最終包含了一個(gè)針對(duì)本地存儲(chǔ)的標(biāo)準(zhǔn),被廣泛的瀏覽器所實(shí)現(xiàn)。事實(shí)上,該標(biāo)準(zhǔn)是最快被采納的標(biāo)準(zhǔn),在所有主要瀏覽器的最新版本中都受到支
持:Microsoft?、Internet Explorer?、Mozilla Firefox、Opera、Apple
Safari 和 Google Chrome。對(duì)于移動(dòng)開發(fā)人員更為重要的是,它在基于 WebKit 的瀏覽器(諸如 iPhone 和使用
Android(版本 2.0 或更高版本)的手機(jī)中的瀏覽器)以及其他移動(dòng)瀏覽器(比如 Mozilla 的
Fennec)中受到支持。記住這一點(diǎn),我們來看一下這個(gè) API。
Storage
API
localStorage
API 十分簡(jiǎn)單。實(shí)際上,根據(jù) HTML
5 規(guī)范,它實(shí)現(xiàn)了 DOM Storage 接口。差別的原因是,HTML 5 指定兩個(gè)不同的對(duì)象實(shí)現(xiàn)該接口:localStorage
和 sessionStorage
。sessionStorage
對(duì)象是一個(gè)只在會(huì)話期間存儲(chǔ)數(shù)據(jù)的 Storage
實(shí)現(xiàn)。更確切地說,只要沒有可以訪問 sessionStorage
的腳本正在運(yùn)行,瀏覽器就可以刪除 sessionStorage
數(shù)據(jù)。這是與 localStorage
相對(duì)的,后者跨多個(gè)用戶會(huì)話。兩個(gè)對(duì)象共享相同的 API,所以我將只著重介紹 localStorage
。
Storage
API 是一種經(jīng)典的名/值對(duì)數(shù)據(jù)結(jié)構(gòu)。您將使用的最常見的方法是 getItem(name)
和
setItem(name, value)
。這些方法完全跟您預(yù)期的一樣:getItem
返回與名稱相關(guān)聯(lián)的值,如果什么都不存在,則返回 null,而 setItem
要么是將名/值對(duì)添加到 localStorage
,要么是取代現(xiàn)有值。還有一個(gè) removeItem(name)
,顧名思意,它從 localStorage
刪除一個(gè)名/值對(duì)(如果存在的話,否則什么都不做)。最后,對(duì)于在所有名/值對(duì)上迭代,存在兩個(gè) API。一個(gè)是長(zhǎng)度屬性,給出正在存儲(chǔ)的名/值對(duì)的總數(shù)。對(duì)應(yīng)地,一個(gè) key(index)
方法從存儲(chǔ)中使用的所有名稱中返回一個(gè)名稱。
利用這些簡(jiǎn)單的 API,可以完成大量任務(wù),比如說個(gè)性化或跟蹤用戶行為。這些可以說對(duì)移動(dòng) Web 開發(fā)人員是重要的用例,但是還有一個(gè)更為重要的用例:高速緩存。利用 localStorage
,可以在客戶機(jī)的本地機(jī)器上容易地從服務(wù)器高速緩存數(shù)據(jù)。這讓您無需等待可能緩慢的服務(wù)器回調(diào),并且最小化了對(duì)服務(wù)器上數(shù)據(jù)的需求量?,F(xiàn)在來看一個(gè)例子,演示了如何使用 localStorage 來獲得這種高速緩存。
回頁首
例子:利用本地存儲(chǔ)實(shí)現(xiàn)高速緩存
本例建立在本系列第 1 部分中的例子之上,那時(shí)您最先開始了 t0 開發(fā)。那個(gè)例子展示了如何通過利用地理定位 API
取得用戶的位置而執(zhí)行 Twitter 的本地搜索。從那個(gè)例子開始,對(duì)它進(jìn)行簡(jiǎn)化,并大大提高它的性能。首先,將那個(gè)例子簡(jiǎn)化成不帶地理位置的
Twitter 搜索。清單 1 展示了簡(jiǎn)化的 Twitter 搜索應(yīng)用程序。
清單 1. 最基本的 Twitter 搜索
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name = "viewport" content = "width = device-width"/>
<title>Basic Twitter Search</title>
<script type="text/javascript">
function searchTwitter(){
var query = "http://search.twitter.com/search.json?callback
=showResults&q=";
query += $("kwBox").value;
var script = document.createElement("script");
script.src = query;
document.getElementsByTagName("head")[0].appendChild(script);
}
// ui code deleted for brevity
function showResults(response){
var tweets = response.results;
tweets.forEach(function(tweet){
tweet.linkUrl = "http://twitter.com/" + tweet.from_user
+ "/status/" + tweet.id;
});
makeResultsTable(tweets);
}
</script>
<!-- CSS deleted for brevity -->
</head>
<body>
<div id="main">
<label for="kwBox">Search Twitter:</label>
<input type="text" id="kwBox"/>
<input type="button" value="Go!" onclick="searchTwitter()"/>
</div>
<div id="results">
</div>
</body>
</html>
|
在這個(gè)應(yīng)用程序中,使用了 Twitter 搜索 API 對(duì) JSONP 的支持。用戶提交搜索時(shí),會(huì)動(dòng)態(tài)添加一個(gè)腳本標(biāo)記到頁面并指定回調(diào)函數(shù)的名稱,從而進(jìn)行一次 API 調(diào)用。這允許您從 Web 頁面進(jìn)行一次跨域調(diào)用。一旦調(diào)用返回,回調(diào)函數(shù)(showResults
)就會(huì)被調(diào)用。您添加一個(gè)鏈接 URL 到 Twitter 返回的每個(gè) tweet,然后創(chuàng)建一個(gè)簡(jiǎn)單的表格用于顯示這些 tweet。為了提速,您可以高速緩存從搜索查詢得到的結(jié)果,然后在用戶每次提交查詢時(shí)使用這些緩存的結(jié)果。首先來看如何使用 localStorage
來本地存儲(chǔ) tweet。
本地保存
基本的 Twitter 搜索將從 Twitter 搜索 API 提供一組 tweet。如果您可以本地保存這些
tweet,并將它們與生成它們的關(guān)鍵詞搜索相關(guān)聯(lián),那么您就具有了一個(gè)有用的高速緩存。要保存 tweet,您只需要修改當(dāng)對(duì) Twitter 搜索
API 的調(diào)用返回時(shí)將被調(diào)用的 callback
函數(shù)。清單 2 展示了修改后的函數(shù)。
清單 2. 搜索和保存
function searchTwitter(){
var keyword = $("kwBox").value;
var query = "http://search.twitter.com/search.json?callback
=processResults&q=";
query += keyword;
var script = document.createElement("script");
script.src = query;
document.getElementsByTagName("head")[0].appendChild(script);
}
function processResults(response){
var keyword = $("kwBox").value;
var tweets = response.results;
tweets.forEach(function(tweet){
saveTweet(keyword, tweet);
tweet.linkUrl = "http://twitter.com/" + tweet.from_user + "/status/" + tweet.id;
});
makeResultsTable();
addTweetsToResultsTable(tweets);
}
function saveTweet(keyword, tweet){
// check if the browser supports localStorage
if (!window.localStorage){
return;
}
if (!localStorage.getItem("tweet" + tweet.id)){
localStorage.setItem("tweet" + tweet.id, JSON.stringify(tweet));
}
var index = localStorage.getItem("index::" + keyword);
if (index){
index = JSON.parse(index);
} else {
index = [];
}
if (!index.contains(tweet.id)){
index.push(tweet.id);
localStorage.setItem("index::"+keyword, JSON.stringify(index));
}
}
|
從第一個(gè)函數(shù) searchTwitter
開始。這在用戶提交搜索時(shí)被調(diào)用。相對(duì)于 清單 1 做了改動(dòng)的惟一的地方是 callback
函數(shù)。不只是在 tweet 返回時(shí)顯示它們,您還需要處理它們(除了顯示,還要保存它們)。因此,您指定一個(gè)新的 callback
函數(shù) processResults
。您針對(duì)每個(gè) tweet 調(diào)用 saveTweet
。您還傳遞被用于生成搜索結(jié)果的關(guān)鍵詞。這是因?yàn)槟胍獙⑦@些 tweet 與該關(guān)鍵詞相關(guān)聯(lián)。
在 saveTweet
函數(shù)中,首先進(jìn)行檢查,確保 localStorage
真正受到瀏覽器的支持。正如前面所提到的,localStorage
在桌面和移動(dòng)瀏覽器中都受到廣泛支持,但是在使用這樣的新特性時(shí)進(jìn)行檢查總是一個(gè)好主意。如果它不受支持,那么您簡(jiǎn)單地從函數(shù)返回。顯然不會(huì)保存任何東
西,但是也不會(huì)報(bào)錯(cuò) — 應(yīng)用程序在這種情況下只是不會(huì)具有高速緩存。如果 localStorage
受到支持,那么首先進(jìn)行檢查,看這個(gè) tweet 是否已經(jīng)存儲(chǔ)。如果沒有存儲(chǔ),那么使用 setItem
本地存儲(chǔ)它。接下來,檢索一個(gè)對(duì)應(yīng)于關(guān)鍵詞的索引對(duì)象。這只是一組與關(guān)鍵詞相關(guān)聯(lián)的 tweet 的 ID。如果 tweet ID 還不是索引的一部分,那么添加它并更新索引。
注意,在 清單 3 中保存和加載 JSON 時(shí),您使用了 JSON.stringify
和 JSON.parse
。JSON 對(duì)象(或者更確切地說,是 window.JSON
)是 HTML 5 規(guī)范的一部分,作為一個(gè)總是存在的 原生 對(duì)象。stringify
方法將把任何 JavaScript 對(duì)象轉(zhuǎn)換成一個(gè)序列化的字符串,而 parse
方法則進(jìn)行相反的操作,它從序列化的字符串表示還原 JavaScript 對(duì)象。這是很必要的,因?yàn)?localStorage
只存儲(chǔ)字符串。但是,原生 JSON 對(duì)象并不被廣泛實(shí)現(xiàn)為 localStorage
。例如,它不出現(xiàn)在 iPhone(在撰寫本文時(shí)是版本 3.1.3)的最新 Mobile
Safari 瀏覽器上。它在最新 Android 瀏覽器上受支持。您可以容易地檢查它是否在那里,如果不在,就加載一個(gè)額外的 JavaScript 文件。您可以通過訪問 Web 站點(diǎn)(參見 參考資料),獲得原生使用的相同 JSON 對(duì)象。要本地查看這些序列化的字符串是什么樣的,可以使用各種瀏覽器工具檢查 localStorage 中為給定站點(diǎn)存儲(chǔ)的內(nèi)容。圖 1 展示了一些高速緩存的 tweet,它們存儲(chǔ)在本地,使用 Chrome 的 Developer Tools 進(jìn)行查看。
圖 1. 本地高速緩存的 tweet
Chrome 和 Safari 都內(nèi)置了開發(fā)人員工具,可以用于查看任何保存在 localStorage
中的數(shù)據(jù)。這對(duì)于調(diào)試使用 localStorage
的應(yīng)用程序非常有用。它以純文本形式展示本地存儲(chǔ)的鍵/值對(duì)。既然您已經(jīng)開始保存來自 Twitter 的搜索 API 的 tweet,以便它們可以被用作高速緩存,所以您只需開始從 localStorage
讀取它們即可。下面來看這是如何做到的。
快速本地?cái)?shù)據(jù)加載
在 清單 2 中,您看到了一些例子使用 getItem
方法從 localStorage
讀取數(shù)據(jù)?,F(xiàn)在當(dāng)一個(gè)用戶提交搜索時(shí),您可以檢查高速緩存命中情況,并立即加載緩存的結(jié)果。當(dāng)然,您仍將針對(duì) Twitter 搜索 API
進(jìn)行查詢,因?yàn)槿藗円恢痹诋a(chǎn)生 tweet 并添加到搜索結(jié)果。但是,通過只尋找還沒在高速緩存中的結(jié)果,現(xiàn)在您也有了讓查詢更為高效的方式。清單 3 展示了更新后的搜索代碼。
清單 3. 首先進(jìn)行本地搜索
function searchTwitter(){
if ($("resultsTable")){
$("resultsTable").innerHTML = ""; // clear results
}
makeResultsTable();
var keyword = $("kwBox").value;
var maxId = loadLocal(keyword);
var query = "http://search.twitter.com/search.json?callback=processResults&q=";
query += keyword;
if (maxId){
query += "&since_id=" + maxId;
}
var script = document.createElement("script");
script.src = query;
document.getElementsByTagName("head")[0].appendChild(script);
}
function loadLocal(keyword){
if (!window.localStorage){
return;
}
var index = localStorage.getItem("index::" + keyword);
var tweets = [];
var i = 0;
var tweet = {};
if (index){
index = JSON.parse(index);
for (i=0;i<index.length;i++){
tweet = localStorage.getItem("tweet"+index[i]);
if (tweet){
tweet = JSON.parse(tweet);
tweets.push(tweet);
}
}
}
if (tweets.length < 1){
return 0;
}
tweets.sort(function(a,b){
return a.id > b.id;
});
addTweetsToResultsTable(tweets);
return tweets[0].id;
}
|
您將注意到的第一件事情是,當(dāng)一個(gè)搜索被提交時(shí),您首先調(diào)用新的 loadLocal
函數(shù)。該函數(shù)返回一個(gè)整數(shù),即高速緩存中找到的最新 tweet 的 ID。loadLocal
函數(shù)接受一個(gè) keyword
作為參數(shù),該關(guān)鍵詞也被用于在 localStorage
高速緩存中尋找相關(guān) tweet。如果具有一個(gè) maxId
,那么使用它來修改對(duì) Twitter 的查詢,添加 since_id
參數(shù)。您在告訴 Twitter API 只返回比該參數(shù)中給定的 ID 新的 tweet。潛在地,這可以減少從 Twitter
返回的結(jié)果數(shù)量。您任何時(shí)候都可以為移動(dòng) Web 應(yīng)用程序優(yōu)化服務(wù)器調(diào)用,因?yàn)樗梢哉嬲纳坡僖苿?dòng)網(wǎng)絡(luò)上的用戶體驗(yàn)?,F(xiàn)在更仔細(xì)地來看一下 loadLocal
。
在 loadLocal
函數(shù)中,您利用了存儲(chǔ)在前面 清單 2 中的數(shù)據(jù)結(jié)構(gòu)。通過使用 getItem
,
您首先加載與關(guān)鍵詞相關(guān)聯(lián)的索引。如果沒找到任何索引,那么就沒有緩存的 tweet,所以就沒有展示的東西,并且沒有可對(duì)查詢進(jìn)行的優(yōu)化(您返回一個(gè) 0
值以指示這一點(diǎn))。如果找到一個(gè)索引,那么您從它得到 ID 列表。這些 tweet 中的每一個(gè)都被本地高速緩存,所以您只需再次使用 getItem
方法,從高速緩存加載每一個(gè) tweet。加載的 tweet 然后被排序。使用 addTweetsToResultsTable
函數(shù)來顯示 tweet,然后返回最新 tweet 的 ID。在本例中,得到新 tweet 的 代碼直接調(diào)用更新 UI 的函數(shù)。您可能會(huì)對(duì)此感到驚訝,因?yàn)樗诖鎯?chǔ)和檢索 tweet 的代碼與顯示它們的代碼之間創(chuàng)建了耦合,全都通過 processResults
函數(shù)。使用存儲(chǔ)事件會(huì)提供一種備選的、更少耦合的方法。
存儲(chǔ)事件
現(xiàn)在擴(kuò)展示例應(yīng)用程序,展示最可能具有緩存結(jié)果的前 10 個(gè)搜索條目。這可能代表用戶最常提交的搜索。清單 4 展示了一個(gè)用于計(jì)算并顯示前 10 個(gè)搜索條目的函數(shù)。
清單 4. 計(jì)算前 10 個(gè)搜索條目
function displayStats(){
if (!window.localStorage){ return; }
var i = 0;
var key = "";
var index = [];
var cachedSearches = [];
for (i=0;i<localStorage.length;i++){
key = localStorage.key(i);
if (key.indexOf("index::") == 0){
index = JSON.parse(localStorage.getItem(key));
cachedSearches.push ({keyword: key.slice(7), numResults: index.length});
}
}
cachedSearches.sort(function(a,b){
if (a.numResults == b.numResults){
if (a.keyword.toLowerCase() < b.keyword.toLowerCase()){
return -1;
} else if (a.keyword.toLowerCase() > b.keyword.toLowerCase()){
return 1;
}
return 0;
}
return b.numResults - a.numResults;
}).slice(0,10).forEach(function(search){
var li = document.createElement("li");
var txt = document.createTextNode(search.keyword + " : " + search.numResults);
li.appendChild(txt);
$("stats").appendChild(li);
});
}
|
該函數(shù)充分展示了 localStorage
API。您首先得到存儲(chǔ)在 localStorage
中的條目的總數(shù),然后再迭代這些條目。如果條目是索引,那么您就解析該對(duì)象并創(chuàng)建一個(gè)表示您要處理的數(shù)據(jù)的對(duì)象:與索引相關(guān)聯(lián)的關(guān)鍵詞和索引中 tweet 的數(shù)量。該數(shù)據(jù)存儲(chǔ)在一個(gè)叫做 cachedSearches
的數(shù)組中。接下來,排序 cachedSearches
,將具有最多結(jié)果的搜索排在第一位,如果兩個(gè)搜索具有相同數(shù)量的緩存結(jié)果,就再使用一個(gè)不區(qū)分大小寫的字母排序。然后對(duì)于前 10 個(gè)搜索,為每個(gè)搜索創(chuàng)建 HTML,并將它們附加到一個(gè)排好序的列表。讓我們?cè)陧撁娉醮渭虞d時(shí)調(diào)用該函數(shù),如 清單 5 所示。
清單 5. 初始化頁面
window.onload = function() {
displayStats();
document.body.setAttribute("onstorage", "handleOnStorage();");
}
|
第一行在頁面加載時(shí)調(diào)用 清單 4 中的函數(shù)。第二次加載是變得更有趣的地方。您在這里為 onstorage
事件設(shè)置一個(gè)事件處理程序。每當(dāng) localStorage.setItem
函數(shù)執(zhí)行完成,該事件就會(huì)激活。這將允許您重新計(jì)算前 10 個(gè)搜索。清單 6 展示了該事件處理程序。
清單 6. Storage 事件處理程序
function handleOnStorage() {
if (window.event && window.event.key.indexOf("index::") == 0){
$("stats").innerHTML = "";
displayStats();
}
}
|
onstorage
事件將與窗口相關(guān)聯(lián)。它具有幾個(gè)有用的屬性:key
、oldValue
和 newValue
。除了這些自解釋的屬性之外,它還有一個(gè) url
(更改值的頁面的 URL)和 source
(包含更改值的腳本的窗口)。如果用戶具有多個(gè)到應(yīng)用程序的窗口或選項(xiàng)卡或者甚至是 iFrames,那么這最后兩個(gè)屬性就更有用,但是沒有哪一個(gè)在移動(dòng)應(yīng)用程序中特別常見?;氐?清單 6,您真正需要的惟一的屬性是 key
屬性。您使用該屬性來看它是不是一個(gè)已修改的索引。如果是的,那么您重新設(shè)置前 10 名列表,并通過再次調(diào)用 displayStats
函數(shù)而重新繪制它。該技術(shù)的優(yōu)點(diǎn)是,其他函數(shù)都不需要了解前 10 名列表,因?yàn)樗亲园摹?/p>
前面 我提到過,DOM Storage(它包含 localStorage
和 sessionStorage
)
總體來說是一個(gè)被廣泛采納的 HTML 5 特性。但是,存儲(chǔ)事件對(duì)于這一點(diǎn)來說是一個(gè)例外 —
至少在桌面瀏覽器上如此。在撰寫本文時(shí),僅有的支持存儲(chǔ)事件的桌面瀏覽器是 Safari 4+ 和 Internet Explorer 8+。在
Firefox、Chrome 和 Opera 中不受支持。但是在移動(dòng)領(lǐng)域,情況稍有好轉(zhuǎn)。iPhone 和 Android
瀏覽器的最新版都完全支持存儲(chǔ)事件,并且這里給出的代碼都能在這些瀏覽器中完美地運(yùn)行。
回頁首
結(jié)束語
作為一名開發(fā)人員,突然在客戶機(jī)上擁有巨額的存儲(chǔ)空間,您會(huì)覺得自己獲得了很大的解放。對(duì)于長(zhǎng)期的 Web
開發(fā)人員來說,為做到他們多年來一直想做、卻苦于找不到好的方式來做的事情帶來了轉(zhuǎn)機(jī)。對(duì)于移動(dòng)開發(fā)人員來說,則更為振奮人心,因?yàn)樗嬲_啟了數(shù)據(jù)的本
地高速緩存。除了大大改善應(yīng)用程序的性能之外,本地高速緩存對(duì)于推進(jìn)移動(dòng) Web 應(yīng)用程序的另一個(gè)新的令人振奮的功能 —— 離線 ——
是很關(guān)鍵的。這將是本系列下一篇文章的主題。
回頁首
下載
描述 | 名字 | 大小 | 下載方法 |
文章源代碼 |
local.zip |
8KB |
HTTP |
關(guān)于下載方法的信息
學(xué)習(xí)
獲得產(chǎn)品和技術(shù)
討論
關(guān)于作者
Michael Galpin 是 eBay 的一名架構(gòu)師。他經(jīng)常為 developerWorks 撰寫文章,同時(shí)在 TheServerSide.com 和 Java Developer 期刊上發(fā)表文章,他還有自己的 博客。他從 1998 年開始做職業(yè)程序員,并擁有加州理工學(xué)院數(shù)學(xué)專業(yè)的學(xué)士學(xué)位。