java學習

          java學習

           

          myeclipse自動提示配置

          1. 打開MyEclipse,然后 Window-------->Preferences;

           

          2.選擇Java-------->展開-------->Editor-------->選擇ContentAssist;

           

          3.選擇ContentAssist-------->然后看到右邊-------->右邊的Auto-Activation下面的Auto Activation triggers for java(指觸發(fā)代碼提示的就是”.”這個符號)這個選項;

          4. AutoActivation triggers for java這個選項-------->在”.”后加
          abcdefghijklmnopqrstuvwxyz
          字母,方便后面的查找修改-------->然后apply-------->點擊OK;

          posted @ 2013-01-18 14:15 楊軍威 閱讀(110) | 評論 (0)編輯 收藏

          struts2標簽

               摘要: Struts2 Taglib抽象了不同表示技術(shù),現(xiàn)在Struts2主要支持三種表示技術(shù):JSP,FreeMarker和Velocity。但部分的Tag在三種表示技術(shù)下都可以使用,但是也有部分只能在某一種情況下使用。 Tab可以分為兩類:通用標簽和UI標簽。 4.1節(jié) 通用標簽 通用標簽用來在頁面表示的時候控制代碼執(zhí)行的過程,這些標簽也允許從Action或者值堆棧中取得數(shù)據(jù)。例如地域,JavaBea...  閱讀全文

          posted @ 2013-01-18 13:38 楊軍威 閱讀(793) | 評論 (0)編輯 收藏

          tomcat中配置數(shù)據(jù)庫連接池

          4.1連接池知識簡介

          眾所周知建立數(shù)據(jù)庫連接是一個非常耗時耗資源的行為,因此現(xiàn)代的Web中間件,無論是開源的Tomcat、Jboss還是商業(yè)的 websphere、weblogic都提供了數(shù)據(jù)庫連接池功能,可以毫不夸張的說,數(shù)據(jù)庫連接池性能的好壞,不同廠商對連接池有著不同的實現(xiàn),本文只介 紹拜特公司使用較多的開源web中間件Tomcat中默認的連接池DBCP(DataBase connection pool)的使用。

          4.2 Tomcat下配置連接池

          下面以tomcat5.5.26為例來介紹如何配置連接池

          1:需要的jar

          在tomcat的安裝目錄common\lib下有一個naming-factory-dbcp.jar,這個是tomcat修改后的dbcp連接池實現(xiàn),同時為了能夠正常運行,還需要commons-pool.jar。

          2:建立context文件

          進入到conf\Catalina\localhost新建一個上下文文件,文件的名稱既為將來要訪問是輸入url上下文名稱,例如我們建立一個名為btweb的文件內(nèi)容如下:

          <Context debug="0"docBase="D:\v10_workspace\build\WebRoot"  reloadable="false">

             <Resource

             name="jdbc/btdb1"

            type="javax.sql.DataSource"

            factory="org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory"

              username="v10"

              password="v10"

          driverClassName="oracle.jdbc.driver.OracleDriver"

          url="jdbc:oracle:thin:@127.0.0.1:1521:cahs"

              maxActive="5"

              maxIdle="3"

              maxWait="5000"

              removeAbandoned="true"

          removeAbandonedTimeout="60"

          testOnBorrow="true"

              validationQuery="selectcount(*) from bt_user"

              logAbandoned="true"

                 />

            </Context>

          4.3參數(shù)分步介紹

          數(shù)據(jù)庫連接相關(guān)

          username="v10"

             password="v10"

          driverClassName="oracle.jdbc.driver.OracleDriver"

          url="jdbc:oracle:thin:@127.0.0.1:1521:cahs"

          jndi相關(guān)

          name="jdbc/btdb1"

            type="javax.sql.DataSource"

            factory="org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory"

          factory默認是org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory,tomcat也允許采用其他連接實現(xiàn),不過默認使用dbcp。

          連接數(shù)控制與連接歸還策略

              maxActive="5" 

          maxIdle="3" 

          minIdle=”2”

              maxWait="5000"

            應對網(wǎng)絡不穩(wěn)定的策略

          testOnBorrow="true"

              validationQuery="selectcount(*) from bt_user"

          應對連接泄漏的策略

              removeAbandoned="true"

          removeAbandonedTimeout="60"

              logAbandoned="true"

            

          如下圖所示:連接池處于應用程序與數(shù)據(jù)庫之間,一方面應用程序通過它來獲取連接,歸還連接,另一方面連接又需要從數(shù)據(jù)里獲取連接,歸還連接。

          步驟1:系統(tǒng)啟動

          系統(tǒng)啟動時,初始化連接池,由于沒有任何請求連接池中連接數(shù)為0。

          maxActive="5"

          表示并發(fā)情況下最大可從連接池中獲取的連接數(shù)。如果數(shù)據(jù)庫不是單獨,供一個應用使用,通過設置maxActive參數(shù)可以避免某個應用無限制的獲取 連接對其他應用造成影響,如果一個數(shù)據(jù)庫只是用來支持一個應用那么maxActive理論上可以設置成該數(shù)據(jù)庫可以支撐的最大連接數(shù)。maxActive 只是表示通過連接池可以并發(fā)的獲取的最大連接數(shù)。

          從圖上我們可以看到連接的獲取與釋放是雙向,當應用程序并發(fā)請求連接池時,連接池就需要從數(shù)據(jù)庫獲取連接,那么但應用程序使用完連接并將連接歸還給 連接池時,連接池是否也同時將連接歸還給數(shù)據(jù)庫呢?很顯然答案是否定的,如果那樣的話連接池就變得多此一舉,不但不能提高性能,反而會降低性能,那么但應 用成歸還連接后,連接池如何處理呢?

          maxIdle="3"

          如果在并發(fā)時達到了maxActive=5,那么連接池就必須從數(shù)據(jù)庫中獲取5個連接來供應用程序使用,當應用程序關(guān)閉連接后,由于maxIdle=3,因此并不是所有的連接都會歸還給數(shù)據(jù)庫,將會有3個連接保持在連接池種中,狀態(tài)為空閑。

          minIdle=”2”

          最小默認情況下并不生效,它的含義是當連接池中的連接少有minIdle,系統(tǒng)監(jiān)控線程將啟動補充功能,一般情況下我們并不啟動補充線程。

          問題:如何設置maxActive和maxIdle?

          理論上講maxActive應該設置成應用的最大并發(fā)數(shù),這樣一來即便是在最大并發(fā)的情況下,應用依然能夠從連接池中獲取連接,但是困難時的是我們 很難準確估計到最大并發(fā)數(shù),設置成最大并發(fā)數(shù)是一種最優(yōu)的服務質(zhì)量保證,事實上,如果某個用戶登錄提示系統(tǒng)繁忙,那么在他再次登錄時,可能系統(tǒng)資源已經(jīng)充 足,對于拜特資金管理系統(tǒng)我們建議將maxActive設置為系統(tǒng)注冊人數(shù)的十分之一到二十分之一之間。例如系統(tǒng)的注冊人數(shù)為1000,那么設置成50-100靠近100的數(shù)字,例如85或90。

          maxIdle對應的連接,實際上是連接池保持的長連接,這也是連接池發(fā)揮優(yōu)勢的部分,理論上講保持較多的長連接,在應用請求時可以更快的響應,但是過多的連接保持,反而會消耗數(shù)據(jù)庫大量的資源,因此maxIdle也并不是越大越好,同上例我們建議將maxIdle設置成

          50-100中靠近50的數(shù)字,例如55。這樣就能在兼顧最大并發(fā)同時,保持較少的數(shù)據(jù)庫連接,而且在絕大多情況,能夠為應用程序提供最快的相應速度。

          testOnBorrow="true"

          validationQuery="selectcount(*) from bt_user"

          我們知道數(shù)據(jù)庫連接從本質(zhì)上架構(gòu)在tcp/ip連接之上,一般情況下web服務器與數(shù)據(jù)庫服務器都不在同一臺物理機器上,而是通過網(wǎng)絡進行連接,那 么當建立數(shù)據(jù)庫連接池的機器與數(shù)據(jù)庫服務器自己出現(xiàn)網(wǎng)絡異常時,保持在連接池中的連接將失效,不能夠在次使用,傳統(tǒng)的情況下只能通過重新啟動,再次建立連 接,通過設置以上兩個參數(shù),但應用程序從連接池中獲取連接時,會首先進行活動性檢測,當獲取的連接是活動的時候才會給應用程序使用,如果連接失效,連接將 釋放該連接。validationQuery是一條測試語句,沒有實際意義,現(xiàn)實中,一般用一條最為簡單的查詢語句充當。

          removeAbandoned="true"

          removeAbandonedTimeout="60"

          logAbandoned="true"

          有時粗心的程序編寫者在從連接池中獲取連接使用后忘記了連接的關(guān)閉,這樣連池的連接就會逐漸達到maxActive直至連接池無法提供服務。現(xiàn)代連接池一般提供一種“智能”的檢查,但設置了removeAbandoned="true"時,當連接池連接數(shù)到達(getNumIdle() < 2) and (getNumActive() > getMaxActive() - 3)時便會啟動連接回收,那種活動時間超過removeAbandonedTimeout="60"的連接將會被回收,同時如果logAbandoned="true"設置為true,程序在回收連接的同時會打印日志。removeAbandoned是連接池的高級功能,理論上這中配置不應該出現(xiàn)在實際的生產(chǎn)環(huán)境,因為有時應用程序執(zhí)行長事務,可能這種情況下,會被連接池誤回收,該種配置一般在程序測試階段,為了定位連接泄漏的具體代碼位置,被開啟,生產(chǎn)環(huán)境中連接的關(guān)閉應該靠程序自己保證。

          posted @ 2013-01-18 13:27 楊軍威 閱讀(3462) | 評論 (0)編輯 收藏

          strut2ognl

          經(jīng)過一段時間的閉關(guān)練習,終于對struts2有所了解.其實struts2并不難, 一看就能明白其中奧妙.我對struts2的驗證體系保留懷疑態(tài)度,因為它的驗證消息使用標簽打在頁面上,實在太丑,在真實項目中不知道是否有人這么做. 也許是我太菜了,還不知道如何將驗證消息顯示得更友好,希望各位不吝拍磚指導.
            然而,我認為struts2最復雜難學的是它內(nèi)置的ognl表達式.這個ognl在我開始學struts2時,讓我云里霧里,不知如何應對.經(jīng)過幾輪 的翻看書籍,與網(wǎng)上資料查詢,還算是讓我有所明白一點.在此記錄,以便日后溫習,同時,如果這篇文章對各位有哪怕一點幫助,那便是我最大的榮幸.
            首先,要知道ognl最主要的功能就是取數(shù)據(jù),它可以利用一段簡短的表達式取出各種各樣豐富的數(shù)據(jù).其次,它還附帶了一些便捷的功能,如:方法調(diào)用、 靜態(tài)方法和屬性調(diào)用、數(shù)值運算……我們最關(guān)心的是如何取數(shù)據(jù),因此,接下來我將重點介紹如何取數(shù)據(jù),至于附帶功能將不做介紹。
            知道了ognl最主要的功能是取數(shù)據(jù)后,那么數(shù)據(jù)從哪里取呢!ognl會從兩個地方取:一個是Action的實例屬性;另一個是 ValueStack(中文名叫值棧)。ognl會先從前者里面取,如果沒取到再到ValueStack里取。Action的實例屬性好理解,但這個 ValueStack從字面上看,著實不好理解,以致于我將struts2的源碼引進eclipse里,單步調(diào)試才算有所啟發(fā)。可以將 ValueStack初步理解為一個map,在這個map里存儲了request、session、application、response、 action實例、parameters數(shù)組……還有很多你不知道的對象。有了這個map,還愁數(shù)據(jù)取不到嗎。
            注意:將ValueStack初步理解為一個map,只適于初學struts2的人,其實它內(nèi)部并沒這么簡單。由于水平、時間有限,我并不能掌握其內(nèi) 部精髓,加上表達能力不佳,怕表達不對誤導大家,所以我們姑且理解ValueStack為一個map吧。如果想更深的了解的ValueStack,請查看 struts2的源碼。

            接下來,便是取數(shù)據(jù)。取action實例的屬性數(shù)據(jù)與取ValueStack中的數(shù)據(jù)不一樣,先說取action實例的屬性數(shù)據(jù)吧。
            action實例的屬性數(shù)據(jù)可以直接在struts2的標簽中通過屬性名取到。如:<s:property value="name"/>、<s:property value="user.password"/>
            注意:不要加#號。

            再是取ValueStack中的數(shù)據(jù)。
            struts2提供三種方式通過ognl表達式來取ValueStack中的數(shù)據(jù):#、%{}、${}
            #和%{}需要放到struts2提供的標簽里才生效。如:<s:property value="#name"/>、<s:property value="%{'hello struts2'}"/>
            一、最常用的方式是:#
            1.#能取request、session、application里的attribute,但需要加前綴。如:<s:property value="#session.name2"/>、<s:property value="#application.name3"/>。如果是取request范圍的attribute,那么不需要加request前綴, 加上反而取不到數(shù)據(jù),ognl默認從request里取,如果沒有取到并不會到session或application里取。 如:<s:property value="#name"/>
            2.#能取request里的請求參數(shù),但必須加parameters前綴,且取到的是一個數(shù)組,所以如果你要得到參數(shù)的第一項值,那么還要加下標。 如:<s:property value="#parameters.name[0]"/>。這相當于調(diào)用 request.getParameterValues("name")[0];
            3.#加attr前綴能按request > session > application順序獲取attribute,這樣當在request中取不到時,會自動向session里取,如果session里也取不到,會 再向application里取。如果取到則返回,不再向上游歷。如:<s:property value="#attr.name"/>
            4.#能構(gòu)造Map,如:<s:set name="foobar" value="#{'foo1':'bar1', 'foo2':'bar2'}" /><s:property value="#foobar['foo1']" />
            5.#能用于過濾和投影(projecting)集合,如:books.{?#this.price<100}
            以上第4、5項功能,我沒有做過多介紹,因為目前為止這兩項功能我使用并不多。
            二、%{}的用途是在標簽的屬性為字符串類型時,計算OGNL表達式的值。這個功能目前還沒有深刻體會,故不介紹。
            三、${}有兩個主要的用途。
            1.用于在國際化資源文件中,引用OGNL表達式。
            2.在Struts 2配置文件中,引用OGNL表達式。如 :
            <action name="AddPhoto" class="addPhoto">
                  <interceptor-ref name="fileUploadStack" />           
                  <result type="redirect">ListPhotos.action?albumId=${albumId}</result>
              </action>

            以上,其實主要介紹了#的使用,大部分情況下我們只與它打交道,另外兩種方式需要在以后的項目中多多使用才能有所體會。
            其實,我是jstl+el的忠實粉絲,在任何項目中,只要能用上jstl標簽的,我決不用其它標簽。因為它是官方標準,還有它簡單且已熟練,我已在眾多項目中實戰(zhàn)演練過,有了它們,我不想在使用其它標簽。
            說到了這里,我還是有必要再多說兩句,是不是使用了struts2,就不能再用el來取數(shù)據(jù)了呢?答案是否定的,完全可以使用el來取數(shù)據(jù)。 struts2會將ValueStack里的session、application里的attribute完全復制到HttpSession、 ServletContext里,這樣el表達式照樣能取到這兩個Scope里的數(shù)據(jù)。然而,struts2并沒有將ValueStack里的 request里的attribute復制到HttpServletRequest,這是不是意味著el表達式就不能取request里的數(shù)據(jù)了呢?還是 可以,不只可以取request里的數(shù)據(jù),還可以取action實例的屬性值。神奇吧!奧秘就在struts2對request做了封裝,這個封裝類是 org.apache.struts2.dispatcher.StrutsRequestWrapper,它重寫了getAttribute()方法, 該方法先從真實的request類里取attribute,如果取到則返回,如果沒有取到則從ValueStack里取,現(xiàn)在明白了吧!

          posted @ 2013-01-18 13:20 楊軍威 閱讀(122) | 評論 (0)編輯 收藏

          各種數(shù)據(jù)庫默認端口

          一 :Oracle

              驅(qū)動:oracle.jdbc.driver.OracleDriver
              URL:jdbc:oracle:thin:@<machine_name><:port>:dbname
              注:machine_name:數(shù)據(jù)庫所在的機器的名稱,如果是本機則是127.0.0.1或者是localhost,如果是遠程連接,則是遠程的IP地址;    

               port:端口號,默認是1521


          二:SQL Server

              驅(qū)動:com.microsoft.jdbc.sqlserver.SQLServerDriver
              URL:jdbc:microsoft:sqlserver://<machine_name><:port>;DatabaseName=<dbname>
              注:machine_name:數(shù)據(jù)庫所在的機器的名稱,如果是本機則是127.0.0.1或者是localhost,如果是遠程連接,則是遠程的IP地址;          
               port:端口號,默認是1433

           

          三:MySQL

              驅(qū)動:org.gjt.mm.mysql.Driver
              URL:jdbc:mysql://<machine_name><:port>/dbname
              注:machine_name:數(shù)據(jù)庫所在的機器的名稱,如果是本機則是127.0.0.1或者是localhost,如果是遠程連接,則是遠程的IP地址;          
               port:端口號,默認3306
            

                  
           四:pointbase

              驅(qū)動:com.pointbase.jdbc.jdbcUniversalDriver
              URL:jdbc:pointbase:server://<machine_name><:port>/dbname
              注:machine_name:數(shù)據(jù)庫所在的機器的名稱,如果是本機則是127.0.0.1或者是localhost,如果是遠程連接,則是遠程的IP地址;
               port:端口號,默認是9092


           五:
          DB2

              驅(qū)動:com.ibm.db2.jdbc.app.DB2Driver
              URL:jdbc:db2://<machine_name><:port>/dbname
              注:machine_name:數(shù)據(jù)庫所在的機器的名稱,如果是本機則是127.0.0.1或者是localhost,如果是遠程連接,則是遠程的IP地址;
               port:端口號,默認是5000

          posted @ 2013-01-18 13:18 楊軍威 閱讀(7892) | 評論 (0)編輯 收藏

          js函數(shù)

          一、function概述

             javascript中的函數(shù)不同于其他的語言,每個函數(shù)都是作為一個對象被維護和運行的。通過函數(shù)對象的性質(zhì),可以很方便的將一個函數(shù)賦值給一個變量或者將函數(shù)作為參數(shù)傳遞。

             函數(shù)對象與其他用戶所定義的對象有著本質(zhì)的區(qū)別,這一類對象被稱之為內(nèi)部對象。內(nèi)置對象的構(gòu)造器是由JavaScript本身所定義的。

          二、function對象的創(chuàng)建

          在JavaScript中,函數(shù)對象對應的類型是Function,可以通過new Function()來創(chuàng)建一個函數(shù)對象,也可以通過function關(guān)鍵字來創(chuàng)建一個對象。

              //使用new Function()方式創(chuàng)建

              var myFunction=new Function("a","b","return a+b");

              //使用function關(guān)鍵字創(chuàng)建

              function myFunction(a,b) {

                return a + b;

              }

             用關(guān)鍵字創(chuàng)建對象的時候,在解釋器內(nèi)部,就會自動構(gòu)造一個Function對象,將函數(shù)作為一個內(nèi)部的對象來存儲和運行。從這里也可以看到,一個函數(shù)對象 名稱(函數(shù)變量)和一個普通變量名稱具有同樣的規(guī)范,都可以通過變量名來引用這個變量,但是函數(shù)變量名后面可以跟上括號和參數(shù)列表來進行函數(shù)調(diào)用。

              new Function()的語法規(guī)范如下:

              var funcName=new Function(p1,p2,...,pn,body);

            參數(shù)的類型都是字符串,p1到pn表示所創(chuàng)建函數(shù)的參數(shù)名稱列表,body表示所創(chuàng)建函數(shù)的函數(shù)體語句,funcName就是所創(chuàng)建函數(shù)的名稱。可以不指定任何參數(shù)創(chuàng)建一個空函數(shù),不指定funcName創(chuàng)建一個匿名函數(shù)。

            需要注意的是,p1到pn是參數(shù)名稱的列表,即p1不僅能代表一個參數(shù),它也可以是一個逗號隔開的參數(shù)列表,例如下面的定義是等價的:

            new Function("a", "b", "c", "return a+b+c")

            new Function("a, b, c", "return a+b+c")

            new Function("a,b", "c", "return a+b+c")

             函數(shù)的本質(zhì)是一個內(nèi)部對象,由JavaScript解釋器決定其運行方式。并且可直接在函數(shù)聲明后面加上括號就表示創(chuàng)建完成后立即進行函數(shù)調(diào)用,例如:

              var i=function (a,b){

              return a+b;

            }(1,2);

            alert(i);

             這段代碼會顯示變量i的值等于3。i是表示返回的值,而不是創(chuàng)建的函數(shù),因為括號“(”比等號“=”有更高的優(yōu)先級。

          三、匿名函數(shù)和有名函數(shù)的區(qū)別

             匿名函數(shù)必須先定義后調(diào)用,有名函數(shù)可以先調(diào)用,后定義。

          A)匿名(這段語句將產(chǎn)生func未定義的錯誤)

             <script>

                func();

                var func = function() {

                  alert(1);

                }

             < /script>

          B)有名(輸出1)

             <script>

                func();

                 function func() {

                  alert(1);

                }

             < /script>

          這是因為JS解釋器是分段分析執(zhí)行的。并且,在同一段中,有名函數(shù)會優(yōu)先被分析。并且同名函數(shù)后面的覆蓋前面的。

          而且,下面這段代碼也是合法的:

          <script>

          function myfunc ()
              {
                  alert("hello");
              };
              myfunc(); //這里調(diào)用myfunc,輸出yeah而不是hello
            
              function myfunc ()
              {
                  alert("yeah");
              };  
              myfunc(); //這里調(diào)用myfunc,輸出yeah

          </script>

          如果要讓上述代碼的第一次調(diào)用輸出“hello”,可以將它們分為兩段:

          <script>

          function myfunc ()
              {
                  alert("hello");
              };
              myfunc(); //這里調(diào)用myfunc,輸出hello
          < /script>

          <script>
              function myfunc ()
              {
                  alert("yeah");
              };  
              myfunc(); //這里調(diào)用myfunc,輸出yeah

          </script>

          下面的代碼輸出“hello”

          <script>

          function myfunc ()
              {
                  alert("hello");
              };
          < /script>

          <script>

          myfunc(); //輸出“hello”

          </script>

          <script>
              function myfunc ()
              {
                  alert("yeah");
              };

          </script>

          下面的代碼輸出“yeah”

          <script>

          function myfunc ()
              {
                  alert("hello");
              };
          < /script>

          <script>
              function myfunc ()
              {
                  alert("yeah");
              };

          </script>

          <script>

          myfunc(); //輸出“yeah”

          </script>

          從上面對段的位置變化導致輸出變化很清楚的解釋了JS解釋器分段分析執(zhí)行的本質(zhì)。

          posted @ 2013-01-18 13:11 楊軍威 閱讀(207) | 評論 (0)編輯 收藏

          java文件上傳下載

          JAVA的文件上傳遍一直是一個比較關(guān)注的問題,而且有幾個NB東西提供了這個功能.

          用的最多的算是三個(我就知道這三個)比較強的,一個是比較早的jspsmartupload,另一個是出身名族的commonupload,還有一個就是orellay的了.

          我用的比較多是前兩個,總的感覺是jspsmartuplod比較靈活,功能上更強一些(一點點吧),但是現(xiàn)在網(wǎng)上也不維護,也不能下載了,特別是 它上傳的時候把上傳文件放到內(nèi)存里,所以上傳文件的大小會和內(nèi)存有關(guān)系.commonupload雖然沒有提供很多API,但是它有比較靈活,它上傳的過 程中會把上傳的文件先寫入磁盤,所以上傳的大小只是帶寬有關(guān)系,我嘗試最大的上傳文件的大小是700M,當然是本地測試:>

          還有是就是在Linux/Unix系統(tǒng)上傳文件的中文問題,我在下面的代碼有了一些解決.

          下面是前兩種方式的上傳代碼:

          try{
          //取session 用戶oid
          int pid = userInfo.getUserId();
          String sys_user_id = String.valueOf(pid);
          //取init配置文件的參數(shù)值
          String sitePhysicalPath = (String)init.getObject("SitePhysicalPath");
          String saveDir = (String)init.getObject("InfoUploadDir");
          String tempDir = (String)init.getObject("InfoUploadDir");
          String fileMemo = ""; //文件說明
          String fileName = null; //存儲到數(shù)據(jù)庫的文件名
          String saveName = null; //存儲到本地的文件名
          String filePath = null; //存儲到數(shù)據(jù)庫的文件路徑
          String savePath = null; //存儲到本地的文件路徑
          long fileSize = 0; //文件大小
          int maxPostSize = -1;
          int dinfo_upload_id = -1;
          %>
          <%
          //初始化
          mySmartUpload.initialize(pageContext);
          //上載文件
          mySmartUpload.upload();
          //循環(huán)取得所有上載文件
          for(int i=0; i<mySmartUpload.getFiles().getCount(); i++)
          {
          //取得上載文件
          com.jspsmart.upload.File file = mySmartUpload.getFiles().getFile(i);
          if(!file.isMissing())
          {
          fileName = file.getFileName();
          //取得文件擴展名file.getFileExt()
          try{
          saveName = fileName.substring(fileName.lastIndexOf("."));

          }catch(Exception e){
          saveName = "";
          }
          //取得文件大小
          fileSize = file.getSize();
          //存儲路徑
          String sql_id = " SELECT S_INFO_UPLOAD.nextval as seqid FROM dual ";
          try{
          Statement stmt = con.createStatement();
          ResultSet rst = stmt.executeQuery(sql_id);
          while(rst.next())
          {
          dinfo_upload_id = rst.getInt("seqid");
          }
          }catch(SQLException sqle){
          return;
          }

          filePath = sitePhysicalPath + saveDir + Integer.toString(dinfo_upload_id) + saveName;
          savePath = saveDir + Integer.toString(dinfo_upload_id) + saveName;
          //存儲文件到本地
          file.saveAs(filePath);
          //存儲文件到數(shù)據(jù)庫
          switch(i)
          {
          case 0: fileMemo = (String)mySmartUpload.getRequest().getParameter("memo1"); break;
          case 1: fileMemo = (String)mySmartUpload.getRequest().getParameter("memo2"); break;
          case 2: fileMemo = (String)mySmartUpload.getRequest().getParameter("memo3"); break;
          case 3: fileMemo = (String)mySmartUpload.getRequest().getParameter("memo4"); break;
          case 4: fileMemo = (String)mySmartUpload.getRequest().getParameter("memo5"); break;
          default: fileMemo = "";
          }

          String sql = " INSERT INTO info_upload (info_upload_id,sys_user_id,file_size,file_path,utime,deleted) "
          + " VALUES( " + Integer.toString(dinfo_upload_id) + "," + sys_user_id + "," + fileSize + ",'" + savePath + "', SYSDATE , 0 )" ;
          sqlcmd cmd = new sqlcmd(con,sql);
          //System.out.println(sql);
          java.sql.PreparedStatement pstmt = null;
          java.sql.Statement stmt = null;
          //fileName = fileName.substring(0, fileName.indexOf("."));
          String sql_cn = " UPDATE info_upload SET file_name=?,file_memo=? WHERE info_upload_id=? ";
          java.io.ByteArrayInputStream bais_name = new java.io.ByteArrayInputStream(fileName.getBytes("ISO-8859-1"));
          java.io.InputStreamReader isr_name = new java.io.InputStreamReader((InputStream)bais_name,"GBK");

          java.io.ByteArrayInputStream bais_memo = new java.io.ByteArrayInputStream(fileMemo.getBytes("GBK"));
          java.io.InputStreamReader isr_memo = new java.io.InputStreamReader((InputStream)bais_memo,"GBK");

          try{
          stmt = con.createStatement();
          stmt.getConnection().setAutoCommit(false);

          pstmt = con.prepareStatement(sql_cn);
          pstmt.setCharacterStream(1, isr_name, fileName.length());
          pstmt.setCharacterStream(2, isr_memo, fileMemo.length());
          pstmt.setInt(3, dinfo_upload_id);

          //System.out.println(sql_cn);

          pstmt.execute();
          stmt.executeUpdate("COMMIT");

          }catch(Exception exce){
          System.out.println(exce);
          stmt.executeUpdate("ROLLBACK");
          }
          }
          }
          }catch(Exception e){
          }


          以上是jspsmart的方式,如果想要其它的方式,請下載全部源代碼.



          //upload_fileUpload.jsp

          <%@ include file = "../../backgeneral.jsp"%>
          <%@ contentType="text/html;charset=GBK" %>
          <jsp:useBean id="userInfo" scope="session" class="com.ges.hbgov.UserInfo"/>
          <%@ page import="org.apache.commons.fileupload.*" %>
          <%
          try{
           //request.setCharacterEncoding("GBK");
          //取session 用戶oid
              int pid = userInfo.getUserId();
              String sys_user_id = String.valueOf(pid);
          //取init配置文件的參數(shù)值
           String sitePhysicalPath = (String)init.getObject("SitePhysicalPath");
           String saveDir  = (String)init.getObject("InfoUploadDir");
           String tempDir  = (String)init.getObject("InfoUploadDir");
           String fileMemo = "";    //文件說明
           String fileName = null;  //存儲到數(shù)據(jù)庫的文件名
           String saveName = null;  //存儲到本地的文件名
           String filePath = null;  //存儲到本地的文件路徑
           String savePath = null;  //存儲到數(shù)據(jù)庫的文件路徑
           long   fileSize = 0;     //文件大小
           int maxPostSize = -1;   
           int dinfo_upload_id = -1;
          %>
          <%
              DiskFileUpload df = new DiskFileUpload();
              //設定上傳文件大小
           df.setSizeMax(maxPostSize);
           //設定臨時目錄
           df.setRepositoryPath(sitePhysicalPath + tempDir);
              //取得request信息
           List items = df.parseRequest(request);
             
           Iterator iter = items.iterator();
             
           int temp = 0;
           FileItem tempItem = null;

           while(iter.hasNext()){
            temp++;
            FileItem item = (FileItem)iter.next();
            if(item.isFormField())    //取得文件說明信息
            {
             fileMemo = item.getString("GBK");
             
            }
            else
            {   //取得上傳文件信息
             fileName = (String)item.getName();
             try{
              fileName = fileName.substring(fileName.lastIndexOf("//")+1);
              fileName = fileName.substring(fileName.lastIndexOf("/")+1);
             }catch(Exception e){
              System.out.println(e);
             }
             fileSize = item.getSize();
             tempItem = item;
            }

            if(temp == 2 && fileSize != 0)
             {    //每兩個iter存儲一個上傳文件

                       //得到info_title_id
                        String SQL_ID="select S_INFO_UPLOAD.nextval as seqid from dual";
                     try {
                          java.sql.Statement stmt = con.createStatement();
                          java.sql.ResultSet rst= stmt.executeQuery(SQL_ID);
                          while(rst.next())
                 {
                                 dinfo_upload_id = rst.getInt("seqid");
                          }

                       }catch(SQLException e1){
                              return;
                       }
                      //取得文件擴展名
                      try{
              saveName = fileName.substring(fileName.lastIndexOf("."));
             }catch(Exception exc){
              saveName = "";
             }

                      filePath = sitePhysicalPath + saveDir + Integer.toString(dinfo_upload_id) + saveName;
                      //存儲文件
             java.io.File uploadFile = new java.io.File(filePath);
             tempItem.write(uploadFile);
             /*try{
                 FileOutputStream fos = new FileOutputStream(filePath);
                 InputStream is = tempItem.getInputStream();
                 byte[] b = new byte[1024];
                 int nRead;
                 long per = 0;
                 double percent = 0;
                          while((nRead = is.read(b, 0, 1024))>0){
                  fos.write(b, 0, nRead);
                  per += nRead;
                  percent = (double)per/fileSize;

                  session.setAttribute("percent",Double.toString(percent).substring(2,4));
                  session.setAttribute("filename",fileName);
                          }
                 is.close();
              fos.close();    
              }catch(Exception e){
               System.out.println(e);
              }*/
                      savePath = saveDir + Integer.toString(dinfo_upload_id) + saveName;
                      /*/存儲數(shù)據(jù)庫
                      String sql = " INSERT INTO info_upload (info_upload_id,sys_user_id,file_name,file_memo,file_size,file_path,utime,deleted) "
                        + " VALUES( " + Integer.toString(dinfo_upload_id) + "," + sys_user_id + ",'" + fileName + "','" + fileMemo + "'," + fileSize + ",'" + savePath + "', SYSDATE , 0 )" ;
             sqlcmd cmd = new sqlcmd(con,sql);
             */
                      String sql = " INSERT INTO info_upload (info_upload_id,sys_user_id,file_size,file_path,utime,deleted) "
                        + " VALUES( " + Integer.toString(dinfo_upload_id) + "," + sys_user_id + "," + fileSize + ",'" + savePath + "', SYSDATE , 0 )" ;
             sqlcmd cmd = new sqlcmd(con,sql);
                      //System.out.println(sql);
             java.sql.PreparedStatement pstmt = null;
             java.sql.Statement stmt = null;
             //fileName = fileName.substring(0, fileName.indexOf("."));
             String sql_cn = " UPDATE info_upload SET file_name=?,file_memo=? WHERE info_upload_id=? ";
             
             java.io.ByteArrayInputStream bais_name = new java.io.ByteArrayInputStream(fileName.getBytes("ISO-8859-1"));
             java.io.InputStreamReader isr_name = new java.io.InputStreamReader((InputStream)bais_name,"GBK");

             java.io.ByteArrayInputStream bais_memo = new java.io.ByteArrayInputStream(fileMemo.getBytes("GBK"));
             java.io.InputStreamReader isr_memo = new java.io.InputStreamReader((InputStream)bais_memo,"GBK");
             
             try{
              stmt = con.createStatement();
              stmt.getConnection().setAutoCommit(false);

              pstmt = con.prepareStatement(sql_cn);
              pstmt.setCharacterStream(1, isr_name, fileName.length());
              pstmt.setCharacterStream(2, isr_memo, fileMemo.length());
              pstmt.setInt(3, dinfo_upload_id);

                          //System.out.println(sql_cn);

              pstmt.execute();
              stmt.executeUpdate("COMMIT");

             }catch(Exception exce){
              System.out.println(exce);
              stmt.executeUpdate("ROLLBACK");
             }

             temp = 0;
            }
            else if (temp == 2 && fileSize == 0) {temp = 0;}

           }
              //session.setAttribute("percent","ok");
          }catch(Exception ex){
           System.out.println(ex);
          }
          response.sendRedirect("list.jsp");

          %>




          //upload_jspSmart.jsp

          <%@ include file = "../../backgeneral.jsp"%>
          <%@ page language="java" import="java.util.*,java.sql.*,java.io.*"%>
          <%@ page language="java" import="com.jspsmart.upload.*"%>
          <%@ page language="java" import="com.ges.hbgov.*"%>
          <jsp:useBean id="userInfo" scope="session" class="com.ges.hbgov.UserInfo"/>
          <jsp:useBean id="mySmartUpload" scope="page" class="com.jspsmart.upload.SmartUpload" />
          <%
          //System.out.println("page=" + (String)session.getAttribute("SYS_USER_ID"));
          if(!userInfo.Request(request)){
          %>
          <script language=javascript>
           function relogin() {
            this.parent.location.href="../../login.jsp";
           }
           relogin();
          </script>
          <%
          }
          %>

          <%

          try{
          //取session 用戶oid
              int pid = userInfo.getUserId();
              String sys_user_id = String.valueOf(pid);
          //取init配置文件的參數(shù)值
           String sitePhysicalPath = (String)init.getObject("SitePhysicalPath");
           String saveDir  = (String)init.getObject("InfoUploadDir");
           String tempDir  = (String)init.getObject("InfoUploadDir");
           String fileMemo = "";    //文件說明
           String fileName = null;  //存儲到數(shù)據(jù)庫的文件名
           String saveName = null;  //存儲到本地的文件名
           String filePath = null;  //存儲到數(shù)據(jù)庫的文件路徑
           String savePath = null;  //存儲到本地的文件路徑
           long   fileSize = 0;     //文件大小
           int maxPostSize = -1;   
           int dinfo_upload_id = -1;
          %>
          <%
           //初始化
           mySmartUpload.initialize(pageContext);
           //上載文件
              mySmartUpload.upload();
           //循環(huán)取得所有上載文件
              for(int i=0; i<mySmartUpload.getFiles().getCount(); i++)
           {
            //取得上載文件
            com.jspsmart.upload.File file = mySmartUpload.getFiles().getFile(i);
            if(!file.isMissing())
            {
             fileName = file.getFileName();
             //取得文件擴展名file.getFileExt()
             try{
              saveName = fileName.substring(fileName.lastIndexOf("."));

             }catch(Exception e){
              saveName = "";
             }
             //取得文件大小
             fileSize = file.getSize();
             //存儲路徑
             String sql_id = " SELECT S_INFO_UPLOAD.nextval as seqid FROM dual ";
             try{
              Statement stmt = con.createStatement();
              ResultSet rst = stmt.executeQuery(sql_id);
              while(rst.next())
              {
               dinfo_upload_id = rst.getInt("seqid");
              }
             }catch(SQLException sqle){
              return;
             }

             filePath = sitePhysicalPath + saveDir + Integer.toString(dinfo_upload_id) + saveName;
             savePath = saveDir + Integer.toString(dinfo_upload_id) + saveName;
             //存儲文件到本地
             file.saveAs(filePath);
             //存儲文件到數(shù)據(jù)庫
             switch(i)
             {
              case 0: fileMemo = (String)mySmartUpload.getRequest().getParameter("memo1"); break;
              case 1: fileMemo = (String)mySmartUpload.getRequest().getParameter("memo2"); break;
                          case 2: fileMemo = (String)mySmartUpload.getRequest().getParameter("memo3"); break;
              case 3: fileMemo = (String)mySmartUpload.getRequest().getParameter("memo4"); break;
              case 4: fileMemo = (String)mySmartUpload.getRequest().getParameter("memo5"); break;
              default: fileMemo = "";
             }

                      String sql = " INSERT INTO info_upload (info_upload_id,sys_user_id,file_size,file_path,utime,deleted) "
                        + " VALUES( " + Integer.toString(dinfo_upload_id) + "," + sys_user_id + "," + fileSize + ",'" + savePath + "', SYSDATE , 0 )" ;
             sqlcmd cmd = new sqlcmd(con,sql);
                      //System.out.println(sql);
             java.sql.PreparedStatement pstmt = null;
             java.sql.Statement stmt = null;
             //fileName = fileName.substring(0, fileName.indexOf("."));
             String sql_cn = " UPDATE info_upload SET file_name=?,file_memo=? WHERE info_upload_id=? ";
             java.io.ByteArrayInputStream bais_name = new java.io.ByteArrayInputStream(fileName.getBytes("ISO-8859-1"));
             java.io.InputStreamReader isr_name = new java.io.InputStreamReader((InputStream)bais_name,"GBK");

             java.io.ByteArrayInputStream bais_memo = new java.io.ByteArrayInputStream(fileMemo.getBytes("GBK"));
             java.io.InputStreamReader isr_memo = new java.io.InputStreamReader((InputStream)bais_memo,"GBK");
             
             try{
              stmt = con.createStatement();
              stmt.getConnection().setAutoCommit(false);

              pstmt = con.prepareStatement(sql_cn);
              pstmt.setCharacterStream(1, isr_name, fileName.length());
              pstmt.setCharacterStream(2, isr_memo, fileMemo.length());
              pstmt.setInt(3, dinfo_upload_id);

                          //System.out.println(sql_cn);

              pstmt.execute();
              stmt.executeUpdate("COMMIT");

             }catch(Exception exce){
              System.out.println(exce);
              stmt.executeUpdate("ROLLBACK");
             }
            }
           }
          }catch(Exception e){
          }

          response.sendRedirect("list.jsp");
          %>

          posted @ 2013-01-17 14:31 楊軍威 閱讀(1826) | 評論 (0)編輯 收藏

          java處理xml方法

          最初,XML 語言僅僅是意圖用來作為 HTML 語言的替代品而出現(xiàn)的,但是隨著該語言的不斷發(fā)展和完善,人們越來越發(fā)現(xiàn)它所具有的優(yōu)點:例如標記語言可擴展,嚴格的語法規(guī)定,可使用有意義的標記,內(nèi)容 存儲和表現(xiàn)分離等等優(yōu)勢注定了該語言從誕生之日起就會走向輝煌。 XML 語言在成為 W3C 標準之后進入到了一個快速發(fā)展的時期,當然它本身所具有的一系列優(yōu)點和優(yōu)勢也注定了各大技術(shù)廠商對它的偏愛,Java 作為軟件行業(yè)的一種開發(fā)技術(shù)也迅速作出了反應,出現(xiàn)了多種對 XML 支持的工具,本文將會從這個角度對 Java 處理 XML 的幾種主流技術(shù)進行介紹,希望能對您有所幫助。在這篇文章中,您將會得到以下信息:
          1. Java 提供了哪些優(yōu)秀的類庫及工具便于程序員對 XML 進行處理 ?
          2. 有了 DOM 了,其它工具類庫還有必要么 ?
          3. 幾個小例程帶你快速了解這三種解析方式

            Java 有哪些優(yōu)秀的類庫及工具便于程序員對 XML 進行處理 ?

          • 大名鼎鼎的 DOM
          • 綠色環(huán)保的 SAX
          • 默默無聞的 Digester

            XML 三種解析方式簡介

            大名鼎鼎的 DOM

            說它大名鼎鼎可是一點不為過,DOM 是 W3C 處理 XML 的標準 API,它是許多其它與 XML 處理相關(guān)的標準的基礎,不僅是 Java,其它諸如 Javascript,PHP,MS .NET 等等語言都實現(xiàn)了該標準, 成為了應用最為廣泛的 XML 處理方式。當然,為了能提供更多更加強大的功能,Java 對于 DOM 直接擴展工具類有很多,比如很多 Java 程序員耳熟能詳?shù)? JDOM,DOM4J 等等, 它們基本上屬于對 DOM 接口功能的擴充,保留了很多 DOM API 的特性,許多原本的 DOM 程序員甚至都沒有任何障礙就熟練掌握了另外兩者的使用,直觀、易于操作的方式使它深受廣大 Java 程序員的喜愛。

            綠色環(huán)保的 SAX

            SAX 的應運而生有它特殊的需要,為什么說它綠色環(huán)保呢,這是因為 SAX 使用了最少的系統(tǒng)資源和最快速的解析方式對 XML 處理提供了支持。 但隨之而來繁瑣的查找方式也給廣大程序員帶來許多困擾,常常令人頭痛不已,同時它對 XPath 查詢功能的支持,令人們對它又愛又恨。

            默默無聞的 Digester:XML 的 JavaBean 化

            Digester 是 apache 基金組織下的一個開源項目,筆者對它的了解源于對 Struts 框架的研究,是否有很多程序員想要一解各大開源框架的設計甚至想要自己寫一個功能強大的框架時會碰到這樣一個難題: 這些形形色色的用 XML 語言標記的框架配置文件,框架底層是用什么技術(shù)來解析呢? DOM 解析耗費時間,SAX 解析又過于繁瑣,況且每次解析系統(tǒng)開銷也會過大, 于是,大家想到需要用與 XML 結(jié)構(gòu)相對應的 JavaBean 來裝載這些信息,由此 Digester 應運而生。它的出現(xiàn)為 XML 轉(zhuǎn)換為 JavaBean 對象的需求帶來了方便的操作接口,使得更多的類似需求得到了比較完美的解決方法, 不再需要程序員自己實現(xiàn)此類繁瑣的解析程序了。與此同時 SUN 也推出了 XML 和 JavaBean 轉(zhuǎn)換工具類 JAXB,有興趣的讀者可以自行了解。

            三種解析方式比較

            DOM

            優(yōu)缺點:實現(xiàn) W3C 標準,有多種編程語言支持這種解析方式,并且這種方法本身操作上簡單快捷,十分易于初學者掌握。其處理方式是將 XML 整個作為類似樹結(jié)構(gòu)的方式讀入內(nèi)存中以便操作及解析,因此支持應用程序?qū)?XML 數(shù)據(jù)的內(nèi)容和結(jié)構(gòu)進行修改,但是同時由于其需要在處理開始時將整個 XML 文件讀入到內(nèi)存中去進行分析,因此其在解析大數(shù)據(jù)量的 XML 文件時會遇到類似于內(nèi)存泄露以及程序崩潰的風險,請對這點多加注意。

            適用范圍:小型 XML 文件解析、需要全解析或者大部分解析 XML、需要修改 XML 樹內(nèi)容以生成自己的對象模型

            SAX

            SAX 從根本上解決了 DOM 在解析 XML 文檔時產(chǎn)生的占用大量資源的問題。其實現(xiàn)是通過類似于流解析的技術(shù),通讀整個 XML 文檔樹,通過事件處理器來響應程序員對于 XML 數(shù)據(jù)解析的需求。由于其不需要將整個 XML 文檔讀入內(nèi)存當中,它對系統(tǒng)資源的節(jié)省是十分顯而易見的,它在一些需要處理大型 XML 文檔以及性能要求較高的場合有起了十分重要的作用。支持 XPath 查詢的 SAX 使得開發(fā)人員更加靈活,處理起 XML 來更加的得心應手。但是同時,其仍然有一些不足之處也困擾廣大的開發(fā)人員:首先是它十分復雜的 API 接口令人望而生畏,其次由于其是屬于類似流解析的文件掃描方式,因此不支持應用程序?qū)τ?XML 樹內(nèi)容結(jié)構(gòu)等的修改,可能會有不便之處。

            適用范圍:大型 XML 文件解析、只需要部分解析或者只想取得部分 XML 樹內(nèi)容、有 XPath 查詢需求、有自己生成特定 XML 樹對象模型的需求

            Digester/JAXB

            優(yōu)缺點 : 由于其是在上述兩者的基礎上衍生出來的工具類,為的是滿足將 XML 轉(zhuǎn)換為 JavaBean 的特殊需求,故而沒有什么特別明顯的優(yōu)缺點。作為大名鼎鼎的開源框架 Struts 的 XML 解析工具 Digester,為我們帶來了將 XML 轉(zhuǎn)換為 JavaBean 的可靠方法。

            適用范圍 : 有將 XML 文檔直接轉(zhuǎn)換為 JavaBean 需求。

            應用示例

            下面給出一段用于解析的 XML 片段:
            清單 1. XML 片段

           <?xml version="1.0" encoding="UTF-8"?>   <books>     <book id="001">        <title>Harry Potter</title>        <author>J K. Rowling</author>     </book>     <book id="002">        <title>Learning XML</title>        <author>Erik T. Ray</author>     </book>   </books> 

            DOM 解析 XML

            Java 中的 DOM 接口簡介: JDK 中的 DOM API 遵循 W3C DOM 規(guī)范,其中 org.w3c.dom 包提供了 Document、DocumentType、Node、NodeList、Element 等接口,這些接口均是訪問 DOM 文檔所必須的。我們可以利用這些接口創(chuàng)建、遍歷、修改 DOM 文檔。

            javax.xml.parsers 包中的 DoumentBuilder 和 DocumentBuilderFactory 用于解析 XML 文檔生成對應的 DOM Document 對象。

            javax.xml.transform.dom 和 javax.xml.transform.stream 包中 DOMSource 類和 StreamSource 類,用于將更新后的 DOM 文檔寫入 XML 文件。

            下面給出一個運用 DOM 解析 XML 的例子:
            清單 2. DOM 解析 XML

           import java.io.File;   import java.io.IOException;   import javax.xml.parsers.DocumentBuilder;   import javax.xml.parsers.DocumentBuilderFactory;   import javax.xml.parsers.ParserConfigurationException;   import org.w3c.dom.Document;   import org.w3c.dom.Element;   import org.w3c.dom.Node;   import org.w3c.dom.NodeList;   import org.xml.sax.SAXException;   public class DOMParser {     DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();     //Load and parse XML file into DOM     public Document parse(String filePath) {        Document document = null;        try {           //DOM parser instance           DocumentBuilder builder = builderFactory.newDocumentBuilder();           //parse an XML file into a DOM tree           document = builder.parse(new File(filePath));        } catch (ParserConfigurationException e) {           e.printStackTrace();         } catch (SAXException e) {           e.printStackTrace();        } catch (IOException e) {           e.printStackTrace();        }        return document;     }     public static void main(String[] args) {           DOMParser parser = new DOMParser();           Document document = parser.parse("books.xml");           //get root element           Element rootElement = document.getDocumentElement();           //traverse child elements           NodeList nodes = rootElement.getChildNodes();           for (int i=0; i < nodes.getLength(); i++)           {              Node node = nodes.item(i);              if (node.getNodeType() == Node.ELEMENT_NODE) {                   Element child = (Element) node;                 //process child element              }           }           NodeList nodeList = rootElement.getElementsByTagName("book");           if(nodeList != null)           {              for (int i = 0 ; i < nodeList.getLength(); i++)              {                 Element element = (Element)nodeList.item(i);                 String id = element.getAttribute("id");              }           }     }   } 

            在上面的例子中,DOMParser 的 Parse() 方法負責解析 XML 文件并生成對應的 DOM Document 對象。其中 DocumentBuilderFactory 用于生成 DOM 文檔解析器以便解析 XML 文檔。 在獲取了 XML 文件對應的 Document 對象之后,我們可以調(diào)用一系列的 API 方便的對文檔對象模型中的元素進行訪問和處理。 需要注意的是調(diào)用 Element 對象的 getChildNodes() 方法時將返回其下所有的子節(jié)點,其中包括空白節(jié)點,因此需要在處理子 Element 之前對節(jié)點類型加以判斷。

            可以看出 DOM 解析 XML 易于開發(fā),只需要通過解析器建立起 XML 對應的 DOM 樹型結(jié)構(gòu)后便可以方便的使用 API 對節(jié)點進行訪問和處理,支持節(jié)點的刪除和修改等。 但是 DOM 解析 XML 文件時會將整個 XML 文件的內(nèi)容解析成樹型結(jié)構(gòu)存放在內(nèi)存中,因此不適合用 DOM 解析很大的 XML 文件。

            SAX 解析 XML

            與 DOM 建立樹形結(jié)構(gòu)的方式不同,SAX 采用事件模型來解析 XML 文檔,是解析 XML 文檔的一種更快速、更輕量的方法。 利用 SAX 可以對 XML 文檔進行有選擇的解析和訪問,而不必像 DOM 那樣加載整個文檔,因此它對內(nèi)存的要求較低。 但 SAX 對 XML 文檔的解析為一次性讀取,不創(chuàng)建任何文檔對象,很難同時訪問文檔中的多處數(shù)據(jù)。

            下面是一個 SAX 解析 XML 的例子:
            清單 3. SAX 解析 XML

           import org.xml.sax.Attributes;   import org.xml.sax.SAXException;   import org.xml.sax.XMLReader;   import org.xml.sax.helpers.DefaultHandler;   import org.xml.sax.helpers.XMLReaderFactory;   public class SAXParser {     class BookHandler extends DefaultHandler {        private List<String> nameList;        private boolean title = false;        public List<String> getNameList() {           return nameList;        }        // Called at start of an XML document        @Override        public void startDocument() throws SAXException {           System.out.println("Start parsing document...");           nameList = new ArrayList<String>();        }        // Called at end of an XML document        @Override        public void endDocument() throws SAXException {            System.out.println("End");         }        /**         * Start processing of an element.         * @param namespaceURI  Namespace URI         * @param localName  The local name, without prefix         * @param qName  The qualified name, with prefix         * @param atts  The attributes of the element         */        @Override        public void startElement(String uri, String localName, String qName,           Attributes atts) throws SAXException {           // Using qualified name because we are not using xmlns prefixes here.           if (qName.equals("title")) {              title = true;           }        }        @Override        public void endElement(String namespaceURI, String localName, String qName)           throws SAXException {           // End of processing current element           if (title) {              title = false;           }        }        @Override        public void characters(char[] ch, int start, int length) {           // Processing character data inside an element           if (title) {              String bookTitle = new String(ch, start, length);              System.out.println("Book title: " + bookTitle);              nameList.add(bookTitle);           }        }     }     public static void main(String[] args) throws SAXException, IOException {        XMLReader parser = XMLReaderFactory.createXMLReader();        BookHandler bookHandler = (new SAXParser()).new BookHandler();        parser.setContentHandler(bookHandler);        parser.parse("books.xml");        System.out.println(bookHandler.getNameList());     }   } 

            SAX 解析器接口和事件處理器接口定義在 org.xml.sax 包中。主要的接口包括 ContentHandler、DTDHandler、EntityResolver 及 ErrorHandler。 其中 ContentHandler 是主要的處理器接口,用于處理基本的文檔解析事件;DTDHandler 和 EntityResolver 接口用于處理與 DTD 驗證和實體解析相關(guān)的事件; ErrorHandler 是基本的錯誤處理接口。DefaultHandler 類實現(xiàn)了上述四個事件處理接口。上面的例子中 BookHandler 繼承了 DefaultHandler 類, 并覆蓋了其中的五個回調(diào)方法 startDocument()、endDocument()、startElement()、endElement() 及 characters() 以加入自己的事件處理邏輯。

            Digester 解析 XML

            為了滿足將 XML 轉(zhuǎn)換為 JavaBean 的特殊需求,Apache 旗下的一個名為 Digester 的工具為我們提供了這么一個選擇。由于最終是將 XML 轉(zhuǎn)化為 JavaBean 存儲在內(nèi)存當中, 故而解析性能等方面其實與使用者并沒有多大關(guān)系。解析的關(guān)鍵在于用以匹配 XML 的模式以及規(guī)則等,由于該工具較為復雜,限于篇幅,作者只能給予簡單的介紹。

            下面是一個 Digester 解析 XML 的例子片段:
            清單 4. Digester 解析 XML

           // 定義要解析的 XML 的路徑,并初始化工具類  File input = new File("books.xml");   Digester digester = new Digester();   // 如果碰到了 <books> 這個標簽,應該初始化 test.myBean.Books 這個 JavaBean 并填裝相關(guān)內(nèi)容  digester.addObjectCreate("books", "test.myBean.Books");   digester.addSetProperties("books");   // 如果碰到了 <books/book> 這個標簽,同上初始化 test.myBean.Book 這個 JavaBean   digester.addObjectCreate("books/book", "test.myBean.Book");   digester.addSetProperties("books/book");   // 通過調(diào)用上面已經(jīng)初始化過的 JavaBean 的 addBook() 方法來把多個 <books/book> 加到一個集合中  digester.addSetNext("books/book", "addBook", "test.myBean.Book");   // 定義好了上面的解析規(guī)則后,就可以開始進行解析工作了  Books books = (Books) digester.parse(input); 

            上述代碼簡單的向讀者展示了 Digester 處理 XML 的一些要點,主要是說明了一些模式以及規(guī)則的匹配。 簡言之,Digester 就是一種用來把一個 XML 轉(zhuǎn)化為一個與該 XML 結(jié)構(gòu)類似的 JavaBean。你可以把 XML 根元素想象成一個 JavaBean, 該根元素的 attribute 就是這個 JavaBean 的各種 Field,當該根元素有其他子 tag 時,又要把這個子 tag 想象成一個個新的 XML,將其視為一個新的 JavaBean, 并作為一個 Field 加入到父 Bean 當中,然后以此類推,通過循環(huán)的方式將整個 XML 進行解析。

            結(jié)束語

            本文介紹了 Java 解析 XML 的三種常用技術(shù),其中 DOM 易于上手,程序易于理解,但缺點在于占用內(nèi)存大,不適合于解析較大的 XML 文件; SAX 基于事件模型占用系統(tǒng)資源少,能夠勝任較大的 XML 文件解析,但解析過程較為繁瑣查找元素不方便; Digester/JAXB 基于上述兩種技術(shù)衍生而來。文中的實例向讀者展示了三種 API 的基本使用方法, 在實際開發(fā)過程中使用那種技術(shù)解析 XML 更好要依據(jù)各自的優(yōu)缺點視具體情況而定。

          posted @ 2013-01-16 14:09 楊軍威 閱讀(598) | 評論 (0)編輯 收藏

          前段開發(fā)10點

           第一日:初嘗禁果

            【上帝說:“要有光!”便有了光】

            萬物生靈、陽光雨露蓋源于造物之初的天工開物,我們無法想象上帝創(chuàng)造光明之前的世界模樣。但幸運的是,前端開發(fā)沒有神祗般的詭魅。這個技術(shù)工種 的孕育、定型、發(fā)展自有軌跡,也頗有淵源,當然,這非常容易理解。不嚴格的講,在楊致遠和費羅在斯坦福大學的機房里攛掇出Yahoo!時,Web前端技術(shù) 就已經(jīng)開始進入公眾視野,只不過當時沒有一個響亮的名字。從那時起,“基于瀏覽器端的開發(fā)”就成了軟件開發(fā)的新的分支,這也是Web前端技術(shù)的核心,即不 論何時何地何種系統(tǒng)以及怎樣的設備,但凡基于瀏覽器,都是Web前端開發(fā)的范疇(當然,這個定義很狹隘,下文會提到)。

            在2000年之后瀏覽器技術(shù)漸漸成熟,Web產(chǎn)品也越來越豐富,中國有大批年輕人開始接觸互聯(lián)網(wǎng),有一點需要注意,大部分人接觸互聯(lián)網(wǎng)不是始于 對瀏覽器功能的好奇,而是被瀏覽器窗口內(nèi)的豐富內(nèi)容所吸引,我們的思維模式從一開始就被限制在一個小窗口之內(nèi),以至于很長時間內(nèi)我們將“視覺”認為是一種 “功能”,Web產(chǎn)品無非是用來展現(xiàn)信息之用。起初的入行者無一例外對“視覺”的關(guān)注超過了對“內(nèi)容”的重視,先讓頁面看起來漂亮,去關(guān)注 html/css,沿著“視覺呈現(xiàn)”的思路,繼續(xù)深入下去。因此,這類人是被“視覺”所吸引,從切頁面入行,著迷于結(jié)構(gòu)化的html和書寫工整的css, 喜歡簡潔優(yōu)雅的UI和工整的頁面設計,之后開始接觸視覺特效,并使用jQuery來實現(xiàn)視覺特效,以此為線索,開始深入研究Dom、Bom和瀏覽器的渲染 機制等,html/css在這些人手中就像進攻兵器,而JavaScript則更如防守的盾牌。

            還有另外一群人從另一條道路接觸Web前端,即工程師轉(zhuǎn)行做前端,他們有較多的后臺語言開發(fā)背景,從讀寫數(shù)據(jù)開始,漸漸觸及瀏覽器端,接觸 JavaScript庫,起初是在html代碼上加js邏輯,后來開始涉及html和css,他們喜歡OO、邏輯清晰、結(jié)構(gòu)悅目的代碼,更關(guān)注界面背后的 “程序語言”和數(shù)據(jù)邏輯。html/css在這些人手中則更像盾牌,而JavaScript更如進攻的兵器。

            應當說這兩類人是互補的,他們各自了解瀏覽器本質(zhì)的一部分,一撥人對渲染引擎了如指掌,另一撥人則將JS引擎奉為至寶,其實任何一部分的優(yōu)勢發(fā) 揮出來都能做出精品。大部分前端工程師都能從這兩條淵源中找到自己的影子。但,這兩類人的思維模式和觀點是如此不同,以至于形成了一些不必要的對抗,比如 在某些公司,干脆將Web前端技術(shù)一分為二,“切頁面的”和“寫js的”。這樣做看上去明確了分工提高了效率,但他對員工的職業(yè)發(fā)展帶來巨大傷害。在第二 日“科班秀才”中會有進一步討論。

            我應該屬于第二類,即在學校正兒八經(jīng)的學習C/Java和C#之類,以為大學畢業(yè)后能去做ERP軟件、桌面軟件或者進某些通信公司寫TCP /IP相關(guān)的程序。校園招聘時選擇了中國雅虎,因為當年(08年)雅虎還是有一點兒名氣,而且我聽說雅虎比較算技術(shù)流的公司……自此就上了賊船,一發(fā)不可 收拾。

            在雅虎的這段時間,我有幸接觸到一股正氣凜然的技術(shù)流派,也形成了我對前端技術(shù)的一些基本看法,這些基本觀點一直影響我至今。

            【優(yōu)雅的學院派】

            當年雅虎的技術(shù)流派正如日中天,擁有眾多“之父”級的高人,所營造出的Hack氛圍實在讓人陶醉的無法自拔,那段時間我甚至寧愿加班到深夜閱讀 海量的文檔和源代碼,感覺真的很舒服,我深深的被雅虎工程師這種低調(diào)務實、精工細琢的“服務精神”所打動,而這種不起眼的優(yōu)秀品質(zhì)很大程度的影響雅虎產(chǎn)品 的用戶體驗和高質(zhì)量的技術(shù)輸出。那么,何謂“服務精神”?即你所做的東西是服務于人的,要么是產(chǎn)品客戶、要么是接手你項目的人、要么是使用你開發(fā)的功能的 人,所以技術(shù)文檔成為伴隨代碼的標配。因此,工程師之間通過代碼就能做到心有靈犀的溝通。這是工程師的一項基本素質(zhì),即,思路清晰的完成項目,且配備了有 價值的技術(shù)文檔,如果你的程序是給其他程序員用的,則更要如此,就好比你制造一款家電都要配備說明書一樣。因此,YDN成了當時最受全球程序員最喜愛的技 術(shù)文檔庫,這種優(yōu)雅務實的“學院氣息”讓人感覺獨具魅力。

            讓人感覺奇怪的是,在中文社區(qū)始終未見這種學院派。甚至在具有先天開源優(yōu)勢的Web前端技術(shù)社區(qū)里也是波瀾不驚,可見寫一篇好的技術(shù)文案真的比 登天還難。我所見到的大部分所謂文檔索性把代碼里輸出數(shù)據(jù)的語句塊拷貝粘貼出來,至于為什么數(shù)據(jù)格式要設計成這樣、如果字段有修改怎么做、編碼解碼要求如 何等等關(guān)鍵信息只字不提,或者開發(fā)者也沒想過這些問題呢。因此,我們一直在強調(diào)代碼的質(zhì)量和可維護性,但一直以來都未見效,蓋源于缺少這種“服務”意識的 灌輸。這種意識在下文中還會多次提到,因為它能影響你做事的每個細節(jié),是最應當首先突破的思想糾結(jié)。

            除了意識問題,另一方面是技術(shù)問題,即文筆。這也是工程師最瞧不上眼的問題,難以置信這竟然是阻礙工程師突破瓶頸的關(guān)鍵所在。我已看到過數(shù)不清 的人在晉升這道關(guān)卡吃了大虧,很多工程師技術(shù)實力很強,但就是表達不出來,要么羅列一大堆信息毫無重點、要么毫無趣味的講代碼細節(jié),不知云云。除非你走狗 屎運碰到一個懂技術(shù)的老板,否則真的沒辦法逃脫碼農(nóng)的宿命。但大部分人還振振有詞不以為然。而在Web前端開發(fā)領(lǐng)域情況更甚。前端工程師是最喜歡搞重構(gòu) 的,但在快節(jié)奏的需求面前,你很難用“提高了可維護性”、“提升了性能”這類虛無縹緲的詞藻為自己爭取到時間來搞重構(gòu),說的露骨一點,可能你真的對某次重 構(gòu)帶來的實際價值無法量化,只是“感覺代碼更整潔了”而已。我會在下文的“偽架構(gòu)”中會展開分析前端工程師的這種浮躁獻媚的技術(shù)情結(jié)。而這正是前端工程師 最欠缺的素質(zhì)之一:用數(shù)據(jù)說話,用嚴謹科學的論據(jù)來支撐你的觀點,老板不傻,有價值的東西當然會讓你去做。

            當然,情況不總是這么糟糕,我們看到中文社區(qū)中已經(jīng)鍛煉出了很多寫手,他們在用高質(zhì)量的文字推銷自己的技術(shù)理念,這是一個好兆頭,好的文筆是可 以鍛煉出來的。而在職場,特別是對前端工程師這個特殊職位來講,這種基本技能可以幫你反思梳理需求的輕重緩急,從凌亂的需求中把握七寸所在。因為當你開始 認真寫一封郵件的時候,這種思考已經(jīng)包含其中了。

            所以,雅虎技術(shù)的推銷是相對成功和遠播的。關(guān)鍵在于兩方面,扎實的技術(shù)功底和高超的寫手。而真正的技術(shù)大牛一定是集兩者與一身,不僅鉆研劍道,還能產(chǎn)出秘籍。這也是Yahoo!優(yōu)雅的學院派氣息的動力源泉。國內(nèi)很多技術(shù)團體想在這方面有所建樹,應當首先想清楚這一點。

            【規(guī)范的破與立 1】

            雅虎的技術(shù)運作非常規(guī)范,剛才已經(jīng)提到,包括技術(shù)、組織、文化,一切看起來有模有樣,也堪稱標桿,自然成了國內(nèi)很多技術(shù)團隊和社區(qū)的效仿對象。一時間各種“規(guī)范“成風、各色“標準“大行其道,結(jié)果是質(zhì)量參差不齊。

            我們到底需要什么樣的規(guī)范?雅虎的技術(shù)規(guī)范到底有何種魔力?以何種思路構(gòu)建的規(guī)范才是貨真價實的?規(guī)范有著怎樣的生命周期?想清楚這些問題,能很大程度減輕很多Web前端工程師的思想負擔,看清一部分技術(shù)本質(zhì),避免盲目跟風。

            我們的確需要規(guī)范,但好的規(guī)范一定是務實的,一定是“解決問題“的。比如針對項目構(gòu)建的DPL可以收納公用的視覺元件以減少重復開發(fā)、規(guī)定某 OPOA項目的事件分發(fā)原則以確立增量開發(fā)的代碼慣性。反之,糟糕的規(guī)范卻顯得過于“抽象“,比如頁面性能指標、響應式設計原則。另外,盡管他山之石可以 攻玉,但拿來主義有一個大前提,就是你了解你的項目的關(guān)鍵問題,你要優(yōu)先解決的是些關(guān)鍵問題,而外來規(guī)范正好能解決你的問題。因此規(guī)范是一本案頭手冊,是 一攬子問題的解決方案,應當是“字典”,而不是“教程“。可見規(guī)范的源頭是“問題”。所以,當你想用CoffeeScript重構(gòu)你的項目時、當你想引入 CommonJS規(guī)范時、當你想在頁面中揉進Bootstrap時、當你打算重復造輪子搞一套JS庫時、當你想重寫一套assets打包工具時,想想這些 東東解決了你的什么問題?會不會帶來新的問題、把事情搞復雜了?還是為了嘗鮮?或者為了在簡歷中堂而皇之的寫上使用并精通各種新技術(shù)?

            規(guī)范之立應當有動因,動因來源于項目需求,項目需求則來自對產(chǎn)品的理解和把握,這是Web前端初級工程師走向中級甚至高級的一次重要蛻變,軟件 工程領(lǐng)域早就有“架構(gòu)師”角色,而架構(gòu)師往往存在于項目需求分析和概設、詳設階段。我看到的情況是,Web前端工程師的思維過多的限制在“界面”之內(nèi),向 前和產(chǎn)品需求離的太遠(認為這是視覺設計師的事)、向后和數(shù)據(jù)邏輯又隔離開來(認為這是后臺工程師該干的事),因此前端規(guī)范也大都泛泛,無關(guān)項目痛癢,成 了玩具。

            雅虎技術(shù)規(guī)范的優(yōu)秀之初在于它們解決問題。所以,學習使用規(guī)范應當多問一句,“他們?yōu)槭裁催@樣做?”其實,想清楚這些問題時,腦海中自然形成了一種“遇山開山”的創(chuàng)造性思維。

            【規(guī)范的破與立 2】

            如果說新技術(shù)的嘗鮮缺少針對性,但至少滿足程序員的某種潔癖和快感,那么“負擔”從何而來呢?對于初學者來說,有價值學習資料可能只有這些規(guī)范,如果說規(guī)范價值不大,那又當從何入手呢?

            剛才我說的不是依賴于規(guī)范,而是對規(guī)范的反思,擺脫規(guī)范灌輸給我們的思維定勢。新人們大概是看了Wiki中的很多指標、結(jié)論、實踐,在做項目之 初就附加了不少“八股式”的負擔,甚至影響我們對項目關(guān)鍵需求和關(guān)鍵問題的洞察力和判斷力,負擔過重就無法輕裝上陣,Wiki中提到的這些指標和規(guī)范是結(jié) 論性的,是大量的實踐之后得出的,也只有經(jīng)歷過大量實踐才會真正理解這些結(jié)論,比如DomReady時間和http請求數(shù)是否有因果關(guān)系,http請求數(shù) 增加是否真的會導致頁面性能下降,什么條件下會導致性能下降?我們從那些條文和結(jié)論中無法找到答案。

            舉個具體的例子,Kissy剛剛出了DPL,也是一大堆結(jié)論,比如他的布局就采用了經(jīng)典的雙飛翼,使用容器浮動來實現(xiàn),那么,這種做法就是不可 撼動的“標準”嗎?看看淘寶車險首頁,布局容器齊刷刷的inline-block,只要頂層容器去掉寬度,布局容器自身就能根據(jù)瀏覽器寬度調(diào)整自然水平/ 垂直排列,輕易的適應終端寬度了。

            再比如,淘寶旅行計劃項目中的部署方式,也沒有完全使用Loader管理依賴,而是將依賴層級做的很少,業(yè)務邏輯使用腳本來合并,這樣就可以更容易在build環(huán)節(jié)加入語法檢查和代碼風格檢查。

            類似這種擺脫原有編程思維,有針對性的用新思路新方法解決問題的做法顯然讓人感覺更加清爽,編程的樂趣也正體現(xiàn)在打破常規(guī)的快感之中,小馬曾經(jīng) 說過:“制造規(guī)范是為了打破規(guī)范”,萬不要因為這些規(guī)范標準加重負擔,導致開始做一個簡單頁面時也顯得縮手縮腳,無法放開身手。大膽的動手實踐,才能真正 得出屬于自己的“結(jié)論 “和“標準“,才會真正深刻理解那些“結(jié)論”的意義所在。代碼寫的多了,自然熟能生巧,也容易形成成熟的技術(shù)觀點。

            在這個過程中,我們唯一的對手是懶惰,惰于思考,就無法真正發(fā)現(xiàn)問題,自然形不成自己的觀點。還是那句話,任何規(guī)范、方法、結(jié)論、實踐都是為了 解決項目中的問題的,所以,我們所接觸到那些看似“八股文”式的規(guī)范標準也是為了解決某些問題而提出的,想清楚這些問題,理解方法論背后的“因“,內(nèi)心自 然有“果”。

            因此,“著眼當下、對癥下藥”的品質(zhì)就顯得彌足珍貴了,比如,雙飛翼布局方法是為了解決一套(html)代碼適應多種布局設計,這里的布局相對 于固定的產(chǎn)品來說也是固定的,而無針對終端的自適應(適用于移動端的榻榻米布局似乎還沒有最佳實踐)。這是雙飛翼產(chǎn)生的背景,如今終端環(huán)境較之5年前已經(jīng) 翻天覆地,問題早已不在“多種布局”上,而在“終端適應“上,這才是我們面臨的問題,需要我們給出新的技術(shù)方案。

            所以,勤于思考,輕裝上陣,大膽實踐,勇于創(chuàng)新,發(fā)掘問題所在,實打?qū)嵉慕鉀Q(潛在)問題,這才是我們真正需要的能力。放下思維定勢枷鎖,也會有一種豁然開朗的感覺。

            第二日:科班秀才

            【秀才仕途】

            Web前端工程師是一個特別的崗位,只存在于互聯(lián)網(wǎng)領(lǐng)域。最近幾年隨著互聯(lián)網(wǎng)產(chǎn)業(yè)的火爆,對前端工程師的需求量暴增,兵源幾近枯竭。各大公司技術(shù)掌門一定都有過類似的苦惱:“招一個靠譜的前端工程師、難于上青天”。

            我想,一部分原因是,當前不少入道的前端工程師大都是轉(zhuǎn)行而來,畢竟,正兒八經(jīng)的學校里也不會教這玩意,覺得“切頁面”有啥好教的,甚至不覺得 html/css是一門語言。轉(zhuǎn)行這事自不必詳說,大家也各自瞄準當前市場需求,造成的現(xiàn)象是,初級前端工程師堆成山,中高級人才卻一將難求,計算機系的 科班出身就更加鳳毛麟角了。一方面反映了教育部門的后知后覺,另一方面也體現(xiàn)了大部分人急功近利的跟風。當然最重要的原因是,所謂中國“第一代前端工程 師”并未做好布道的工作。導致大家對于基礎和潛力的態(tài)度從之前的忽視演變?yōu)槿缃竦拿镆暋K^基礎,就是在大學上的那些計算機基礎課。所謂潛力,就是戒驕戒 躁的務實作風。這些會在后文中多次提到。

            對于科班出身的莘莘學苗來說,根正苗紅本身就是一種優(yōu)勢,事實證明,這些人在前端技術(shù)上的成長軌跡有一定的套路,而且大都能如期的突破技能瓶頸。從一個人大學畢業(yè)到他最滿意的工作狀態(tài),中間會經(jīng)過幾個階段。

            前2年是學習技能的階段,這個階段主要精力放在專業(yè)技能的提升上,2年內(nèi)起碼要趕上平均水平,即所謂“中級“,在這個階段的人通常對軟技能不怎 么關(guān)注,溝通能力達不到平均水平,基本上是來啥活干啥活,干不完就加班的這種,對需求的合理性不甚理解,對項目也沒什么把控,盡管在技能上有提高的空間, 也不是公司最需要的人,但有不少成長空間。

            工作2-3年的人在前端技能上趨于穩(wěn)定,也就是技能上的第一次瓶頸,這種人干活熟練,切頁面可能也很快,代碼看上去也比較規(guī)范,屬于熟練工,開 始注重溝通技巧和一些職業(yè)技能的積累,比如帶人帶項目,至少有這方面的意識,并有過推動項目、和業(yè)務方pk需求的經(jīng)歷,這就達到了中級應當具備的職業(yè)技 能,但應當注意的是,這時最容易出現(xiàn)偏科的情況,特別是對于那些“專門切頁面的“和“專門寫腳本的“人,畢竟html/css/js三者不分彼此,三者是 一個合格前端工程師都必須要掌握的。如果你覺察到自身有偏廢的嫌疑,則要小心了,要清楚的了解自身的差距,并意識到瓶頸的存在,為過渡到“中級“的打下基 礎。

            過了這道坎之后,工作3年以上的人大部分技能也趨穩(wěn),有些人對前端新技術(shù)有鉆研,能夠熟練應對日常工作,軟技能也ok,具備有針對性的“拿來主 義“,代碼也具有一定的架構(gòu)性,開始突破“代碼民工”的這一層瓶頸,對團隊氣氛、培訓、工作環(huán)境有個性化的要求,一般來講,這種人是典型的具有潛力的“中 級”工程師,但很快會遇到職業(yè)發(fā)展中的第二個技術(shù)瓶頸。

            有少數(shù)工作3年或4年以上,在不斷尋求新的技能上的突破,最明顯的一點體現(xiàn)是,開始關(guān)注“底層協(xié)議”,即HTTP、第三方應用、系統(tǒng)對接、制造 工具、工作流程等,這時思考的重點已經(jīng)脫離了“切頁面”,變?yōu)?#8220;出方案“,比如要架設一個站點,能夠搭建站點框架,預見站點后續(xù)(前端)開發(fā)中的所有風 險,并一一給出解決方案。項目后續(xù)開發(fā)遇到問題只要翻閱你提供的“手冊”即能找到答案。這種人是標準的“高級”Web前端工程師。

            出方案是一件挺難的事情,它要求一個工程師同時具備經(jīng)驗、技術(shù)、氣場等諸多硬技能。尤其是對技術(shù)底子的要求非常高。

            【半路出家】

            那么,轉(zhuǎn)行做前端的人又當如何呢?其實發(fā)展軌跡和科班秀才們非常類似,只是時間跨度可能會長一些,你要花更多的精力、做更多的項目、更多的反思和總結(jié)才能理解某個知識點的本質(zhì)(比如HTTP協(xié)議)。當然這只是一般情況。

            此外,這些人還需要擺脫很多思維定勢的禁錮。這里我推薦大家閱讀阿當?shù)摹禬eb前端開發(fā)修煉之道》。當然,如果你有一個靠譜的師兄帶你入道,自然幸運萬倍。

            但不管怎樣,我始終認為應當秉承興趣第一的原則,不管你是誤打誤撞、還是意欲為之,不管你是科班秀才、還是半路出家,興趣始終應當是第一原則, 然后才是你“想做好“。我對自己的要求無法強加于人,所以很多業(yè)界大牛在回顧自己成功之路時,提到最多的是:“熱愛你的工作、擁抱它給你帶來的挑戰(zhàn)”。 N.C.Zakas曾經(jīng)這樣勉勵大家:

            “我對Web開發(fā)人員最大的建議就是:熱愛你的工作。熱愛跨瀏覽器開發(fā)帶來的挑戰(zhàn)、熱愛互聯(lián)網(wǎng)技術(shù)的種種異端,熱愛業(yè)內(nèi)的同行,熱愛你的 工 具。互聯(lián)網(wǎng)發(fā)展太快了,如果你不熱愛它的話,不可能跟上它的步伐。這意味著你必須多閱讀,多動手,保證自己的才能與日俱增。下了班也不能閑著,要做一 些對自己有用的 事兒。可以參與一些開源軟件的開發(fā),讀讀好書,看看牛人的博客。經(jīng)常參加一些會議,看看別人都在干什么。要想讓自己快速成長,有很多事兒 可以去做,而且付出一定會有回報。“

            第三日,幸福感

            【先精通十行?!】

            興趣第一,聽上去很美,但現(xiàn)實卻不總是這么酷。練就了一身本領(lǐng),那也要找到對口的怪物來打一打才過癮。

            自然,每個人都想做出好東西,每個工程師也都渴求這樣的機遇,用層次分明的設計、漂亮優(yōu)雅的代碼、精妙的細節(jié)雕琢,做出美觀、安全、實用耐用的 產(chǎn)品,不過現(xiàn)實是如此殘酷,以至于工程師們一直都缺乏對產(chǎn)品的歸屬感。作為前端工程師,如何才能在江湖中把握住前進方向、步步走高?畢竟,在職位繁雜的大 公司,缺乏人性化的工作流程影響著工程師的工作幸福感。產(chǎn)品從設計之初、到技術(shù)方案評審、再到實現(xiàn),處處充滿了妥協(xié),大部分產(chǎn)品都是雜交的產(chǎn)物,人與人相 互掣肘,每個人都對產(chǎn)品不滿意……,大躍進式的敏捷開發(fā)早就被證明百害無一利。但,或許這就是成長的代價。年輕的工程師需要更多的了解需求和設計、產(chǎn)品經(jīng) 理更要懂得軟件迭代規(guī)律。對于前端工程師來講更是如此,多學習交互設計和UI,多了解網(wǎng)絡協(xié)議和軟件迭代模型,更能幫助前端工程師和需求方溝通、和后臺的 銜接、以及控制版本的迭代。

            說來奇怪,前端工程師不是寫html/css/js的嗎,搞懂那些邊緣知識有什么用?《Web前端開發(fā)修煉之道》中也提到,精通一行需要先精通十行。這里我來解釋一下原因。

            作為交互設計師的下游,前端工程師學需要習設計知識是很容易理解的,因為它能幫助你更準確的理解設計師的意圖,在原型不完整的時候也能正確的反 饋設計缺陷,將問題阻擋在設計的環(huán)節(jié),會大大減少UI bug數(shù)量,比如說,設計師會給出理想狀態(tài)下的容器樣式,卻往往忽略了文字溢出折行、長連續(xù)字符、 容器寬高是否適應內(nèi)容尺寸變化而變化,溢出部分是作截字還是隱藏等諸多細節(jié),因為設計師不懂“邊界值測試”的道理,而這些問題往往在測試階段才被發(fā)現(xiàn),所 以,如果能在拿到UI設計稿時就提醒設計師補充完整這些場景,自然減少測試回歸次數(shù)。

            另外,前端工程師必須要了解網(wǎng)絡協(xié)議,原因很簡單,我們做的產(chǎn)品運行在Web上。很多依賴于Ajax的實現(xiàn),只有前端工程師才會提出實現(xiàn)方案, 產(chǎn)品經(jīng)理不了解技術(shù)瓶頸,后臺工程師更不會在意客戶端的用戶體驗,舉個簡單的例子:通過JS實現(xiàn)一個Ajax,如果Ajax抓取的數(shù)據(jù)源是一個302跳 轉(zhuǎn),則需要在JS程序中多做一些事情,這就需要前端工程師了解一些HTTP協(xié)議。應當說,這是很常見的一個場景。

            那么,為什么說前端工程師也要關(guān)注代碼版本控制呢?因為web開發(fā)和軟件開發(fā)本質(zhì)無異,同樣具有迭代周期,需求不是一攬子提完、一口氣開發(fā)完 的,是有步驟的開發(fā),因此,每次上線開發(fā)哪些功能、為后續(xù)擴展功能留足哪些接口、代碼在可擴展和可維護性上應當作哪些考慮……,這些應當是每個工程師關(guān)注 的事情,所謂迭代就是指這種需求的疊加,這是軟件開發(fā)的常態(tài),也是web開發(fā)的常態(tài),剛開始,前端工程師總會不斷抱怨沒完沒了的需求,代碼起初還算干凈, 但很快就越來越亂,代碼的版本管理對于Web前端工程師來說有些困難,這也使得大部分前端工程師很難上檔次,從這個角度講,前端工程師是需要向后臺工程師 學習的,他們的開發(fā)量不比前端少,維護代碼的能力要超過前端工程師。另外,對于剛?cè)胄械那岸斯こ處煟膽B(tài)要放對,提需求是產(chǎn)品經(jīng)理的職責所在,整理出有價 值的需求是交互設計師的職責所在,將需求作版本控制分步實現(xiàn)是前端工程師的職責所在,前端工程師沒必要去抱怨產(chǎn)品經(jīng)理提一大堆沒規(guī)律的需求,而更應當去理 解需求緣由,將需求提煉成UC(用例),讓需求在自己手中可控制。只是多數(shù)前端工程師缺乏提煉、整理需求的能力,一味的在接需求,才會搞的手忙腳亂,帶著 情緒堆代碼。

            所以,只有練就了一身本領(lǐng),才會更有目標的去尋找對產(chǎn)品的責任感和對團隊的歸屬感,不要誤以為能切出漂亮的頁面就是能力的提高,純粹的寫代碼每 個人都差不多的,要成為合格的工程師,眼界要進一步放開,前端工程師能做的,不僅僅是切頁面而已,作一個精品項目,一定不乏專業(yè)的過程把控,這也是大多數(shù) 人最易忽略的地方。

            【勵志之本】

            其實,除了個人需要明確努力的方向,每個人都更渴望身處一個好團隊,誰都不希望有豬一樣的隊友。我們都很羨慕處身這樣的團隊,可以放心的將精力 放在純粹的技術(shù)上,身邊每個人都自覺的補充文檔注釋,代碼也層次清晰解偶充分重用率高,精妙的設計實現(xiàn)可以更快的傳播,bug得到的改進建議也是務實專業(yè) 的,技術(shù)在這種良性互動中價值倍增。我想這也算是好團隊的一種境界了,這有賴于團隊成員水平水漲船高。不過,反觀Yahoo的成長之路,他們的技術(shù)積淀也 是靠點滴的積累,其實他們當初的狀況不比現(xiàn)在的我們好哪去,10年的進化,才造就了Yahoo技術(shù)團隊的專業(yè)性和Hack精神,我們每個人才剛剛起步而 已。為了積攢工作中的幸福感,多付出一些是值得的。

            但我猜,你現(xiàn)在的處境一定不會太過樂觀,產(chǎn)品亂提需求、一句話的PRD、不被重視,被生硬的當作“資源“……反正,情況就是這么個情況,要么你 選擇抱怨下去,要么想辦法去改變。“積極主動“是源自內(nèi)心的一種堅韌品質(zhì),也是勵志之本,有些人在現(xiàn)實中被磨平了理想,有些人卻在黑暗森林中找到了方向, 這就是犬儒主義和英雄氣概之間的差別。這自不必詳說,因為這讓我想起了“大長今”,這簡直就是前端工程師的勵志典范:“這是一個可怕的環(huán)境,足以消磨任何 人的斗志和信念,所有來這里的人都變得麻木和無所作為,‘多栽軒‘惡劣的環(huán)境沒有改變長今,但長今卻改變了‘多栽軒‘所有的人“。

            如果你想做到“資深”,就一定要想清楚這一點,因為你是團隊的頂梁柱(業(yè)務),也是幸福感的源頭(士氣)。

            第四日,架構(gòu)和偽架構(gòu)

            【代碼設計的本質(zhì)】

            讀到這里,你不禁會問,前端領(lǐng)域存在“架構(gòu)師”嗎?這個問題會在后面的“碼農(nóng)的宿命”中展開解釋。這里先說下代碼架構(gòu)的一些瑣事吧。

            什么是架構(gòu)?架構(gòu)是由“架”和“構(gòu)”組成,架,即元件,構(gòu),即連接件。因此,架構(gòu)即是將總體分解為單元,然后定義單元之間的連接方式。架構(gòu)的含 義源自禪宗,而禪宗的基本信條則之一就是真理是無法用語言來描述的。這個基本信條有其背景,即語言具有某種抽象性。而人們對這種抽象性的悟道則直接影響對 事物的看法,進而決定了對客觀世界的分解方法。

            而在編程語言中,同樣存在這種禪宗所隱喻的悖論。在面向?qū)ο蟮慕炭茣校ǔEe一些顯而易見的例子,比如“水果”是一個類,包含有蘋果、桔子、 香蕉等實例,“蔬菜”也是一個類,包含白菜、冬瓜、茄子等實例。這兩個類之間并無交集,因此很容易理解。但實際項目中情況要復雜的多,比如兩個圖書類目 “文學”和“歷史”,那么“明朝那些事”應當是“文學”類的實例還是“歷史”類的實例呢?即一旦用語言說出了某一事物,即人為的割裂了世界,于是就會陷入 迷途。這在程序設計領(lǐng)域情況更甚,也是造成混亂的主要根源,也就是說,如果你的程序可擴展性不好,一定是程序作者對“單元”的定義不夠準確,即單元的概念 之間不夠“正交”。而這種架構(gòu)終是徒有其形,根基不穩(wěn)。

            因此,變量和類的命名才是真正考驗架構(gòu)功力的關(guān)鍵(命名是否準確清晰、單元之間是否有概念重疊或盲區(qū)),而和所謂“組合”、“繼承”、“橋接”等模式化的“外表”無本質(zhì)聯(lián)系。

            【偽架構(gòu)】

            實際情況是,程序員早早的就想讓自己和“架構(gòu)”扯上關(guān)系,并自封xx架構(gòu)師。在項目中應用各種模式分層、解耦方法,每個項目都可以產(chǎn)出一套看上 去很復雜的“架構(gòu)圖”,感覺很牛逼的樣子,沒錯,實踐這些方法論總不是壞事,但世界觀才是方法論的基礎,只有在概念上對產(chǎn)品模塊有科學的定義,方法論便自 然形成了,《編程珠璣》中一再提及數(shù)據(jù)結(jié)構(gòu)就是靜態(tài)的算法,在Web前端領(lǐng)域亦是如此,在頁面的建模過程中,定義分解維度要比分解方法更加基礎和重要。我 想阿當可以在《Web前端開發(fā)修煉之道》的第二版里加上這部分內(nèi)容。

            真正的高手用記事本就能寫出高質(zhì)量的代碼、用cvs就能做到完美的版本控制、用字典式的分解就能做好系統(tǒng)架構(gòu),我想,這正是劍宗一派的最高境界吧。

            第五日:尋找突破

            【動心忍性】

            技術(shù)流派看上去是如此吸引人,高手就像俠客一般,來去如風瀟灑自如。但反觀自己怎么看怎么沒有俠客那股范兒。盡管上文提到了一些道理,了解這些 盡管不是壞事,但缺少實踐總感覺是紙上談兵。更何況,日常的工作又是枯燥無味、繁雜單調(diào)。每個人都盼望更高的目標、接觸新鮮技術(shù)、將新技術(shù)運用到日常,在 探索嘗試之中尋找成就感。這種感覺可以理解,但卻缺少更深層次的思考。因為越到最后越會發(fā)現(xiàn)一線的工作才是最有挑戰(zhàn)的。當然,我說這話的前提是,你能如前 文所說具備合格的軟技能,需要一些技巧讓工作變得工整有序、節(jié)奏健康,這樣你才能將注意力放在純粹的代碼中,擺脫了外界的煩擾,方能從技術(shù)的角度思考突 破。這也是從初級到高級的進化過程需要大量的歷練的原因。正如玉伯所說,“枯燥是創(chuàng)新的源泉。如果你發(fā)現(xiàn)自己沒什么新想法,做事缺少激情,很可能是因為你 還未曾體驗過真正的枯燥的工作”。

            關(guān)于如何尋找突破,我的建議是馬上動手做、不要等,相信自己的直覺(這里和上文提到的先思后行是兩碼事)。比如,Slide幻燈控件理應支持觸 屏事件以更好的適應移動終端,或許你在用的Slide幻燈版本很舊、或者時間不允許、再或者你害怕對Slide改造而引入bug,不要擔心,大不了多花業(yè) 余時間,只要想,只要感覺合理和必要,就去做。因為這個過程帶來的編程體驗才是工程師們獨有的美妙體味。我現(xiàn)在還時常深夜寫代碼,沒有打擾、思如泉涌、代 碼也更加工整嚴謹,不失為一種享受。因此,用眼睛去觀察,用心去感觸,“所以動心忍性,才會增益其所不能”啊。

            【得與失】

            互聯(lián)網(wǎng)的發(fā)展的確太快,Web前端技術(shù)也在花樣翻新,有人經(jīng)不起誘惑,開始做新的嘗試。前端技術(shù)雖然范圍廣,但各個分支都還比較容易入門,比如 服務器端腳本編程、再比如純粹的WebApp,我認為這兩者都是前端技術(shù)的范疇,畢竟他們都沒有脫離“瀏覽器”,或者說類似瀏覽器的環(huán)境。NodeJS依 賴于V8,WebApp更是軟件化的WebPage。只要打好基礎,這些方向都是值得深入鉆研的,因為,互聯(lián)網(wǎng)的形態(tài)越發(fā)多元,新的技術(shù)總能找到用武之 地,這就要憑借自己的技術(shù)嗅覺和產(chǎn)品直覺,尋找技術(shù)和業(yè)務的契合點。

            這看上去是一種放棄,放棄了自己賴以生存的鐵飯碗(熟練的切頁面至少不會失業(yè)),實則不然。這種想法是一種誤區(qū),新的選擇并不會讓你放棄什么, 就像學會了開車,并不意味著就不會騎車了。其實改變的是思維方式而已,是一種進步,如果你能想通這一點,你也能跟得上互聯(lián)網(wǎng)發(fā)展的腳步了,打開你的思維, 讓技術(shù)變成你的金剛鉆,而不是包袱。

            所以,所謂得失之間的權(quán)衡,其實就是“解放思想”。做到了這一點,那么你已經(jīng)在做“技術(shù)驅(qū)動”了。

            【誤區(qū)】

            但是,不要高興的太早,“技術(shù)驅(qū)動”是需要大量的積累和經(jīng)驗的。在入行初期,很多人過于著迷與此,從而陷入了迷途。比如有人糾結(jié)于是否將dt、 dd的樣式清除從reset.css中拿掉,原因是覺得這兩個標簽的清除樣式會耗費一些渲染性能;或者是否需要將for循環(huán)改為while循環(huán)以提高js 執(zhí)行速度。盡管這些考慮看上去是合理的,但并不是性能的瓶頸所在,也就是說,你花了很大力氣重構(gòu)的代碼帶來的頁面性能提升,往往還不如將兩個css文件合 成一個帶來的提升明顯。就好比用一把米尺量東西,沒必要精確到小數(shù)點后10位,因為精確到小數(shù)點后2位就已經(jīng)是不準確的了。這種技術(shù)誤區(qū)常常讓人撿了芝麻 丟了西瓜。

            話說回來,這里提到的懷疑權(quán)威的精神是絕對應當鼓勵的,但不應當止于表象,如果懷疑dt的清除樣式會對性能帶來影響,就應當想辦法拿到數(shù)據(jù),用事實來證明自己的猜測。數(shù)據(jù)是不會騙人的。而求證過程本身就是一種能力的鍛煉。

            【技術(shù)驅(qū)動】

            說到這里,你大概對“技術(shù)驅(qū)動”有那么一點點感覺了。身邊太多人在抱怨“公司不重視前端”、公司不是技術(shù)驅(qū)動的、技術(shù)沒機會推動產(chǎn)品業(yè)績、我的價值得不到體現(xiàn)?

            什么是技術(shù)驅(qū)動?簡單講,就是技術(shù)對業(yè)務有積極推動作用。更多的是工程師發(fā)起、工程師影響、工程師負責。剛才提到的用數(shù)據(jù)說話只是一種“驅(qū)動”技巧,那么我需要何種數(shù)據(jù),數(shù)據(jù)從哪里來?我來分享一個實際的場景吧。

            工程師A被委派一個重要的頻道首頁,因為是新年版,所以要趕在年前上線。A學了一點點響應式設計,想在這次重構(gòu)中加上,但誰也沒做過響應式設 計,需求方根本不懂,設計師也懵懵懂懂,交互設計師太忙,做完交互稿就忙別的去了。A糾結(jié)了,按部就班的把項目做完上線發(fā)布,盡管不會出什么問題,但總覺 少點什么。這時A做了兩個決定,1,我要按時完成項目,2,趁機實踐我在響應式設計中的想法和思考,若成功,作為附加值贈送給需求方,若失敗,權(quán)當技術(shù)玩 具耍一耍罷了。所以A熟練的提前完成了項目,剩下的時間開始考慮如何將首頁適應到各個平臺中,視覺設計是一大難題,他用吃飯的時間找了設計師收集建議,對 窄屏中的內(nèi)容模塊做了看似合理的編排,代碼上hack一下,能夠正確適配,就發(fā)布上線了。這件事情需求方不知道,視覺設計師也不了解,交互設計師更沒工夫 操心。A感覺挺爽,開始給工程師弟兄們到處炫耀這個好玩的功能,B看了問,手機端訪問量如何,A覺得這個問題有道理,就去部署埋點,一周后拿到數(shù)據(jù)出奇的 意外,首先,移動段的訪問量穩(wěn)步增加,趨勢健康,再者,移動端首屏焦點廣告位的點擊率較PC端高了近一倍,這個數(shù)據(jù)讓A喜出望外,興奮的拿著報表找到交互 設計師C和市場研究的同事D,D看了報表之后立即啟動一個項目,專門調(diào)研公司全站響應式設計頁面在PC端和移動端的點擊率、PV、UV趨勢方面的影響…… 后來發(fā)生的事情就都水到渠成了,設計師C開始注意設計頁面交互時(至少是有條件的考慮)對移動端的適配,D的調(diào)研報告也放到了UED老大的案頭……接下來 的事情,你懂得。A被指派要出一套響應式最佳實踐和規(guī)范,最終,A走在了技術(shù)的前沿,也因此拿到了好績效。

            這件事情就是一個典型的技術(shù)驅(qū)動的例子。誰不讓你玩技術(shù)了,誰不重視你了,誰把你當工具了,誰覺得你的代碼沒價值?這世界只有自己把自己看扁,誰想跟你這個蠅頭小卒過不去?用實力說話,用數(shù)據(jù)說話,用獨到的見解說話,想不做技術(shù)驅(qū)動都難。

            第六日:碼農(nóng)的宿命

            【青春飯】

            “碼農(nóng)”是IT從業(yè)者一個自嘲的稱號,也有從事沒有發(fā)展前景的軟件開發(fā)職位,靠寫代碼為生的意思。但我認為碼農(nóng)是一個愛稱,編碼的農(nóng)民,和農(nóng)民 一樣有著執(zhí)著純真樸實豪爽的共性,僅僅分工不同而已。就好比農(nóng)業(yè)社會對糧食的依賴,工業(yè)化進程對計算機應用也有著很強的依賴,大量的需求催生出這樣一群 人。他們有智慧的大腦,對于編程,設計,開發(fā)都有著熟練的技巧,但多數(shù)人看來,碼農(nóng)的特點是:

            1,收入低
            2,工作單調(diào)
            3,工作時間長

            實際上這個描述非常片面,或者說是外行看熱鬧。第一,全行業(yè)比較來看,軟件開發(fā)領(lǐng)域收入為中等偏上;第二,程序員一般都是有癖好的,沉浸在自己 的癖好中是不會感覺單調(diào)的;第三,程序員有一定的時間自由度(如果你是一名合格的程序員的話),至少不會像流水生產(chǎn)線工人一樣。其實,通過幾十年的發(fā)展, 我們對程序員的定義更加科學,比如很多IT企業(yè)都開始建立詳細的JM(Job Module),即職級模型,程序員沿著專業(yè)方向可以走到很高,甚至可以 說,程序員是可以被當成一生的事業(yè)的。

            然而,有一個非常普遍的觀點是,程序員和做模特一樣是吃青春飯的,到了三十歲就要考慮轉(zhuǎn)行或者轉(zhuǎn)管理。盡管這種觀點頗具欺騙性,但至少它對一種 人是適用的,即入錯了行的人。如果你骨子里不想寫程序,就算年紀輕輕為了生計寫幾年代碼,之后肯定會另有他途。心非所屬則不必勉強,但問題是,即便如此, 你知道你的心之所屬嗎?

            我們知道,一個成熟的產(chǎn)業(yè)一定需要各色崗位來支撐,若要成熟,則需要時間的沉淀,比如實體經(jīng)濟制造業(yè),創(chuàng)意、生產(chǎn)線、高級技工、技術(shù)管理四個方 面都產(chǎn)出大量的高級人才。因為歷史悠久,我們能看得到。而軟件產(chǎn)業(yè)則不然,九成以上是剛出道的新手,并沒有太多“高級”和“資深”的具體樣板可供參照,在 前端開發(fā)領(lǐng)域中情況更甚,絕大部分人根本搞不清楚什么樣才是“資深”前端工程師,相比傳統(tǒng)軟件行業(yè)近四十年的進化,我不相信僅有幾年光景的前端技術(shù)崗位能 產(chǎn)出多少貨真價實的“資深”。但互聯(lián)網(wǎng)崛起速度太快,還沒有等技術(shù)基礎打牢,互聯(lián)網(wǎng)形態(tài)就又花樣翻新了,這種變化是一種常態(tài),而崗位的設定也在這種變化之 中自然的優(yōu)勝劣汰,比如兩年前可能還難以想象數(shù)據(jù)部門會需要前端工程師,他們甚至不直接和瀏覽器打交道。前端工程師需要適應這種變化帶來的觀念沖擊,不要 以為自己只能做切頁面、或者只會給頁面搞重構(gòu)、只會搞兼容性,要把自己放在整個軟件行業(yè)來看。

            所以,由于歷史“不悠久”導致的崗位模糊本身不是什么大問題,崗位的演化本身就包含在互聯(lián)網(wǎng)的發(fā)展軌跡之中。所以,當今的互聯(lián)網(wǎng)IT狀況,就好 比移動終端的大哥大時代、云計算的肉雞時代、或者桌面操作系統(tǒng)的DOS時代。因此,前端工程師當前要務是要想清楚看清楚,在互聯(lián)網(wǎng)中我能做什么,而不是作 為前端工程師我能做什么,所以,從這個角度講,技術(shù)是一個工具,放大來看,技術(shù)也只是你職業(yè)生涯中很小的組成部分,而你的從業(yè)積累、和知識面的廣度深度才 是你隨著時間的推移慢慢步入“資深”的原因所在,而不是寫了個什么框架就變“資深”了。如果有一天互聯(lián)網(wǎng)形態(tài)固定了,它的崗位可能真正就定型了,才會有真 正清晰的職能邊界,就像藍色巨人IBM中的各色崗位一樣,邊界清晰,權(quán)責分明,普通程序員只能實現(xiàn)接口而無機會設計接口、低層級的工程師也無機會躍進式的 接觸項目架構(gòu)、技術(shù)經(jīng)理人也不能輕易對產(chǎn)品有決策性影響,到這時,人的能力才真正的被限制在方圓之內(nèi),容不得越界,這種環(huán)境下人的成長非常緩慢。根本不會 有像今天互聯(lián)網(wǎng)亂局之中所提倡的創(chuàng)新、革命、成長和思想解放。簡單講,一旦產(chǎn)業(yè)定型,就不太需要很多“創(chuàng)造”了,更多的是“維護”。所以,我個人寧愿互聯(lián) 網(wǎng)IT“黑暗”的中世紀越久越好,至少對于年輕氣盛程序員來說,黑暗的叢林環(huán)境才是真正的自然進化最理想的土壤,這時我想起了狄更斯在“雙城記”中的開 篇。

            “這是最好的時代,這是最壞的時代;這是智慧的時代,這是愚蠢的時代;這是信仰的時期,這是懷疑的時期;這是光明的季節(jié),這是黑暗的季節(jié);這是希望之春,這是失望之冬;人們面前有著各樣事物,人們面前一無所有;人們正在直登天堂,人們正在直下地獄”。

            【半路出家的危與機】

            然而,不管怎樣,信心的樹立不是一蹴而就的,對于轉(zhuǎn)行做前端的人來說更是如此。俗話說,隔行入隔山。每個行業(yè)自有其道,自然不是想做就做。前端 技術(shù)領(lǐng)域半路出家者非常多,我們來分析一下轉(zhuǎn)行的心理。第一,看到前端技術(shù)入門簡單、互聯(lián)網(wǎng)對前端技術(shù)的需求缺口巨大;第二,前端技術(shù)所見即所得、感覺學 習起來很快;第三,我身邊的某某轉(zhuǎn)行作前端看上去不錯、我似乎也可以;第四,我不喜歡我現(xiàn)在做的工作、想換行業(yè)、正好前端技術(shù)上手較快,就選他吧;第五, 我真的喜歡做Web前端,為它付出再多都是值得的。

            轉(zhuǎn)行者的心態(tài)比較容易走兩個極端,一是只看到新行業(yè)的好,二是只覺得原工作很糟糕。但不管是什么行業(yè)的轉(zhuǎn)行,對自己的職業(yè)規(guī)劃的思考都應當先行一步。即務必首先清晰的回答這些問題:

            1,我能做什么?
            2,我不能做什么?
            3,我的優(yōu)勢是什么?
            4,我的劣勢是什么?
            5,做新行業(yè)對我有何好處?
            6,換行會讓我付出何種代價?
            7,如何定義轉(zhuǎn)行成功?

            因為面試的時候一定會被這些問題所挑戰(zhàn)。如果支支吾吾說不清楚,要么是對自己未來不負責任,要么骨子里就是草根一族,習慣做什么都蜻蜓點水淺嘗 輒止,也難讓人信服你的轉(zhuǎn)行是一個權(quán)衡再三看起來合理的選擇。我無法幫每個人回答這些問題,但至少有兩點是確定的,第一,Web前端技術(shù)是一個朝陽行業(yè), 絕對值得義無反顧的堅持下去;第二,你將經(jīng)歷從未有過的枯燥、苛刻的歷練,所謂痛苦的“行弗亂其所為“階段。不過話說回來,經(jīng)歷過高考的人,還怕個屁啊。

            有心之人自有城府、懂得放棄,看得清大勢中的危機、識得懂繁華里的機遇。尤其當立足于Web前端技術(shù)時,這種感覺就愈發(fā)強烈。因為國內(nèi)外前端技 術(shù)領(lǐng)域從2000年至今一直非常活躍,前端技術(shù)前進的步伐也很快,對于一些人來說,不管你是在大公司供職還是創(chuàng)業(yè),不管你是在接外包項目還是自己寫開源項 目,從轉(zhuǎn)行到跟得上新技術(shù)的腳步是有一些方法和“捷徑”的。

            第一,梳理知識架構(gòu)

            我們知道知識積累有兩種思路,第一種是先構(gòu)建知識面、建立技術(shù)體系的大局觀,即構(gòu)建樹干,然后分別深入每一個知識點,即構(gòu)建枝葉,最終形成大 樹。第二種是先收集知識點,越多越好,最后用一根線索將這些知識點串接起來,同樣形成大樹。第一種方法比較適合科班秀才,第二種方法則更適合轉(zhuǎn)行作前端的 人,即實踐先行,理論升華在后。比如對“IE6怪異模式“這條線索來說,要首先將遇到的IE6下的樣式bug收集起來,每個bug都力爭寫一個簡單的 demo復現(xiàn)之,等到你收集到第100個bug的時候,再笨的人都能看出一些規(guī)律,這時就會自然的理解IE的hasLayout、BFC和各種bug的原 因、你就成為了IE6的hack專家了,當你成為100個知識線索的專家的時候,你已經(jīng)可以稱得上“資深”的水平了。我們知道,10個人中有9個是堅持不 下來的,他們會以項目忙等各種理由萬般推托,將自己硬生生的限制在草根一族,坐等被淘汰。所以,對于立志作前端的人來說,這種點滴積累和梳理知識非常重 要。

            第二,分解目標

            將手頭的工作分解為幾部分來看待,1,基本技能,2,項目經(jīng)驗,3,溝通能力,4,主動性和影響力。想清楚做一件事情你想在哪方面得到歷練,比 如,我之前在做第一次淘寶彩票常規(guī)性重構(gòu)的時候(正好是一次視覺和交互上的全新設計),我清楚的明白這次重構(gòu)的目的是鍛煉自己在架構(gòu)準富應用時的模塊解偶 能力,尋找在其他項目中架構(gòu)的共通之處,所以我寧愿加班或花更多精力做這個事情,當然更沒打算向業(yè)務方多解釋什么,這件事情對我來說純粹是技能的鍛煉。而 經(jīng)過這一次重構(gòu)之后,我意外的發(fā)現(xiàn)對業(yè)務的理解更透徹深入、更清晰的把握用戶體驗上的瓶頸所在。如果一開始就把這次常規(guī)改版當成一個普通的項目按部就班的 做,我只能說,你也能按時完成項目,按時發(fā)布,但真真浪費了一次寶貴的鍛煉機會,項目總結(jié)時也難有“動心忍性”的體會。

            所以,每個項目的每個事情都應當認真對待,甚至要超出認真的對待,想清楚做好每件事對于自己哪方面有所提升?哪怕是一個bug的解決,即便不是 自己的問題也不要草草踢出去了事,而是分析出問題原因,給出方案,有目的involve各方知曉……,正規(guī)的對待每個不起眼的小事,時間久了歷練了心智, 這時如果突然遇到一個p0級的嚴重線上bug(比如淘寶首頁白屏,夠嚴重的了吧)也不會立即亂了方寸,這也是我上文提到的心有城府自然淡定萬倍,而這種淡 定的氣場對身邊浮躁的人來說也是一種震懾和療傷,影響力自然而然就形成了。

            第三,作分享

            做分享這事兒真的是一本萬利。有心的人一定要逼著自己做分享,而且要做好。首先,自己了解的知識不叫掌握,只有理解并表達出來能讓別人理解才叫 掌握,比如如果你解釋不清楚hasLayout,多半說明自己沒理解,如果你搞不懂雙飛翼的使用場景,可能真的不知道布局的核心要素。再者,作分享絕對鍛 煉知識點的提煉能力和表達能力,我們作為工程師不知道多少次和強硬的需求方pk,被擊敗的一塌糊涂。也反映出工程師很難提煉出通俗易懂的語言將技術(shù)要點表 述清楚。而做ppt和分享正是鍛煉這種能力,將自己的觀點提煉出要點和線索,分享次數(shù)多了,自然熟能生巧。檔次也再慢慢提高。另一方面,逼迫自己站在公眾 場合里大聲講話,本來就是提高自信的一種鍛煉。

            這時,你或許會問,我講的東西大家都明白,我講的是不是多余,我第一次講講不好怎么辦,大家會不會像看玩猴似的看我“這SB,講這么爛還上來講”?要是講不好我以后再講沒人聽怎么辦,我今后怎么做人啊?

            老實說,這是一道坎,任何人都要跨過去的,誰都一樣,你敢鼓起勇氣在大庭廣眾之下向愛人表白,就沒勇氣對自己的職業(yè)宿命說不?其實勇敢的跨越這 一步,你會意外的收獲他人的掌聲和贊許,這些掌聲和贊許不是送給你所分享的內(nèi)容,而是送給你的認真和勇氣。這個心結(jié)過不去,那就老老實實呆在自己的象牙塔 里遺老終生,當一輩子工程師里的鉆石王老五吧。

            【匠人多福】

            如果你能耐心讀到這里,心里一定有一個疑問,上面說的都是技術(shù)上能力上怎樣怎樣,那我所做項目不給力又當如何?如果項目不掙錢、黃了、裁了,我的努力不就白費了嗎?我又有什么績效和價值呢?

            沒錯,有這種想法的人不在少數(shù)。特別是剛出道的校招同學往往更加心高氣傲,以為自己有改變世界的本事,一定要參與一個牛逼的團隊做一款光鮮靚麗受人追捧能給自己臉上貼金的項目。如果你有這種想法,趁早打消掉這個念頭,當然,我們這里先不討論創(chuàng)業(yè)的情形。

            第一,如果你剛畢業(yè)就加入一個牛逼團隊,說難聽點,你就是團隊中其他人眼中的“豬一樣的隊友”,不創(chuàng)造價值且拖項目后腿(顯然大家都要照顧你的成長啊),按照271理論,你沒有理由不是這個1。至少相當長一段時間內(nèi)是這樣。

            第二,你在所謂牛逼團隊中的創(chuàng)造性受限,因為創(chuàng)新多來自于團隊中的“資深“和大牛們,你參與討論但觀點通常不會被采納,他們只會給你這個菜鳥分活干,想想看,你如何能花兩到三年就超越身邊的大牛們?甚至連拉近與他們的距離都難。

            第三,如果身在牛逼團隊,自然心理對周圍的牛人們有所期待,希望他們能灌輸給你一些牛逼的知識和牛逼的理念。這種思想上的惰性在職場生涯之初是非常危險的。要知道技術(shù)和知識本身是很簡單和淳樸的,只不過披上了一個光鮮項目的外衣而讓人感覺與眾不同。

            第四,由簡入奢易,由奢入簡難,做過一個看似光彩的項目,心理再難放平穩(wěn),去踏實的做一個看上去不那么酷的產(chǎn)品。這種浮躁心態(tài)會嚴重影響今后的職業(yè)發(fā)展和成長。

            第五,光鮮靚麗的項目被各種老大關(guān)注,是難容忍犯錯誤的,傻瓜都知道犯錯誤在成長之初的重要性。

            就我所看到的情形看,一開始加入看似很牛的項目組,三年后得到的成長,比那些開始加入一個不被重視的項目的同學要小很多,而后者在能力上的彈性 卻更大。所以,道理很簡單,你是要把一個很酷的項目做的和之前差不多酷,還是把一個不酷的項目做的很酷?項目是不是因為你的加入而變得與眾不同了?

            從這個角度講,不管是轉(zhuǎn)行的新人還是剛出道的秀才,最好將自己當作“匠人”來對待,你的工作是“打磨”你的項目,并在這個過程中收獲經(jīng)驗和成 長。付出的是勤奮,鍛煉的是手藝,磨練的是心智。因此,你的價值來自于你“活兒“的質(zhì)量,“活兒”的質(zhì)量來自于你接手的項目之前和之后的差別。做好活兒是 匠人應有的職業(yè)心態(tài)。想通這一點,內(nèi)心自然少一些糾結(jié),才會對自己對項目的貢獻度有客觀的認識,不會感覺被項目所綁架。

            做一名多福的匠人,擁有了金剛鉆、就不怕攬不到瓷器活兒。但對于人的成長來說,如果說“項目”重要但不關(guān)鍵,那么什么才是關(guān)鍵呢?這個話題還會在接下來的“伯樂與千里馬”這篇中給出答案。 

            【若干年后】

            現(xiàn)在,讓我們回過頭回答一下“青春飯”的問題。在“青春飯”小節(jié)中提到,“程序員到三十歲之后需要轉(zhuǎn)行或者轉(zhuǎn)管理嗎?”

            上文提到,工業(yè)化生產(chǎn)的四個領(lǐng)域,1,創(chuàng)意,2,生產(chǎn)線,3,高級技工,4,技術(shù)管理。Web前端技術(shù)也是如此,可以在這四個領(lǐng)域找到各自的歸宿。

            第一,“創(chuàng)意“

            即和產(chǎn)品需求越走越近,擁有良好的產(chǎn)品感,對產(chǎn)品需求、設計交互把握準確,能夠用適當?shù)募夹g(shù)方案推動產(chǎn)品用戶體驗,屬于“架構(gòu)師”的范疇,因為 職能更加靠前,偏“出主意”型的。這種人更貼近用戶,需要活躍的思維、廣闊眼界、厚實的項目經(jīng)驗。更多的影響產(chǎn)品體驗方面的決策。

            第二,“生產(chǎn)線“

            即前端基礎設施建設,優(yōu)化前端開發(fā)流程,開發(fā)工具,包括開發(fā)環(huán)境、打包上線自動化、和各種監(jiān)控平臺和數(shù)據(jù)收集等,屬于“技術(shù)支持”的范疇,相比于很多企業(yè)粗獷難用的平臺工具,前端技術(shù)方面的基礎設施建設基礎還需更加夯實,因為這是高效生產(chǎn)的基本保證。

            第三,“高級技工“

            即高級前端開發(fā)工程師,專職做項目,將產(chǎn)品做精做透,用代碼將產(chǎn)品用戶體驗推向極致,偏“實戰(zhàn)”型的,是項目的中堅力量,直接產(chǎn)出成果,影響產(chǎn)品效益。屬于項目里的“資深”。

            第四,“技術(shù)管理“

            即做技術(shù)經(jīng)理,這才是多數(shù)人所理解的“管理”,其實就是帶團隊、靠團隊拿成果。這類人具有敏感的技術(shù)情結(jié),在技術(shù)風潮中把握方向,能夠指導培訓新人,為各個業(yè)務輸出前端人才,偏“教練”型的,促進新技術(shù)對業(yè)務的影響。并有意識的開辟新的技術(shù)領(lǐng)域。

            可見,轉(zhuǎn)管理可不是想當然,也不是所謂做項目變資深了就能轉(zhuǎn)管理,轉(zhuǎn)了也不一定能做好。根據(jù)“彼得原理”,即人總是傾向于晉升到他所不能勝任的崗位,這時就又陷入“帕金森”定律所隱喻的惡性循環(huán)之中,直到你帶的團隊整個垮掉。

            所以,轉(zhuǎn)管理應當是一件非常慎重的事情,不是所謂程序員混不下去就轉(zhuǎn)管理這么簡單。但不管怎樣,有一件事情是需要尤其要想清楚,即,轉(zhuǎn)了管理,技術(shù)就丟了嗎?我們在第七日“伯樂與千里馬”中再深入聊聊這個事兒。

            第七日,伯樂與千里馬

            【師兄們的抉擇 1】

            千里馬常有,而伯樂不常有。——韓愈,“馬說”。

            一個人這輩子能遇到一個好師兄是一種緣分,可遇不可求。很多人工作中的幸福感似乎也源自這種被認同,被師兄的了解和認同,有人能直言不諱的指出 你的不足,幫你發(fā)現(xiàn)機會,并將最適合你做的事情分配給你,這是莫大的幸運,但如此幸運的人十之一二,大多數(shù)人因為缺少伯樂的提點,漸漸辱于“奴隸人之手 “,潛力漸失,毀于中庸。

            在前端技術(shù)領(lǐng)域,這種情況很普遍也很特殊,當然有很多客觀原因。即前端技術(shù)進入公眾視野時間不長,有實力的伯樂更加是鳳毛麟角。更何況,Web 前端技術(shù)還有著一些江湖氣,知識點過于瑣碎,技術(shù)價值觀的博弈也難分伯仲,即全局的系統(tǒng)的知識結(jié)構(gòu)并未成體系,這些因素也客觀上影響了“正統(tǒng)“前端技術(shù)的 沉淀,奇技淫巧被濫用,前端技術(shù)知識的傳承也過于泛泛,新人很難看清時局把握主次,加之業(yè)務上的壓力,未免過早導致技術(shù)動作變形。而這些問題也無法全賴自 己全盤消化,若有人指點迷津,情況要好上萬倍。因此,前端技術(shù)領(lǐng)域,為自己覓得一個靠譜的師兄,重要性要蓋過項目、團隊、公司、甚至薪水。

            這也是上文所說的“項目不重要,師兄才重要“的原因。說到這里就有一個問題,每個人都問下自己,你是想當師弟呢還是想當師兄呢?當師兄有什么好處呢?

            沒錯,很多師兄都是被師兄,甚至沒有做好當師兄的準備,更進一步說,不少經(jīng)理人也都是“被經(jīng)理人“,沒有做好準備就被推到了管理崗位。帶人是耗 精力的,師兄要做很多思想斗爭才舍得把這些寶貴的精力放在那些菜鳥身上,這不是一個技術(shù)問題,而是一個道德問題。要記住,沒有誰應該無緣無故把自己所掌握 技能給你傾囊相授,如此皆命也。讀到這里,作為菜鳥,作為學徒,作為新人,作為師弟,你做到對這份命運的足夠尊重了嗎?

            尊師重教的傳統(tǒng)美德并沒有在技術(shù)領(lǐng)域得以很好的延續(xù)。也正因為此,人才梯隊難建立起來,但對于師兄來說,卻是有更多機遇的。

            【師兄們的抉擇 2】

            作為師兄,不管是主動還是被動,肯定會想當師兄對我有什么提升?對于初次做師兄的人來說,最大的提升在于兩方面,1,任務分解,2,問題分析。

            第一,任務分解,作為師兄要給師弟派分任務,就涉及到任務分解,分解這事兒往低了說,就是派活,往高了說,其實就是做“架構(gòu)”,比如一個頁面, 按照什么思路進行模塊劃分,模塊劃分是否適合單人開發(fā),如何控制共用樣式和共用腳本,我需要為他提供什么接口,如何控制他的代碼并入整個頁面時不會影響整 體頁面代碼的熵值,這些都是實打?qū)嵉?#8220;架構(gòu)“應該包含的問題,而從小頁面開始就做這種鍛煉,做的多了,“架構(gòu)感”自然就形成了。

            第二,問題分析,在之前自己寫代碼都是單打獨斗,什么都是用代碼解決問題,但一旦涉及協(xié)作,就要強迫自己分析問題,或者說給徒弟分析問題,告訴 他應當用什么方法來解決問題,當說到“方法”時,腦子里定形成了一個方案,按照這個方案路子走一定能解決問題。分析問題比寫代碼要更抽象、更高效,因為在 腦子里構(gòu)建方案要比寫代碼要快,思考也會更加縝密,當鍛煉的多了,思考越來越快,代碼的草稿也很快就在腦海中形成了,這也是我們說為什么很多人不寫代碼但 編碼思路和水平都很高的原因。

            這些工作方法對了,積累多了,就是提高。對于技術(shù)經(jīng)理人來說,也是同樣的道理。所以,就像在第五日的“得與失”部分提到的那樣,轉(zhuǎn)身師兄、變身管理并不意味著“失“掉技術(shù)飯碗,而是一種進步。

            【做自己的伯樂】

            那么,在前端技術(shù)領(lǐng)域里什么樣的人才算千里馬,其實人人都是千里馬,人人都可以發(fā)掘自己的潛力,如果上面的文字你能讀懂,能認可,這種自我發(fā)掘已經(jīng)開始了,沒有一個好伯樂又何妨呢?做一個勤快的小碼農(nóng),少一些勢利的紛爭,很快會發(fā)現(xiàn),自己才是最好的伯樂。

            但這并不是說,他人對自己的觀點不重要,有時甚至要綜合各種聲音,所以,多找身邊的大牛們聊聊天,多找你的師兄和主管,不管他們給你的建議是多么形而上,總有一些聲音對你是有益的,多收集,有好處。

            第八日,做地球上最牛的UED

            【誰推動了歷史前進,英雄?還是人民?】

            “做地球上最牛的UED!”,這是淘寶UED創(chuàng)立之初的口號,現(xiàn)在被漸漸淡忘了,因為微博上的一些討論,又想起了這份曾經(jīng)美好的初衷。玉伯也感 嘆道:“這愿景曾吸引了多少好漢前往投奔呀。只可惜短短幾年間,這愿景好像越來越遠了”。問題是,要做好一個團隊,靠的是個人、還是整體?愿景是越來越遠 了嗎?

            是誰推動了歷史的前進,是英雄?還是人民?微觀來看,是英雄,宏觀來看,是人民。再放大了看,是互聯(lián)網(wǎng)大潮之崛起推動了前端技術(shù)的進步,時勢需要UED、需要用戶體驗。

            所以,UED團隊的創(chuàng)立發(fā)展受這些積極的外因影響,趕上了好時候,成員也跟著沾光。然而,我并不關(guān)心這個口號,我只關(guān)心體制內(nèi)的關(guān)鍵人物,那些 帶動整個團隊水漲船高的人們。往往我們發(fā)現(xiàn),某些人的高度代表了整個團隊的高度,個體的影響力代表了整個團隊的影響力,某個人的水平代表了整個團隊的水 平。支付寶、淘寶、騰訊、百度、盛大,都是如此。而我們作為普通的個體,正是要勵志成為這種人,成為真真用技術(shù)推動用戶體驗前進的尖刀人物。

            這時我想起了很多人在知乎上的問題,關(guān)于跳槽、關(guān)于轉(zhuǎn)行、關(guān)于創(chuàng)業(yè)、關(guān)于各種UED團隊。我想,讀得懂我上面的文字,你心理也許會有自己的答案。

            【歸宿】

            最后,還有一個不得不說的問題,即歸屬問題,前端開發(fā)應當歸屬于UED還是技術(shù)部門?應當說,當前Web前端技術(shù)的價值體現(xiàn)在“用戶體驗“上。 是用戶體驗這塊陣地最后一道坎。也就是說,前端工程師應當重點考慮我所作的頁面的感官體驗。這是需要一些靈感和感性的,應當看到帥氣優(yōu)雅的界面會心有所 動、或者實現(xiàn)一款精巧的小組件時萌生一陣快意。這種所見即所得的美妙編程體驗正是其他后端工程師無法體驗到的。因此,這種精確到像素級的精工雕琢雖然不直 接決定產(chǎn)品生死,但卻是提升產(chǎn)品品味和時尚感的要素。物質(zhì)越來越豐富的今天,大眾的更高訴求不就是品味和時尚嗎?

            如果將前端歸到技術(shù)部門,一方面和“設計“離的更遠,代碼寫的規(guī)規(guī)矩矩但漸缺少了靈性,另一方面作為工程師又缺少計算機專業(yè)課的功底,才真正喪 失了優(yōu)勢所在,如果有一天,前端工程師的平均水平足夠高,清一色的計算機科班出身,似乎更合適歸入到技術(shù)部門。所以,Web前端工程師是“工程師“,需要 科學嚴謹?shù)木幊棠芰Γ硖嶶ED所應當具備的美感和靈性是萬不可被剝奪去的。

            還有一點,Web前端工程師作為UED之中最具實踐精神和邏輯思維的工種,是能夠?qū)⒓夹g(shù)對設計的影響發(fā)揮到最大,可以催生出大量的創(chuàng)造和革新的,這一點也是傳統(tǒng)后端工程師所不具備的。

            第九日,前端技術(shù)體系

            現(xiàn)在越來越感覺到前端技術(shù)需要成體系的積累,一方面可以規(guī)范我們的前端技術(shù)培訓,另一方面,作為知識線索為新人做指引,省的走彎路,避免陷入奇技淫巧的深坑之中不能自拔。

            之前我整理了一下“前端技術(shù)知識結(jié)構(gòu)”,羅列的比較散,但也基本表述清楚了我的觀點。今年上半年也在整個研發(fā)中心組織了一次前端技術(shù)培訓,對于前端技術(shù)的演化規(guī)律也有過整理,都放在了這個ppt中,希望對大家有所幫助。

          posted @ 2013-01-16 12:53 楊軍威 閱讀(299) | 評論 (0)編輯 收藏

          js面向?qū)ο?/a>

          【一】 面向?qū)ο蟮幕靖拍?/strong>

            面向?qū)ο蟮挠⑽娜Q叫做Object Oriented,簡稱OO。OO其實包括OOA(Object Oriented Analysis,面向?qū)ο蠓治?、OOD(Object Oriented Design,面向?qū)ο笤O計)和OOP(Object Oriented Programming,面向?qū)ο蟮某绦蛟O計)。

            通常所說的面向?qū)ο笫侵窸OP, OOP是一種圍繞真實世界的概念來組織模型的程序設計方法,它采用對象來描述問題空間的實體。在使用計算機解決問題時,對象是作為計算機模擬真實世界的一個抽象,一個對象就是一個物理實體或邏輯實體,它反映了系統(tǒng)為之保存信息和(或)與它交互的能力。使其具有自己的屬性和行為, 從而簡化對復雜事物的描述,更有利于工程的可維護性和擴展性。

            OOP同結(jié)構(gòu)化程序設計相比最大的區(qū)別就在于: 前者首先關(guān)心的是所要處理的數(shù)據(jù),而后者首先關(guān)心的是功能。

            【二】 面向?qū)ο笕齻€基本特征

            封裝 (Encapsulation) 將數(shù)據(jù)以及相關(guān)的操作組織在一起,成為獨立的構(gòu)件。外部無法直接訪問這些封裝了的數(shù)據(jù),從而保證了這些數(shù)據(jù)的正確性。封裝的目的是為了內(nèi)部數(shù)據(jù)表現(xiàn)形式和實現(xiàn)細節(jié)的隱藏,信息隱藏是為了減少系統(tǒng)各部分間的依賴性,各部分間必須通過明確的通道傳送信息,也就是對象間的接口.這樣一來,隱藏了部分內(nèi)部的細節(jié),極大方便系統(tǒng)的開發(fā),維護和擴展。

            繼承 (Inheritance) 繼承是一種聯(lián)結(jié)類的層次模型,并且允許和鼓勵類的重用,它提供了一種明確表述共性的方法。一個新類可以從現(xiàn)有的類中派生,這個過程稱為類的繼承。新類繼承了原始類的特性,新類稱為原始類的派生類(子類),而原始類稱為新類的基類(父類)。派生類可以從它的基類那里繼承方法和實例變量,并且派生類可以修改或增加新的方法使之更適合特殊的需求。繼承性很好地解決了軟件的可重用性問題。

            多態(tài) (Polymorphism) 多態(tài)是允許你將父對象設置成為和一個或更多的他的子對象相等的技術(shù),賦值之后,父對象就可以根據(jù)當前賦值給它的子對象的特性以不同的方式運作。簡單的說,就是允許類與類之間相同方法名的指針得以調(diào)用, 這樣很好地解決了應用程序函數(shù)同名問題。實現(xiàn)多態(tài),有二種方式,覆蓋,重載。

            【三】 Javascript 面向?qū)ο?/strong>

            javascript本身是一種基于對象(object-based)的語言,我們?nèi)粘>幋a過程中用到的所有東西幾乎都是對象(Number, String, Boolean, etc.)。但是,相對于一些流行的面向?qū)ο笳Z言(C++, C#, java),它又不是一種真正的面向?qū)ο缶幊?OOP)語言,因為它的語法中沒有class的概念。

            Keyword: class, object, `this`, closure, constructor, prototype

            幾種對象封裝的方法

          • 繼承
          • 多態(tài)體現(xiàn)

            之一、幾種對象封裝的方法

            1. 對象封裝 – 原始模式

            假定我們把貓看成一個對象,它有”name”和”color”兩個屬性, “etc” 行為。

          var Cat = {     name: ''     color: '',     eat: function() {} }; 

            現(xiàn)在,我們需要根據(jù)這個原型對象的規(guī)格(schema),生成兩個實例對象。

          function eat() {     console.log('I\'m eta fish'); } var cat1 = {name: 'Kitty', color: 'white', eat: eat}; var cat2 = {name: 'Smokey', color: 'black', eat: eat};  // var cat3, cat4 ,... 

            不方便創(chuàng)建多個實例對象,擴展性差, 實例(cat1, cat2)之間找不到聯(lián)系。…

            2. 對象封裝 – 構(gòu)造函數(shù)模式

            “構(gòu)造函數(shù)”,就是一個普通函數(shù),但是內(nèi)部使用了 `this` 變量。對函數(shù)使用 `new` 運算符,就能生成實例,并且 `this` 變量會綁定在實例對象上。

            使用構(gòu)造器創(chuàng)建出來的對象會有一個 `constructor` 屬性,指向它們的構(gòu)造函數(shù)。

            `Class` 只是一個模板,創(chuàng)建出來的來實例都是由模板生成。

            比如,貓的原型對象現(xiàn)在可以這樣寫:

          function Cat(name,color){     this.name = name;     this.color = color;     this.eat = function() { console.log('eat fish'); }; } var cat1 = new Cat('Kitty', 'black'); console.log(cat1.name); // Kitty console.log(cat1 instanceof Cat); // TRUE // 這時 cat1 實例會自動含有一個 `constructor` 屬性,指向它們的構(gòu)造函數(shù) `Cat`。 var cat2 = Cat('Smokey', 'white'); console.log(cat2); // undefined 

            3. 對象封裝 – Prototype 模式

            `prototype` 是 `Function` 對象的一個屬性,這個屬性指向另一個對象。 這個對象的所有屬性和方法,都會被構(gòu)造函數(shù)的實例繼承。

            同時 `prototype` 又存在一個指向構(gòu)造函數(shù)的引用 `constructor`,這樣就成功的構(gòu)成一個循環(huán)引用的原型鏈結(jié)構(gòu)。

            我們可以把那些不變的屬性和方法,直接定義在 `prototype` 對象上, 節(jié)省內(nèi)存開銷。

          function Cat(name, color) {     this.name = name;     this.color = color; } Cat.prototype.type = 'mammal'; Cat.prototype.eat = function() { console.log('eat fish'); }; var cat1 = new Cat('Kitty', 'white'); var cat2 = new Cat('Smokey', 'black'); console.log(cat1.type); // mammal console.log(cat1.eta === cat2.eta);     // TRUE, same reference console.log(cat1.constructor === Cat)   // TRUE, from Person.prototype 

            之二、繼承 (Inheritance)

            將持有共性特點的屬性或行為抽象出一個基本類, 可以按不同層次結(jié)構(gòu)的業(yè)務分組抽象出多個基礎類。

            Cat, Bird

            1. 繼承 – 構(gòu)造函數(shù)綁定

            使用call或apply方法,將父對象的構(gòu)造函數(shù)綁定在子對象上。

          function Animal() {     this.species = 'animal';     this.sleep = function() { console.log('I\'m sleep at night'); }; } function Cat(name, color) {     this.name = name;     this.color = color; } 

            讓`Cat` 繼承 `Animal` 的特性:

          /** @class Cat */ function Cat(name, color) {     Animal.apply(this);     this.name = name;     this.color = color; } var cat1 = new Cat('Kitty', 'white'); cat1.sleep(); // I am sleep at night 

            2. 繼承 – 原型鏈繼承

            如果”貓”的prototype對象,指向一個Animal的實例,那么所有”貓”的實例,就能繼承Animal了。

          /** @class Cat */ function Cat(name, color) {     this.name = name;     this.color = color; } Cat.prototype = new Animal; Cat.prototype.eta = function() { console.log('fish is my delicious'); }; 

            它相當于完全刪除了prototype 對象原先的值,然后賦予一個新值

          // 任何一個prototype對象都有一個constructor屬性,指向它的構(gòu)造函數(shù) Cat.prototype.constructor = Cat; // fix prototype chains var cat = new Cat('Kitty', 'fish'); cat.eat();      // fish is my delicious cat.sleep();    // I'm sleep at night' console.log(cat instanceof Cat);    // TRUE console.log(cat instanceof Animal); // TRUE 

            需要創(chuàng)建父類實列來實現(xiàn) `prototype` 繼承

            3. 繼承 (Inheritance) – 利用空對象作為中介實現(xiàn)原型繼承

          var F = function() {}; F.prototype = Animal.prototype; Cat.prototype = new F(); Cat.prototype.constructor = Cat; 

            我們將上面的方法,封裝成一個函數(shù),便于使用。

          function extend(ctor, superctor, px) {     if (!superctor || !ctor) throw Error('extend failed, verify dependencies');     var F = function() {};     F.prototype = superctor.prototype;     ctor.prototype = new F();     ctor.prototype.constructor = ctor;     ctor.superclass = superctor.prototype; // cache super class proto reference.     if (px) { // extend class implements         for (var k in px) {             if (px.hasOwnProperty(k)) ctor.prototype[k] = px[k];         }     }     return ctor; } 

            4 繼承 – 借住工具方法實現(xiàn)繼承

          /** @class Mammal */ extend(Cat, Animal, {     eat: function() {         Cat.superclass.eat.call(this); // call super method         console.log('Also i like some ofther food, such as beef and more.');     } }); var cat = new Cat('Smokey', 'fish'); cat.sleep(); cat.eat(); console.log(cat instanceof Animal); console.log(cat instanceof Cat); 

            之三、多態(tài)

            1. 多態(tài) – 通過重寫原型方法來實現(xiàn)方法重名調(diào)用

          /** @class Cat */ extend(Cat, Animal, {     eat: function() {         Cat.superclass.eat.call(this); // call super method         console.log('Also i like some ofther food, such as beef and more.');     } }); 

            2. 多態(tài) (Polymorphism) – 原型繼承 `prototype` 鏈上的方法、屬性查找

            【四】總結(jié) Summary

            Constructor

            Prototype

            Inheritance

          posted @ 2013-01-16 12:52 楊軍威 閱讀(349) | 評論 (0)編輯 收藏

          僅列出標題
          共43頁: First 上一頁 32 33 34 35 36 37 38 39 40 下一頁 Last 

          導航

          統(tǒng)計

          • 隨筆 - 391
          • 文章 - 34
          • 評論 - 20
          • 引用 - 0

          常用鏈接

          留言簿

          隨筆檔案

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 岗巴县| 西平县| 柳河县| 南阳市| 瑞安市| 巴青县| 甘肃省| 太谷县| 通城县| 竹溪县| 织金县| 乐山市| 景谷| 太谷县| 苏尼特左旗| 彰武县| 孝昌县| 德昌县| 吉安市| 湖口县| 达拉特旗| 新泰市| 伽师县| 阳山县| 宁都县| 大关县| 无锡市| 遵义市| 青冈县| 贺兰县| 衡山县| 汉源县| 和政县| 垦利县| 永昌县| 宜君县| 金川县| 新津县| 祁阳县| 修武县| 郸城县|