java学习

java学习

 

myeclipse自动提示配置

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

 

2.选择Java-------->展开-------->Editor-------->选择ContentAssist;

 

3.选择ContentAssist-------->然后看到右边-------->右边的Auto-Activation下面的Auto Activation triggers for java(指触发代码提示的就是”.”这个符号)这个选项;

4. AutoActivation triggers for java这个选项-------->在”.”后加
abcdefghijklmnopqrstuvwxyz
字母,方便后面的查找修改-------->然后apply-------->点击OK;

posted @ 2013-01-18 14:15 杨军威 阅读(103) | 评论 (0)编辑 收藏

struts2标签

     摘要: Struts2 Taglib抽象了不同表示技术,现在Struts2主要支持三种表示技术:JSP,FreeMarker和Velocity。但部分的Tag在三种表示技术下都可以使用,但是也有部分只能在某一种情况下使用。 Tab可以分为两类:通用标签和UI标签。 4.1节 通用标签 通用标签用来在页面表示的时候控制代码执行的过程,这些标签也允许从Action或者值堆栈中取得数据。例如地域,JavaBea...  阅读全文

posted @ 2013-01-18 13:38 杨军威 阅读(783) | 评论 (0)编辑 收藏

tomcat中配置数据库连接池

4.1连接池知识简介

众所周知建立数据库连接是一个非常耗时耗资源的行为,因此现代的Web中间件,无论是开源的Tomcat、Jboss还是商业的 websphere、weblogic都提供了数据库连接池功能,可以毫不夸张的说,数据库连接池性能的好坏,不同厂商对连接池有着不同的实现,本文只介 绍拜特公司使用较多的开源web中间件Tomcat中默认的连接池DBCP(DataBase connection pool)的使用。

4.2 Tomcat下配置连接池

下面以tomcat5.5.26为例来介绍如何配置连接池

1:需要的jar

在tomcat的安装目录common\lib下有一个naming-factory-dbcp.jar,这个是tomcat修改后的dbcp连接池实现,同时为了能够正常运行,还需要commons-pool.jar。

2:建立context文件

进入到conf\Catalina\localhost新建一个上下文文件,文件的名称既为将来要访问是输入url上下文名称,例如我们建立一个名为btweb的文件内容如下:

<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参数分步介绍

数据库连接相关

username="v10"

   password="v10"

driverClassName="oracle.jdbc.driver.OracleDriver"

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

jndi相关

name="jdbc/btdb1"

  type="javax.sql.DataSource"

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

factory默认是org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory,tomcat也允许采用其他连接实现,不过默认使用dbcp。

连接数控制与连接归还策略

    maxActive="5" 

maxIdle="3" 

minIdle=”2”

    maxWait="5000"

  应对网络不稳定的策略

testOnBorrow="true"

    validationQuery="selectcount(*) from bt_user"

应对连接泄漏的策略

    removeAbandoned="true"

removeAbandonedTimeout="60"

    logAbandoned="true"

  

如下图所示:连接池处于应用程序与数据库之间,一方面应用程序通过它来获取连接,归还连接,另一方面连接又需要从数据里获取连接,归还连接。

步骤1:系统启动

系统启动时,初始化连接池,由于没有任何请求连接池中连接数为0。

maxActive="5"

表示并发情况下最大可从连接池中获取的连接数。如果数据库不是单独,供一个应用使用,通过设置maxActive参数可以避免某个应用无限制的获取 连接对其他应用造成影响,如果一个数据库只是用来支持一个应用那么maxActive理论上可以设置成该数据库可以支撑的最大连接数。maxActive 只是表示通过连接池可以并发的获取的最大连接数。

从图上我们可以看到连接的获取与释放是双向,当应用程序并发请求连接池时,连接池就需要从数据库获取连接,那么但应用程序使用完连接并将连接归还给 连接池时,连接池是否也同时将连接归还给数据库呢?很显然答案是否定的,如果那样的话连接池就变得多此一举,不但不能提高性能,反而会降低性能,那么但应 用成归还连接后,连接池如何处理呢?

maxIdle="3"

如果在并发时达到了maxActive=5,那么连接池就必须从数据库中获取5个连接来供应用程序使用,当应用程序关闭连接后,由于maxIdle=3,因此并不是所有的连接都会归还给数据库,将会有3个连接保持在连接池种中,状态为空闲。

minIdle=”2”

最小默认情况下并不生效,它的含义是当连接池中的连接少有minIdle,系统监控线程将启动补充功能,一般情况下我们并不启动补充线程。

问题:如何设置maxActive和maxIdle?

理论上讲maxActive应该设置成应用的最大并发数,这样一来即便是在最大并发的情况下,应用依然能够从连接池中获取连接,但是困难时的是我们 很难准确估计到最大并发数,设置成最大并发数是一种最优的服务质量保证,事实上,如果某个用户登录提示系统繁忙,那么在他再次登录时,可能系统资源已经充 足,对于拜特资金管理系统我们建议将maxActive设置为系统注册人数的十分之一到二十分之一之间。例如系统的注册人数为1000,那么设置成50-100靠近100的数字,例如85或90。

maxIdle对应的连接,实际上是连接池保持的长连接,这也是连接池发挥优势的部分,理论上讲保持较多的长连接,在应用请求时可以更快的响应,但是过多的连接保持,反而会消耗数据库大量的资源,因此maxIdle也并不是越大越好,同上例我们建议将maxIdle设置成

50-100中靠近50的数字,例如55。这样就能在兼顾最大并发同时,保持较少的数据库连接,而且在绝大多情况,能够为应用程序提供最快的相应速度。

testOnBorrow="true"

validationQuery="selectcount(*) from bt_user"

我们知道数据库连接从本质上架构在tcp/ip连接之上,一般情况下web服务器与数据库服务器都不在同一台物理机器上,而是通过网络进行连接,那 么当建立数据库连接池的机器与数据库服务器自己出现网络异常时,保持在连接池中的连接将失效,不能够在次使用,传统的情况下只能通过重新启动,再次建立连 接,通过设置以上两个参数,但应用程序从连接池中获取连接时,会首先进行活动性检测,当获取的连接是活动的时候才会给应用程序使用,如果连接失效,连接将 释放该连接。validationQuery是一条测试语句,没有实际意义,现实中,一般用一条最为简单的查询语句充当。

removeAbandoned="true"

removeAbandonedTimeout="60"

logAbandoned="true"

有时粗心的程序编写者在从连接池中获取连接使用后忘记了连接的关闭,这样连池的连接就会逐渐达到maxActive直至连接池无法提供服务。现代连接池一般提供一种“智能”的检查,但设置了removeAbandoned="true"时,当连接池连接数到达(getNumIdle() < 2) and (getNumActive() > getMaxActive() - 3)时便会启动连接回收,那种活动时间超过removeAbandonedTimeout="60"的连接将会被回收,同时如果logAbandoned="true"设置为true,程序在回收连接的同时会打印日志。removeAbandoned是连接池的高级功能,理论上这中配置不应该出现在实际的生产环境,因为有时应用程序执行长事务,可能这种情况下,会被连接池误回收,该种配置一般在程序测试阶段,为了定位连接泄漏的具体代码位置,被开启,生产环境中连接的关闭应该靠程序自己保证。

posted @ 2013-01-18 13:27 杨军威 阅读(3456) | 评论 (0)编辑 收藏

strut2ognl

经过一段时间的闭关练习,终于对struts2有所了解.其实struts2并不难, 一看就能明白其中奥妙.我对struts2的验证体系保留怀疑态度,因为它的验证消息使用标签打在页面上,实在太丑,在真实项目中不知道是否有人这么做. 也许是我太菜了,还不知道如何将验证消息显示得更友好,希望各位不吝拍砖指导.
  然而,我认为struts2最复杂难学的是它内置的ognl表达式.这个ognl在我开始学struts2时,让我云里雾里,不知如何应对.经过几轮 的翻看书籍,与网上资料查询,还算是让我有所明白一点.在此记录,以便日后温习,同时,如果这篇文章对各位有哪怕一点帮助,那便是我最大的荣幸.
  首先,要知道ognl最主要的功能就是取数据,它可以利用一段简短的表达式取出各种各样丰富的数据.其次,它还附带了一些便捷的功能,如:方法调用、 静态方法和属性调用、数值运算……我们最关心的是如何取数据,因此,接下来我将重点介绍如何取数据,至于附带功能将不做介绍。
  知道了ognl最主要的功能是取数据后,那么数据从哪里取呢!ognl会从两个地方取:一个是Action的实例属性;另一个是 ValueStack(中文名叫值栈)。ognl会先从前者里面取,如果没取到再到ValueStack里取。Action的实例属性好理解,但这个 ValueStack从字面上看,着实不好理解,以致于我将struts2的源码引进eclipse里,单步调试才算有所启发。可以将 ValueStack初步理解为一个map,在这个map里存储了request、session、application、response、 action实例、parameters数组……还有很多你不知道的对象。有了这个map,还愁数据取不到吗。
  注意:将ValueStack初步理解为一个map,只适于初学struts2的人,其实它内部并没这么简单。由于水平、时间有限,我并不能掌握其内 部精髓,加上表达能力不佳,怕表达不对误导大家,所以我们姑且理解ValueStack为一个map吧。如果想更深的了解的ValueStack,请查看 struts2的源码。

  接下来,便是取数据。取action实例的属性数据与取ValueStack中的数据不一样,先说取action实例的属性数据吧。
  action实例的属性数据可以直接在struts2的标签中通过属性名取到。如:<s:property value="name"/>、<s:property value="user.password"/>
  注意:不要加#号。

  再是取ValueStack中的数据。
  struts2提供三种方式通过ognl表达式来取ValueStack中的数据:#、%{}、${}
  #和%{}需要放到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前缀, 加上反而取不到数据,ognl默认从request里取,如果没有取到并不会到session或application里取。 如:<s:property value="#name"/>
  2.#能取request里的请求参数,但必须加parameters前缀,且取到的是一个数组,所以如果你要得到参数的第一项值,那么还要加下标。 如:<s:property value="#parameters.name[0]"/>。这相当于调用 request.getParameterValues("name")[0];
  3.#加attr前缀能按request > session > application顺序获取attribute,这样当在request中取不到时,会自动向session里取,如果session里也取不到,会 再向application里取。如果取到则返回,不再向上游历。如:<s:property value="#attr.name"/>
  4.#能构造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标签的,我决不用其它标签。因为它是官方标准,还有它简单且已熟练,我已在众多项目中实战演练过,有了它们,我不想在使用其它标签。
  说到了这里,我还是有必要再多说两句,是不是使用了struts2,就不能再用el来取数据了呢?答案是否定的,完全可以使用el来取数据。 struts2会将ValueStack里的session、application里的attribute完全复制到HttpSession、 ServletContext里,这样el表达式照样能取到这两个Scope里的数据。然而,struts2并没有将ValueStack里的 request里的attribute复制到HttpServletRequest,这是不是意味着el表达式就不能取request里的数据了呢?还是 可以,不只可以取request里的数据,还可以取action实例的属性值。神奇吧!奥秘就在struts2对request做了封装,这个封装类是 org.apache.struts2.dispatcher.StrutsRequestWrapper,它重写了getAttribute()方法, 该方法先从真实的request类里取attribute,如果取到则返回,如果没有取到则从ValueStack里取,现在明白了吧!

posted @ 2013-01-18 13:20 杨军威 阅读(113) | 评论 (0)编辑 收藏

各种数据库默认端口

一 :Oracle

    驱动:oracle.jdbc.driver.OracleDriver
    URL:jdbc:oracle:thin:@<machine_name><:port>:dbname
    注:machine_name:数据库所在的机器的名称,如果是本机则是127.0.0.1或者是localhost,如果是远程连接,则是远程的IP地址;    

     port:端口号,默认是1521


二:SQL Server

    驱动:com.microsoft.jdbc.sqlserver.SQLServerDriver
    URL:jdbc:microsoft:sqlserver://<machine_name><:port>;DatabaseName=<dbname>
    注:machine_name:数据库所在的机器的名称,如果是本机则是127.0.0.1或者是localhost,如果是远程连接,则是远程的IP地址;          
     port:端口号,默认是1433

 

三:MySQL

    驱动:org.gjt.mm.mysql.Driver
    URL:jdbc:mysql://<machine_name><:port>/dbname
    注:machine_name:数据库所在的机器的名称,如果是本机则是127.0.0.1或者是localhost,如果是远程连接,则是远程的IP地址;          
     port:端口号,默认3306
  

        
 四:pointbase

    驱动:com.pointbase.jdbc.jdbcUniversalDriver
    URL:jdbc:pointbase:server://<machine_name><:port>/dbname
    注:machine_name:数据库所在的机器的名称,如果是本机则是127.0.0.1或者是localhost,如果是远程连接,则是远程的IP地址;
     port:端口号,默认是9092


 五:
DB2

    驱动:com.ibm.db2.jdbc.app.DB2Driver
    URL:jdbc:db2://<machine_name><:port>/dbname
    注:machine_name:数据库所在的机器的名称,如果是本机则是127.0.0.1或者是localhost,如果是远程连接,则是远程的IP地址;
     port:端口号,默认是5000

posted @ 2013-01-18 13:18 杨军威 阅读(7881) | 评论 (0)编辑 收藏

js函数

一、function概述

   javascript中的函数不同于其他的语言,每个函数都是作为一个对象被维护和运行的。通过函数对象的性质,可以很方便的将一个函数赋值给一个变量或者将函数作为参数传递。

   函数对象与其他用户所定义的对象有着本质的区别,这一类对象被称之为内部对象。内置对象的构造器是由JavaScript本身所定义的。

二、function对象的创建

在JavaScript中,函数对象对应的类型是Function,可以通过new Function()来创建一个函数对象,也可以通过function关键字来创建一个对象。

    //使用new Function()方式创建

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

    //使用function关键字创建

    function myFunction(a,b) {

      return a + b;

    }

   用关键字创建对象的时候,在解释器内部,就会自动构造一个Function对象,将函数作为一个内部的对象来存储和运行。从这里也可以看到,一个函数对象 名称(函数变量)和一个普通变量名称具有同样的规范,都可以通过变量名来引用这个变量,但是函数变量名后面可以跟上括号和参数列表来进行函数调用。

    new Function()的语法规范如下:

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

  参数的类型都是字符串,p1到pn表示所创建函数的参数名称列表,body表示所创建函数的函数体语句,funcName就是所创建函数的名称。可以不指定任何参数创建一个空函数,不指定funcName创建一个匿名函数。

  需要注意的是,p1到pn是参数名称的列表,即p1不仅能代表一个参数,它也可以是一个逗号隔开的参数列表,例如下面的定义是等价的:

  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")

   函数的本质是一个内部对象,由JavaScript解释器决定其运行方式。并且可直接在函数声明后面加上括号就表示创建完成后立即进行函数调用,例如:

    var i=function (a,b){

    return a+b;

  }(1,2);

  alert(i);

   这段代码会显示变量i的值等于3。i是表示返回的值,而不是创建的函数,因为括号“(”比等号“=”有更高的优先级。

三、匿名函数和有名函数的区别

   匿名函数必须先定义后调用,有名函数可以先调用,后定义。

A)匿名(这段语句将产生func未定义的错误)

   <script>

      func();

      var func = function() {

        alert(1);

      }

   < /script>

B)有名(输出1)

   <script>

      func();

       function func() {

        alert(1);

      }

   < /script>

这是因为JS解释器是分段分析执行的。并且,在同一段中,有名函数会优先被分析。并且同名函数后面的覆盖前面的。

而且,下面这段代码也是合法的:

<script>

function myfunc ()
    {
        alert("hello");
    };
    myfunc(); //这里调用myfunc,输出yeah而不是hello
  
    function myfunc ()
    {
        alert("yeah");
    };  
    myfunc(); //这里调用myfunc,输出yeah

</script>

如果要让上述代码的第一次调用输出“hello”,可以将它们分为两段:

<script>

function myfunc ()
    {
        alert("hello");
    };
    myfunc(); //这里调用myfunc,输出hello
< /script>

<script>
    function myfunc ()
    {
        alert("yeah");
    };  
    myfunc(); //这里调用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解释器分段分析执行的本质。

posted @ 2013-01-18 13:11 杨军威 阅读(197) | 评论 (0)编辑 收藏

java文件上传下载

JAVA的文件上传遍一直是一个比较关注的问题,而且有几个NB东西提供了这个功能.

用的最多的算是三个(我就知道这三个)比较强的,一个是比较早的jspsmartupload,另一个是出身名族的commonupload,还有一个就是orellay的了.

我用的比较多是前两个,总的感觉是jspsmartuplod比较灵活,功能上更强一些(一点点吧),但是现在网上也不维护,也不能下载了,特别是 它上传的时候把上传文件放到内存里,所以上传文件的大小会和内存有关系.commonupload虽然没有提供很多API,但是它有比较灵活,它上传的过 程中会把上传的文件先写入磁盘,所以上传的大小只是带宽有关系,我尝试最大的上传文件的大小是700M,当然是本地测试:>

还有是就是在Linux/Unix系统上传文件的中文问题,我在下面的代码有了一些解决.

下面是前两种方式的上传代码:

try{
//取session 用户oid
int pid = userInfo.getUserId();
String sys_user_id = String.valueOf(pid);
//取init配置文件的参数值
String sitePhysicalPath = (String)init.getObject("SitePhysicalPath");
String saveDir = (String)init.getObject("InfoUploadDir");
String tempDir = (String)init.getObject("InfoUploadDir");
String fileMemo = ""; //文件说明
String fileName = null; //存储到数据库的文件名
String saveName = null; //存储到本地的文件名
String filePath = null; //存储到数据库的文件路径
String savePath = null; //存储到本地的文件路径
long fileSize = 0; //文件大小
int maxPostSize = -1;
int dinfo_upload_id = -1;
%>
<%
//初始化
mySmartUpload.initialize(pageContext);
//上载文件
mySmartUpload.upload();
//循环取得所有上载文件
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);
//存储文件到数据库
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配置文件的参数值
 String sitePhysicalPath = (String)init.getObject("SitePhysicalPath");
 String saveDir  = (String)init.getObject("InfoUploadDir");
 String tempDir  = (String)init.getObject("InfoUploadDir");
 String fileMemo = "";    //文件说明
 String fileName = null;  //存储到数据库的文件名
 String saveName = null;  //存储到本地的文件名
 String filePath = null;  //存储到本地的文件路径
 String savePath = null;  //存储到数据库的文件路径
 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;
            /*/存储数据库
            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配置文件的参数值
 String sitePhysicalPath = (String)init.getObject("SitePhysicalPath");
 String saveDir  = (String)init.getObject("InfoUploadDir");
 String tempDir  = (String)init.getObject("InfoUploadDir");
 String fileMemo = "";    //文件说明
 String fileName = null;  //存储到数据库的文件名
 String saveName = null;  //存储到本地的文件名
 String filePath = null;  //存储到数据库的文件路径
 String savePath = null;  //存储到本地的文件路径
 long   fileSize = 0;     //文件大小
 int maxPostSize = -1;   
 int dinfo_upload_id = -1;
%>
<%
 //初始化
 mySmartUpload.initialize(pageContext);
 //上载文件
    mySmartUpload.upload();
 //循环取得所有上载文件
    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);
   //存储文件到数据库
   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 杨军威 阅读(1822) | 评论 (0)编辑 收藏

java处理xml方法

最初,XML 语言仅仅是意图用来作为 HTML 语言的替代品而出现的,但是随着该语言的不断发展和完善,人们越来越发现它所具有的优点:例如标记语言可扩展,严格的语法规定,可使用有意义的标记,内容 存储和表现分离等等优势注定了该语言从诞生之日起就会走向辉煌。 XML 语言在成为 W3C 标准之后进入到了一个快速发展的时期,当然它本身所具有的一系列优点和优势也注定了各大技术厂商对它的偏爱,Java 作为软件行业的一种开发技术也迅速作出了反应,出现了多种对 XML 支持的工具,本文将会从这个角度对 Java 处理 XML 的几种主流技术进行介绍,希望能对您有所帮助。在这篇文章中,您将会得到以下信息:
  1. Java 提供了哪些优秀的类库及工具便于程序员对 XML 进行处理 ?
  2. 有了 DOM 了,其它工具类库还有必要么 ?
  3. 几个小例程带你快速了解这三种解析方式

  Java 有哪些优秀的类库及工具便于程序员对 XML 进行处理 ?

  • 大名鼎鼎的 DOM
  • 绿色环保的 SAX
  • 默默无闻的 Digester

  XML 三种解析方式简介

  大名鼎鼎的 DOM

  说它大名鼎鼎可是一点不为过,DOM 是 W3C 处理 XML 的标准 API,它是许多其它与 XML 处理相关的标准的基础,不仅是 Java,其它诸如 Javascript,PHP,MS .NET 等等语言都实现了该标准, 成为了应用最为广泛的 XML 处理方式。当然,为了能提供更多更加强大的功能,Java 对于 DOM 直接扩展工具类有很多,比如很多 Java 程序员耳熟能详的 JDOM,DOM4J 等等, 它们基本上属于对 DOM 接口功能的扩充,保留了很多 DOM API 的特性,许多原本的 DOM 程序员甚至都没有任何障碍就熟练掌握了另外两者的使用,直观、易于操作的方式使它深受广大 Java 程序员的喜爱。

  绿色环保的 SAX

  SAX 的应运而生有它特殊的需要,为什么说它绿色环保呢,这是因为 SAX 使用了最少的系统资源和最快速的解析方式对 XML 处理提供了支持。 但随之而来繁琐的查找方式也给广大程序员带来许多困扰,常常令人头痛不已,同时它对 XPath 查询功能的支持,令人们对它又爱又恨。

  默默无闻的 Digester:XML 的 JavaBean 化

  Digester 是 apache 基金组织下的一个开源项目,笔者对它的了解源于对 Struts 框架的研究,是否有很多程序员想要一解各大开源框架的设计甚至想要自己写一个功能强大的框架时会碰到这样一个难题: 这些形形色色的用 XML 语言标记的框架配置文件,框架底层是用什么技术来解析呢? DOM 解析耗费时间,SAX 解析又过于繁琐,况且每次解析系统开销也会过大, 于是,大家想到需要用与 XML 结构相对应的 JavaBean 来装载这些信息,由此 Digester 应运而生。它的出现为 XML 转换为 JavaBean 对象的需求带来了方便的操作接口,使得更多的类似需求得到了比较完美的解决方法, 不再需要程序员自己实现此类繁琐的解析程序了。与此同时 SUN 也推出了 XML 和 JavaBean 转换工具类 JAXB,有兴趣的读者可以自行了解。

  三种解析方式比较

  DOM

  优缺点:实现 W3C 标准,有多种编程语言支持这种解析方式,并且这种方法本身操作上简单快捷,十分易于初学者掌握。其处理方式是将 XML 整个作为类似树结构的方式读入内存中以便操作及解析,因此支持应用程序对 XML 数据的内容和结构进行修改,但是同时由于其需要在处理开始时将整个 XML 文件读入到内存中去进行分析,因此其在解析大数据量的 XML 文件时会遇到类似于内存泄露以及程序崩溃的风险,请对这点多加注意。

  适用范围:小型 XML 文件解析、需要全解析或者大部分解析 XML、需要修改 XML 树内容以生成自己的对象模型

  SAX

  SAX 从根本上解决了 DOM 在解析 XML 文档时产生的占用大量资源的问题。其实现是通过类似于流解析的技术,通读整个 XML 文档树,通过事件处理器来响应程序员对于 XML 数据解析的需求。由于其不需要将整个 XML 文档读入内存当中,它对系统资源的节省是十分显而易见的,它在一些需要处理大型 XML 文档以及性能要求较高的场合有起了十分重要的作用。支持 XPath 查询的 SAX 使得开发人员更加灵活,处理起 XML 来更加的得心应手。但是同时,其仍然有一些不足之处也困扰广大的开发人员:首先是它十分复杂的 API 接口令人望而生畏,其次由于其是属于类似流解析的文件扫描方式,因此不支持应用程序对于 XML 树内容结构等的修改,可能会有不便之处。

  适用范围:大型 XML 文件解析、只需要部分解析或者只想取得部分 XML 树内容、有 XPath 查询需求、有自己生成特定 XML 树对象模型的需求

  Digester/JAXB

  优缺点 : 由于其是在上述两者的基础上衍生出来的工具类,为的是满足将 XML 转换为 JavaBean 的特殊需求,故而没有什么特别明显的优缺点。作为大名鼎鼎的开源框架 Struts 的 XML 解析工具 Digester,为我们带来了将 XML 转换为 JavaBean 的可靠方法。

  适用范围 : 有将 XML 文档直接转换为 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 规范,其中 org.w3c.dom 包提供了 Document、DocumentType、Node、NodeList、Element 等接口,这些接口均是访问 DOM 文档所必须的。我们可以利用这些接口创建、遍历、修改 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 对象之后,我们可以调用一系列的 API 方便的对文档对象模型中的元素进行访问和处理。 需要注意的是调用 Element 对象的 getChildNodes() 方法时将返回其下所有的子节点,其中包括空白节点,因此需要在处理子 Element 之前对节点类型加以判断。

  可以看出 DOM 解析 XML 易于开发,只需要通过解析器建立起 XML 对应的 DOM 树型结构后便可以方便的使用 API 对节点进行访问和处理,支持节点的删除和修改等。 但是 DOM 解析 XML 文件时会将整个 XML 文件的内容解析成树型结构存放在内存中,因此不适合用 DOM 解析很大的 XML 文件。

  SAX 解析 XML

  与 DOM 建立树形结构的方式不同,SAX 采用事件模型来解析 XML 文档,是解析 XML 文档的一种更快速、更轻量的方法。 利用 SAX 可以对 XML 文档进行有选择的解析和访问,而不必像 DOM 那样加载整个文档,因此它对内存的要求较低。 但 SAX 对 XML 文档的解析为一次性读取,不创建任何文档对象,很难同时访问文档中的多处数据。

  下面是一个 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 验证和实体解析相关的事件; ErrorHandler 是基本的错误处理接口。DefaultHandler 类实现了上述四个事件处理接口。上面的例子中 BookHandler 继承了 DefaultHandler 类, 并覆盖了其中的五个回调方法 startDocument()、endDocument()、startElement()、endElement() 及 characters() 以加入自己的事件处理逻辑。

  Digester 解析 XML

  为了满足将 XML 转换为 JavaBean 的特殊需求,Apache 旗下的一个名为 Digester 的工具为我们提供了这么一个选择。由于最终是将 XML 转化为 JavaBean 存储在内存当中, 故而解析性能等方面其实与使用者并没有多大关系。解析的关键在于用以匹配 XML 的模式以及规则等,由于该工具较为复杂,限于篇幅,作者只能给予简单的介绍。

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

 // 定义要解析的 XML 的路径,并初始化工具类  File input = new File("books.xml");   Digester digester = new Digester();   // 如果碰到了 <books> 这个标签,应该初始化 test.myBean.Books 这个 JavaBean 并填装相关内容  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");   // 通过调用上面已经初始化过的 JavaBean 的 addBook() 方法来把多个 <books/book> 加到一个集合中  digester.addSetNext("books/book", "addBook", "test.myBean.Book");   // 定义好了上面的解析规则后,就可以开始进行解析工作了  Books books = (Books) digester.parse(input); 

  上述代码简单的向读者展示了 Digester 处理 XML 的一些要点,主要是说明了一些模式以及规则的匹配。 简言之,Digester 就是一种用来把一个 XML 转化为一个与该 XML 结构类似的 JavaBean。你可以把 XML 根元素想象成一个 JavaBean, 该根元素的 attribute 就是这个 JavaBean 的各种 Field,当该根元素有其他子 tag 时,又要把这个子 tag 想象成一个个新的 XML,将其视为一个新的 JavaBean, 并作为一个 Field 加入到父 Bean 当中,然后以此类推,通过循环的方式将整个 XML 进行解析。

  结束语

  本文介绍了 Java 解析 XML 的三种常用技术,其中 DOM 易于上手,程序易于理解,但缺点在于占用内存大,不适合于解析较大的 XML 文件; SAX 基于事件模型占用系统资源少,能够胜任较大的 XML 文件解析,但解析过程较为繁琐查找元素不方便; Digester/JAXB 基于上述两种技术衍生而来。文中的实例向读者展示了三种 API 的基本使用方法, 在实际开发过程中使用那种技术解析 XML 更好要依据各自的优缺点视具体情况而定。

posted @ 2013-01-16 14:09 杨军威 阅读(589) | 评论 (0)编辑 收藏

前段开发10点

 第一日:初尝禁果

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

  万物生灵、阳光雨露盖源于造物之初的天工开物,我们无法想象上帝创造光明之前的世界模样。但幸运的是,前端开发没有神祗般的诡魅。这个技术工种 的孕育、定型、发展自有轨迹,也颇有渊源,当然,这非常容易理解。不严格的讲,在杨致远和费罗在斯坦福大学的机房里撺掇出Yahoo!时,Web前端技术 就已经开始进入公众视野,只不过当时没有一个响亮的名字。从那时起,“基于浏览器端的开发”就成了软件开发的新的分支,这也是Web前端技术的核心,即不 论何时何地何种系统以及怎样的设备,但凡基于浏览器,都是Web前端开发的范畴(当然,这个定义很狭隘,下文会提到)。

  在2000年之后浏览器技术渐渐成熟,Web产品也越来越丰富,中国有大批年轻人开始接触互联网,有一点需要注意,大部分人接触互联网不是始于 对浏览器功能的好奇,而是被浏览器窗口内的丰富内容所吸引,我们的思维模式从一开始就被限制在一个小窗口之内,以至于很长时间内我们将“视觉”认为是一种 “功能”,Web产品无非是用来展现信息之用。起初的入行者无一例外对“视觉”的关注超过了对“内容”的重视,先让页面看起来漂亮,去关注 html/css,沿着“视觉呈现”的思路,继续深入下去。因此,这类人是被“视觉”所吸引,从切页面入行,着迷于结构化的html和书写工整的css, 喜欢简洁优雅的UI和工整的页面设计,之后开始接触视觉特效,并使用jQuery来实现视觉特效,以此为线索,开始深入研究Dom、Bom和浏览器的渲染 机制等,html/css在这些人手中就像进攻兵器,而JavaScript则更如防守的盾牌。

  还有另外一群人从另一条道路接触Web前端,即工程师转行做前端,他们有较多的后台语言开发背景,从读写数据开始,渐渐触及浏览器端,接触 JavaScript库,起初是在html代码上加js逻辑,后来开始涉及html和css,他们喜欢OO、逻辑清晰、结构悦目的代码,更关注界面背后的 “程序语言”和数据逻辑。html/css在这些人手中则更像盾牌,而JavaScript更如进攻的兵器。

  应当说这两类人是互补的,他们各自了解浏览器本质的一部分,一拨人对渲染引擎了如指掌,另一拨人则将JS引擎奉为至宝,其实任何一部分的优势发 挥出来都能做出精品。大部分前端工程师都能从这两条渊源中找到自己的影子。但,这两类人的思维模式和观点是如此不同,以至于形成了一些不必要的对抗,比如 在某些公司,干脆将Web前端技术一分为二,“切页面的”和“写js的”。这样做看上去明确了分工提高了效率,但他对员工的职业发展带来巨大伤害。在第二 日“科班秀才”中会有进一步讨论。

  我应该属于第二类,即在学校正儿八经的学习C/Java和C#之类,以为大学毕业后能去做ERP软件、桌面软件或者进某些通信公司写TCP /IP相关的程序。校园招聘时选择了中国雅虎,因为当年(08年)雅虎还是有一点儿名气,而且我听说雅虎比较算技术流的公司……自此就上了贼船,一发不可 收拾。

  在雅虎的这段时间,我有幸接触到一股正气凛然的技术流派,也形成了我对前端技术的一些基本看法,这些基本观点一直影响我至今。

  【优雅的学院派】

  当年雅虎的技术流派正如日中天,拥有众多“之父”级的高人,所营造出的Hack氛围实在让人陶醉的无法自拔,那段时间我甚至宁愿加班到深夜阅读 海量的文档和源代码,感觉真的很舒服,我深深的被雅虎工程师这种低调务实、精工细琢的“服务精神”所打动,而这种不起眼的优秀品质很大程度的影响雅虎产品 的用户体验和高质量的技术输出。那么,何谓“服务精神”?即你所做的东西是服务于人的,要么是产品客户、要么是接手你项目的人、要么是使用你开发的功能的 人,所以技术文档成为伴随代码的标配。因此,工程师之间通过代码就能做到心有灵犀的沟通。这是工程师的一项基本素质,即,思路清晰的完成项目,且配备了有 价值的技术文档,如果你的程序是给其他程序员用的,则更要如此,就好比你制造一款家电都要配备说明书一样。因此,YDN成了当时最受全球程序员最喜爱的技 术文档库,这种优雅务实的“学院气息”让人感觉独具魅力。

  让人感觉奇怪的是,在中文社区始终未见这种学院派。甚至在具有先天开源优势的Web前端技术社区里也是波澜不惊,可见写一篇好的技术文案真的比 登天还难。我所见到的大部分所谓文档索性把代码里输出数据的语句块拷贝粘贴出来,至于为什么数据格式要设计成这样、如果字段有修改怎么做、编码解码要求如 何等等关键信息只字不提,或者开发者也没想过这些问题呢。因此,我们一直在强调代码的质量和可维护性,但一直以来都未见效,盖源于缺少这种“服务”意识的 灌输。这种意识在下文中还会多次提到,因为它能影响你做事的每个细节,是最应当首先突破的思想纠结。

  除了意识问题,另一方面是技术问题,即文笔。这也是工程师最瞧不上眼的问题,难以置信这竟然是阻碍工程师突破瓶颈的关键所在。我已看到过数不清 的人在晋升这道关卡吃了大亏,很多工程师技术实力很强,但就是表达不出来,要么罗列一大堆信息毫无重点、要么毫无趣味的讲代码细节,不知云云。除非你走狗 屎运碰到一个懂技术的老板,否则真的没办法逃脱码农的宿命。但大部分人还振振有词不以为然。而在Web前端开发领域情况更甚。前端工程师是最喜欢搞重构 的,但在快节奏的需求面前,你很难用“提高了可维护性”、“提升了性能”这类虚无缥缈的词藻为自己争取到时间来搞重构,说的露骨一点,可能你真的对某次重 构带来的实际价值无法量化,只是“感觉代码更整洁了”而已。我会在下文的“伪架构”中会展开分析前端工程师的这种浮躁献媚的技术情结。而这正是前端工程师 最欠缺的素质之一:用数据说话,用严谨科学的论据来支撑你的观点,老板不傻,有价值的东西当然会让你去做。

  当然,情况不总是这么糟糕,我们看到中文社区中已经锻炼出了很多写手,他们在用高质量的文字推销自己的技术理念,这是一个好兆头,好的文笔是可 以锻炼出来的。而在职场,特别是对前端工程师这个特殊职位来讲,这种基本技能可以帮你反思梳理需求的轻重缓急,从凌乱的需求中把握七寸所在。因为当你开始 认真写一封邮件的时候,这种思考已经包含其中了。

  所以,雅虎技术的推销是相对成功和远播的。关键在于两方面,扎实的技术功底和高超的写手。而真正的技术大牛一定是集两者与一身,不仅钻研剑道,还能产出秘籍。这也是Yahoo!优雅的学院派气息的动力源泉。国内很多技术团体想在这方面有所建树,应当首先想清楚这一点。

  【规范的破与立 1】

  雅虎的技术运作非常规范,刚才已经提到,包括技术、组织、文化,一切看起来有模有样,也堪称标杆,自然成了国内很多技术团队和社区的效仿对象。一时间各种“规范“成风、各色“标准“大行其道,结果是质量参差不齐。

  我们到底需要什么样的规范?雅虎的技术规范到底有何种魔力?以何种思路构建的规范才是货真价实的?规范有着怎样的生命周期?想清楚这些问题,能很大程度减轻很多Web前端工程师的思想负担,看清一部分技术本质,避免盲目跟风。

  我们的确需要规范,但好的规范一定是务实的,一定是“解决问题“的。比如针对项目构建的DPL可以收纳公用的视觉元件以减少重复开发、规定某 OPOA项目的事件分发原则以确立增量开发的代码惯性。反之,糟糕的规范却显得过于“抽象“,比如页面性能指标、响应式设计原则。另外,尽管他山之石可以 攻玉,但拿来主义有一个大前提,就是你了解你的项目的关键问题,你要优先解决的是些关键问题,而外来规范正好能解决你的问题。因此规范是一本案头手册,是 一揽子问题的解决方案,应当是“字典”,而不是“教程“。可见规范的源头是“问题”。所以,当你想用CoffeeScript重构你的项目时、当你想引入 CommonJS规范时、当你想在页面中揉进Bootstrap时、当你打算重复造轮子搞一套JS库时、当你想重写一套assets打包工具时,想想这些 东东解决了你的什么问题?会不会带来新的问题、把事情搞复杂了?还是为了尝鲜?或者为了在简历中堂而皇之的写上使用并精通各种新技术?

  规范之立应当有动因,动因来源于项目需求,项目需求则来自对产品的理解和把握,这是Web前端初级工程师走向中级甚至高级的一次重要蜕变,软件 工程领域早就有“架构师”角色,而架构师往往存在于项目需求分析和概设、详设阶段。我看到的情况是,Web前端工程师的思维过多的限制在“界面”之内,向 前和产品需求离的太远(认为这是视觉设计师的事)、向后和数据逻辑又隔离开来(认为这是后台工程师该干的事),因此前端规范也大都泛泛,无关项目痛痒,成 了玩具。

  雅虎技术规范的优秀之初在于它们解决问题。所以,学习使用规范应当多问一句,“他们为什么这样做?”其实,想清楚这些问题时,脑海中自然形成了一种“遇山开山”的创造性思维。

  【规范的破与立 2】

  如果说新技术的尝鲜缺少针对性,但至少满足程序员的某种洁癖和快感,那么“负担”从何而来呢?对于初学者来说,有价值学习资料可能只有这些规范,如果说规范价值不大,那又当从何入手呢?

  刚才我说的不是依赖于规范,而是对规范的反思,摆脱规范灌输给我们的思维定势。新人们大概是看了Wiki中的很多指标、结论、实践,在做项目之 初就附加了不少“八股式”的负担,甚至影响我们对项目关键需求和关键问题的洞察力和判断力,负担过重就无法轻装上阵,Wiki中提到的这些指标和规范是结 论性的,是大量的实践之后得出的,也只有经历过大量实践才会真正理解这些结论,比如DomReady时间和http请求数是否有因果关系,http请求数 增加是否真的会导致页面性能下降,什么条件下会导致性能下降?我们从那些条文和结论中无法找到答案。

  举个具体的例子,Kissy刚刚出了DPL,也是一大堆结论,比如他的布局就采用了经典的双飞翼,使用容器浮动来实现,那么,这种做法就是不可 撼动的“标准”吗?看看淘宝车险首页,布局容器齐刷刷的inline-block,只要顶层容器去掉宽度,布局容器自身就能根据浏览器宽度调整自然水平/ 垂直排列,轻易的适应终端宽度了。

  再比如,淘宝旅行计划项目中的部署方式,也没有完全使用Loader管理依赖,而是将依赖层级做的很少,业务逻辑使用脚本来合并,这样就可以更容易在build环节加入语法检查和代码风格检查。

  类似这种摆脱原有编程思维,有针对性的用新思路新方法解决问题的做法显然让人感觉更加清爽,编程的乐趣也正体现在打破常规的快感之中,小马曾经 说过:“制造规范是为了打破规范”,万不要因为这些规范标准加重负担,导致开始做一个简单页面时也显得缩手缩脚,无法放开身手。大胆的动手实践,才能真正 得出属于自己的“结论 “和“标准“,才会真正深刻理解那些“结论”的意义所在。代码写的多了,自然熟能生巧,也容易形成成熟的技术观点。

  在这个过程中,我们唯一的对手是懒惰,惰于思考,就无法真正发现问题,自然形不成自己的观点。还是那句话,任何规范、方法、结论、实践都是为了 解决项目中的问题的,所以,我们所接触到那些看似“八股文”式的规范标准也是为了解决某些问题而提出的,想清楚这些问题,理解方法论背后的“因“,内心自 然有“果”。

  因此,“着眼当下、对症下药”的品质就显得弥足珍贵了,比如,双飞翼布局方法是为了解决一套(html)代码适应多种布局设计,这里的布局相对 于固定的产品来说也是固定的,而无针对终端的自适应(适用于移动端的榻榻米布局似乎还没有最佳实践)。这是双飞翼产生的背景,如今终端环境较之5年前已经 翻天覆地,问题早已不在“多种布局”上,而在“终端适应“上,这才是我们面临的问题,需要我们给出新的技术方案。

  所以,勤于思考,轻装上阵,大胆实践,勇于创新,发掘问题所在,实打实的解决(潜在)问题,这才是我们真正需要的能力。放下思维定势枷锁,也会有一种豁然开朗的感觉。

  第二日:科班秀才

  【秀才仕途】

  Web前端工程师是一个特别的岗位,只存在于互联网领域。最近几年随着互联网产业的火爆,对前端工程师的需求量暴增,兵源几近枯竭。各大公司技术掌门一定都有过类似的苦恼:“招一个靠谱的前端工程师、难于上青天”。

  我想,一部分原因是,当前不少入道的前端工程师大都是转行而来,毕竟,正儿八经的学校里也不会教这玩意,觉得“切页面”有啥好教的,甚至不觉得 html/css是一门语言。转行这事自不必详说,大家也各自瞄准当前市场需求,造成的现象是,初级前端工程师堆成山,中高级人才却一将难求,计算机系的 科班出身就更加凤毛麟角了。一方面反映了教育部门的后知后觉,另一方面也体现了大部分人急功近利的跟风。当然最重要的原因是,所谓中国“第一代前端工程 师”并未做好布道的工作。导致大家对于基础和潜力的态度从之前的忽视演变为如今的蔑视。所谓基础,就是在大学上的那些计算机基础课。所谓潜力,就是戒骄戒 躁的务实作风。这些会在后文中多次提到。

  对于科班出身的莘莘学苗来说,根正苗红本身就是一种优势,事实证明,这些人在前端技术上的成长轨迹有一定的套路,而且大都能如期的突破技能瓶颈。从一个人大学毕业到他最满意的工作状态,中间会经过几个阶段。

  前2年是学习技能的阶段,这个阶段主要精力放在专业技能的提升上,2年内起码要赶上平均水平,即所谓“中级“,在这个阶段的人通常对软技能不怎 么关注,沟通能力达不到平均水平,基本上是来啥活干啥活,干不完就加班的这种,对需求的合理性不甚理解,对项目也没什么把控,尽管在技能上有提高的空间, 也不是公司最需要的人,但有不少成长空间。

  工作2-3年的人在前端技能上趋于稳定,也就是技能上的第一次瓶颈,这种人干活熟练,切页面可能也很快,代码看上去也比较规范,属于熟练工,开 始注重沟通技巧和一些职业技能的积累,比如带人带项目,至少有这方面的意识,并有过推动项目、和业务方pk需求的经历,这就达到了中级应当具备的职业技 能,但应当注意的是,这时最容易出现偏科的情况,特别是对于那些“专门切页面的“和“专门写脚本的“人,毕竟html/css/js三者不分彼此,三者是 一个合格前端工程师都必须要掌握的。如果你觉察到自身有偏废的嫌疑,则要小心了,要清楚的了解自身的差距,并意识到瓶颈的存在,为过渡到“中级“的打下基 础。

  过了这道坎之后,工作3年以上的人大部分技能也趋稳,有些人对前端新技术有钻研,能够熟练应对日常工作,软技能也ok,具备有针对性的“拿来主 义“,代码也具有一定的架构性,开始突破“代码民工”的这一层瓶颈,对团队气氛、培训、工作环境有个性化的要求,一般来讲,这种人是典型的具有潜力的“中 级”工程师,但很快会遇到职业发展中的第二个技术瓶颈。

  有少数工作3年或4年以上,在不断寻求新的技能上的突破,最明显的一点体现是,开始关注“底层协议”,即HTTP、第三方应用、系统对接、制造 工具、工作流程等,这时思考的重点已经脱离了“切页面”,变为“出方案“,比如要架设一个站点,能够搭建站点框架,预见站点后续(前端)开发中的所有风 险,并一一给出解决方案。项目后续开发遇到问题只要翻阅你提供的“手册”即能找到答案。这种人是标准的“高级”Web前端工程师。

  出方案是一件挺难的事情,它要求一个工程师同时具备经验、技术、气场等诸多硬技能。尤其是对技术底子的要求非常高。

  【半路出家】

  那么,转行做前端的人又当如何呢?其实发展轨迹和科班秀才们非常类似,只是时间跨度可能会长一些,你要花更多的精力、做更多的项目、更多的反思和总结才能理解某个知识点的本质(比如HTTP协议)。当然这只是一般情况。

  此外,这些人还需要摆脱很多思维定势的禁锢。这里我推荐大家阅读阿当的《Web前端开发修炼之道》。当然,如果你有一个靠谱的师兄带你入道,自然幸运万倍。

  但不管怎样,我始终认为应当秉承兴趣第一的原则,不管你是误打误撞、还是意欲为之,不管你是科班秀才、还是半路出家,兴趣始终应当是第一原则, 然后才是你“想做好“。我对自己的要求无法强加于人,所以很多业界大牛在回顾自己成功之路时,提到最多的是:“热爱你的工作、拥抱它给你带来的挑战”。 N.C.Zakas曾经这样勉励大家:

  “我对Web开发人员最大的建议就是:热爱你的工作。热爱跨浏览器开发带来的挑战、热爱互联网技术的种种异端,热爱业内的同行,热爱你的 工 具。互联网发展太快了,如果你不热爱它的话,不可能跟上它的步伐。这意味着你必须多阅读,多动手,保证自己的才能与日俱增。下了班也不能闲着,要做一 些对自己有用的 事儿。可以参与一些开源软件的开发,读读好书,看看牛人的博客。经常参加一些会议,看看别人都在干什么。要想让自己快速成长,有很多事儿 可以去做,而且付出一定会有回报。“

  第三日,幸福感

  【先精通十行?!】

  兴趣第一,听上去很美,但现实却不总是这么酷。练就了一身本领,那也要找到对口的怪物来打一打才过瘾。

  自然,每个人都想做出好东西,每个工程师也都渴求这样的机遇,用层次分明的设计、漂亮优雅的代码、精妙的细节雕琢,做出美观、安全、实用耐用的 产品,不过现实是如此残酷,以至于工程师们一直都缺乏对产品的归属感。作为前端工程师,如何才能在江湖中把握住前进方向、步步走高?毕竟,在职位繁杂的大 公司,缺乏人性化的工作流程影响着工程师的工作幸福感。产品从设计之初、到技术方案评审、再到实现,处处充满了妥协,大部分产品都是杂交的产物,人与人相 互掣肘,每个人都对产品不满意……,大跃进式的敏捷开发早就被证明百害无一利。但,或许这就是成长的代价。年轻的工程师需要更多的了解需求和设计、产品经 理更要懂得软件迭代规律。对于前端工程师来讲更是如此,多学习交互设计和UI,多了解网络协议和软件迭代模型,更能帮助前端工程师和需求方沟通、和后台的 衔接、以及控制版本的迭代。

  说来奇怪,前端工程师不是写html/css/js的吗,搞懂那些边缘知识有什么用?《Web前端开发修炼之道》中也提到,精通一行需要先精通十行。这里我来解释一下原因。

  作为交互设计师的下游,前端工程师学需要习设计知识是很容易理解的,因为它能帮助你更准确的理解设计师的意图,在原型不完整的时候也能正确的反 馈设计缺陷,将问题阻挡在设计的环节,会大大减少UI bug数量,比如说,设计师会给出理想状态下的容器样式,却往往忽略了文字溢出折行、长连续字符、 容器宽高是否适应内容尺寸变化而变化,溢出部分是作截字还是隐藏等诸多细节,因为设计师不懂“边界值测试”的道理,而这些问题往往在测试阶段才被发现,所 以,如果能在拿到UI设计稿时就提醒设计师补充完整这些场景,自然减少测试回归次数。

  另外,前端工程师必须要了解网络协议,原因很简单,我们做的产品运行在Web上。很多依赖于Ajax的实现,只有前端工程师才会提出实现方案, 产品经理不了解技术瓶颈,后台工程师更不会在意客户端的用户体验,举个简单的例子:通过JS实现一个Ajax,如果Ajax抓取的数据源是一个302跳 转,则需要在JS程序中多做一些事情,这就需要前端工程师了解一些HTTP协议。应当说,这是很常见的一个场景。

  那么,为什么说前端工程师也要关注代码版本控制呢?因为web开发和软件开发本质无异,同样具有迭代周期,需求不是一揽子提完、一口气开发完 的,是有步骤的开发,因此,每次上线开发哪些功能、为后续扩展功能留足哪些接口、代码在可扩展和可维护性上应当作哪些考虑……,这些应当是每个工程师关注 的事情,所谓迭代就是指这种需求的叠加,这是软件开发的常态,也是web开发的常态,刚开始,前端工程师总会不断抱怨没完没了的需求,代码起初还算干净, 但很快就越来越乱,代码的版本管理对于Web前端工程师来说有些困难,这也使得大部分前端工程师很难上档次,从这个角度讲,前端工程师是需要向后台工程师 学习的,他们的开发量不比前端少,维护代码的能力要超过前端工程师。另外,对于刚入行的前端工程师,心态要放对,提需求是产品经理的职责所在,整理出有价 值的需求是交互设计师的职责所在,将需求作版本控制分步实现是前端工程师的职责所在,前端工程师没必要去抱怨产品经理提一大堆没规律的需求,而更应当去理 解需求缘由,将需求提炼成UC(用例),让需求在自己手中可控制。只是多数前端工程师缺乏提炼、整理需求的能力,一味的在接需求,才会搞的手忙脚乱,带着 情绪堆代码。

  所以,只有练就了一身本领,才会更有目标的去寻找对产品的责任感和对团队的归属感,不要误以为能切出漂亮的页面就是能力的提高,纯粹的写代码每 个人都差不多的,要成为合格的工程师,眼界要进一步放开,前端工程师能做的,不仅仅是切页面而已,作一个精品项目,一定不乏专业的过程把控,这也是大多数 人最易忽略的地方。

  【励志之本】

  其实,除了个人需要明确努力的方向,每个人都更渴望身处一个好团队,谁都不希望有猪一样的队友。我们都很羡慕处身这样的团队,可以放心的将精力 放在纯粹的技术上,身边每个人都自觉的补充文档注释,代码也层次清晰解偶充分重用率高,精妙的设计实现可以更快的传播,bug得到的改进建议也是务实专业 的,技术在这种良性互动中价值倍增。我想这也算是好团队的一种境界了,这有赖于团队成员水平水涨船高。不过,反观Yahoo的成长之路,他们的技术积淀也 是靠点滴的积累,其实他们当初的状况不比现在的我们好哪去,10年的进化,才造就了Yahoo技术团队的专业性和Hack精神,我们每个人才刚刚起步而 已。为了积攒工作中的幸福感,多付出一些是值得的。

  但我猜,你现在的处境一定不会太过乐观,产品乱提需求、一句话的PRD、不被重视,被生硬的当作“资源“……反正,情况就是这么个情况,要么你 选择抱怨下去,要么想办法去改变。“积极主动“是源自内心的一种坚韧品质,也是励志之本,有些人在现实中被磨平了理想,有些人却在黑暗森林中找到了方向, 这就是犬儒主义和英雄气概之间的差别。这自不必详说,因为这让我想起了“大长今”,这简直就是前端工程师的励志典范:“这是一个可怕的环境,足以消磨任何 人的斗志和信念,所有来这里的人都变得麻木和无所作为,‘多栽轩‘恶劣的环境没有改变长今,但长今却改变了‘多栽轩‘所有的人“。

  如果你想做到“资深”,就一定要想清楚这一点,因为你是团队的顶梁柱(业务),也是幸福感的源头(士气)。

  第四日,架构和伪架构

  【代码设计的本质】

  读到这里,你不禁会问,前端领域存在“架构师”吗?这个问题会在后面的“码农的宿命”中展开解释。这里先说下代码架构的一些琐事吧。

  什么是架构?架构是由“架”和“构”组成,架,即元件,构,即连接件。因此,架构即是将总体分解为单元,然后定义单元之间的连接方式。架构的含 义源自禅宗,而禅宗的基本信条则之一就是真理是无法用语言来描述的。这个基本信条有其背景,即语言具有某种抽象性。而人们对这种抽象性的悟道则直接影响对 事物的看法,进而决定了对客观世界的分解方法。

  而在编程语言中,同样存在这种禅宗所隐喻的悖论。在面向对象的教科书中,通常举一些显而易见的例子,比如“水果”是一个类,包含有苹果、桔子、 香蕉等实例,“蔬菜”也是一个类,包含白菜、冬瓜、茄子等实例。这两个类之间并无交集,因此很容易理解。但实际项目中情况要复杂的多,比如两个图书类目 “文学”和“历史”,那么“明朝那些事”应当是“文学”类的实例还是“历史”类的实例呢?即一旦用语言说出了某一事物,即人为的割裂了世界,于是就会陷入 迷途。这在程序设计领域情况更甚,也是造成混乱的主要根源,也就是说,如果你的程序可扩展性不好,一定是程序作者对“单元”的定义不够准确,即单元的概念 之间不够“正交”。而这种架构终是徒有其形,根基不稳。

  因此,变量和类的命名才是真正考验架构功力的关键(命名是否准确清晰、单元之间是否有概念重叠或盲区),而和所谓“组合”、“继承”、“桥接”等模式化的“外表”无本质联系。

  【伪架构】

  实际情况是,程序员早早的就想让自己和“架构”扯上关系,并自封xx架构师。在项目中应用各种模式分层、解耦方法,每个项目都可以产出一套看上 去很复杂的“架构图”,感觉很牛逼的样子,没错,实践这些方法论总不是坏事,但世界观才是方法论的基础,只有在概念上对产品模块有科学的定义,方法论便自 然形成了,《编程珠玑》中一再提及数据结构就是静态的算法,在Web前端领域亦是如此,在页面的建模过程中,定义分解维度要比分解方法更加基础和重要。我 想阿当可以在《Web前端开发修炼之道》的第二版里加上这部分内容。

  真正的高手用记事本就能写出高质量的代码、用cvs就能做到完美的版本控制、用字典式的分解就能做好系统架构,我想,这正是剑宗一派的最高境界吧。

  第五日:寻找突破

  【动心忍性】

  技术流派看上去是如此吸引人,高手就像侠客一般,来去如风潇洒自如。但反观自己怎么看怎么没有侠客那股范儿。尽管上文提到了一些道理,了解这些 尽管不是坏事,但缺少实践总感觉是纸上谈兵。更何况,日常的工作又是枯燥无味、繁杂单调。每个人都盼望更高的目标、接触新鲜技术、将新技术运用到日常,在 探索尝试之中寻找成就感。这种感觉可以理解,但却缺少更深层次的思考。因为越到最后越会发现一线的工作才是最有挑战的。当然,我说这话的前提是,你能如前 文所说具备合格的软技能,需要一些技巧让工作变得工整有序、节奏健康,这样你才能将注意力放在纯粹的代码中,摆脱了外界的烦扰,方能从技术的角度思考突 破。这也是从初级到高级的进化过程需要大量的历练的原因。正如玉伯所说,“枯燥是创新的源泉。如果你发现自己没什么新想法,做事缺少激情,很可能是因为你 还未曾体验过真正的枯燥的工作”。

  关于如何寻找突破,我的建议是马上动手做、不要等,相信自己的直觉(这里和上文提到的先思后行是两码事)。比如,Slide幻灯控件理应支持触 屏事件以更好的适应移动终端,或许你在用的Slide幻灯版本很旧、或者时间不允许、再或者你害怕对Slide改造而引入bug,不要担心,大不了多花业 余时间,只要想,只要感觉合理和必要,就去做。因为这个过程带来的编程体验才是工程师们独有的美妙体味。我现在还时常深夜写代码,没有打扰、思如泉涌、代 码也更加工整严谨,不失为一种享受。因此,用眼睛去观察,用心去感触,“所以动心忍性,才会增益其所不能”啊。

  【得与失】

  互联网的发展的确太快,Web前端技术也在花样翻新,有人经不起诱惑,开始做新的尝试。前端技术虽然范围广,但各个分支都还比较容易入门,比如 服务器端脚本编程、再比如纯粹的WebApp,我认为这两者都是前端技术的范畴,毕竟他们都没有脱离“浏览器”,或者说类似浏览器的环境。NodeJS依 赖于V8,WebApp更是软件化的WebPage。只要打好基础,这些方向都是值得深入钻研的,因为,互联网的形态越发多元,新的技术总能找到用武之 地,这就要凭借自己的技术嗅觉和产品直觉,寻找技术和业务的契合点。

  这看上去是一种放弃,放弃了自己赖以生存的铁饭碗(熟练的切页面至少不会失业),实则不然。这种想法是一种误区,新的选择并不会让你放弃什么, 就像学会了开车,并不意味着就不会骑车了。其实改变的是思维方式而已,是一种进步,如果你能想通这一点,你也能跟得上互联网发展的脚步了,打开你的思维, 让技术变成你的金刚钻,而不是包袱。

  所以,所谓得失之间的权衡,其实就是“解放思想”。做到了这一点,那么你已经在做“技术驱动”了。

  【误区】

  但是,不要高兴的太早,“技术驱动”是需要大量的积累和经验的。在入行初期,很多人过于着迷与此,从而陷入了迷途。比如有人纠结于是否将dt、 dd的样式清除从reset.css中拿掉,原因是觉得这两个标签的清除样式会耗费一些渲染性能;或者是否需要将for循环改为while循环以提高js 执行速度。尽管这些考虑看上去是合理的,但并不是性能的瓶颈所在,也就是说,你花了很大力气重构的代码带来的页面性能提升,往往还不如将两个css文件合 成一个带来的提升明显。就好比用一把米尺量东西,没必要精确到小数点后10位,因为精确到小数点后2位就已经是不准确的了。这种技术误区常常让人捡了芝麻 丢了西瓜。

  话说回来,这里提到的怀疑权威的精神是绝对应当鼓励的,但不应当止于表象,如果怀疑dt的清除样式会对性能带来影响,就应当想办法拿到数据,用事实来证明自己的猜测。数据是不会骗人的。而求证过程本身就是一种能力的锻炼。

  【技术驱动】

  说到这里,你大概对“技术驱动”有那么一点点感觉了。身边太多人在抱怨“公司不重视前端”、公司不是技术驱动的、技术没机会推动产品业绩、我的价值得不到体现?

  什么是技术驱动?简单讲,就是技术对业务有积极推动作用。更多的是工程师发起、工程师影响、工程师负责。刚才提到的用数据说话只是一种“驱动”技巧,那么我需要何种数据,数据从哪里来?我来分享一个实际的场景吧。

  工程师A被委派一个重要的频道首页,因为是新年版,所以要赶在年前上线。A学了一点点响应式设计,想在这次重构中加上,但谁也没做过响应式设 计,需求方根本不懂,设计师也懵懵懂懂,交互设计师太忙,做完交互稿就忙别的去了。A纠结了,按部就班的把项目做完上线发布,尽管不会出什么问题,但总觉 少点什么。这时A做了两个决定,1,我要按时完成项目,2,趁机实践我在响应式设计中的想法和思考,若成功,作为附加值赠送给需求方,若失败,权当技术玩 具耍一耍罢了。所以A熟练的提前完成了项目,剩下的时间开始考虑如何将首页适应到各个平台中,视觉设计是一大难题,他用吃饭的时间找了设计师收集建议,对 窄屏中的内容模块做了看似合理的编排,代码上hack一下,能够正确适配,就发布上线了。这件事情需求方不知道,视觉设计师也不了解,交互设计师更没工夫 操心。A感觉挺爽,开始给工程师弟兄们到处炫耀这个好玩的功能,B看了问,手机端访问量如何,A觉得这个问题有道理,就去部署埋点,一周后拿到数据出奇的 意外,首先,移动段的访问量稳步增加,趋势健康,再者,移动端首屏焦点广告位的点击率较PC端高了近一倍,这个数据让A喜出望外,兴奋的拿着报表找到交互 设计师C和市场研究的同事D,D看了报表之后立即启动一个项目,专门调研公司全站响应式设计页面在PC端和移动端的点击率、PV、UV趋势方面的影响…… 后来发生的事情就都水到渠成了,设计师C开始注意设计页面交互时(至少是有条件的考虑)对移动端的适配,D的调研报告也放到了UED老大的案头……接下来 的事情,你懂得。A被指派要出一套响应式最佳实践和规范,最终,A走在了技术的前沿,也因此拿到了好绩效。

  这件事情就是一个典型的技术驱动的例子。谁不让你玩技术了,谁不重视你了,谁把你当工具了,谁觉得你的代码没价值?这世界只有自己把自己看扁,谁想跟你这个蝇头小卒过不去?用实力说话,用数据说话,用独到的见解说话,想不做技术驱动都难。

  第六日:码农的宿命

  【青春饭】

  “码农”是IT从业者一个自嘲的称号,也有从事没有发展前景的软件开发职位,靠写代码为生的意思。但我认为码农是一个爱称,编码的农民,和农民 一样有着执着纯真朴实豪爽的共性,仅仅分工不同而已。就好比农业社会对粮食的依赖,工业化进程对计算机应用也有着很强的依赖,大量的需求催生出这样一群 人。他们有智慧的大脑,对于编程,设计,开发都有着熟练的技巧,但多数人看来,码农的特点是:

  1,收入低
  2,工作单调
  3,工作时间长

  实际上这个描述非常片面,或者说是外行看热闹。第一,全行业比较来看,软件开发领域收入为中等偏上;第二,程序员一般都是有癖好的,沉浸在自己 的癖好中是不会感觉单调的;第三,程序员有一定的时间自由度(如果你是一名合格的程序员的话),至少不会像流水生产线工人一样。其实,通过几十年的发展, 我们对程序员的定义更加科学,比如很多IT企业都开始建立详细的JM(Job Module),即职级模型,程序员沿着专业方向可以走到很高,甚至可以 说,程序员是可以被当成一生的事业的。

  然而,有一个非常普遍的观点是,程序员和做模特一样是吃青春饭的,到了三十岁就要考虑转行或者转管理。尽管这种观点颇具欺骗性,但至少它对一种 人是适用的,即入错了行的人。如果你骨子里不想写程序,就算年纪轻轻为了生计写几年代码,之后肯定会另有他途。心非所属则不必勉强,但问题是,即便如此, 你知道你的心之所属吗?

  我们知道,一个成熟的产业一定需要各色岗位来支撑,若要成熟,则需要时间的沉淀,比如实体经济制造业,创意、生产线、高级技工、技术管理四个方 面都产出大量的高级人才。因为历史悠久,我们能看得到。而软件产业则不然,九成以上是刚出道的新手,并没有太多“高级”和“资深”的具体样板可供参照,在 前端开发领域中情况更甚,绝大部分人根本搞不清楚什么样才是“资深”前端工程师,相比传统软件行业近四十年的进化,我不相信仅有几年光景的前端技术岗位能 产出多少货真价实的“资深”。但互联网崛起速度太快,还没有等技术基础打牢,互联网形态就又花样翻新了,这种变化是一种常态,而岗位的设定也在这种变化之 中自然的优胜劣汰,比如两年前可能还难以想象数据部门会需要前端工程师,他们甚至不直接和浏览器打交道。前端工程师需要适应这种变化带来的观念冲击,不要 以为自己只能做切页面、或者只会给页面搞重构、只会搞兼容性,要把自己放在整个软件行业来看。

  所以,由于历史“不悠久”导致的岗位模糊本身不是什么大问题,岗位的演化本身就包含在互联网的发展轨迹之中。所以,当今的互联网IT状况,就好 比移动终端的大哥大时代、云计算的肉鸡时代、或者桌面操作系统的DOS时代。因此,前端工程师当前要务是要想清楚看清楚,在互联网中我能做什么,而不是作 为前端工程师我能做什么,所以,从这个角度讲,技术是一个工具,放大来看,技术也只是你职业生涯中很小的组成部分,而你的从业积累、和知识面的广度深度才 是你随着时间的推移慢慢步入“资深”的原因所在,而不是写了个什么框架就变“资深”了。如果有一天互联网形态固定了,它的岗位可能真正就定型了,才会有真 正清晰的职能边界,就像蓝色巨人IBM中的各色岗位一样,边界清晰,权责分明,普通程序员只能实现接口而无机会设计接口、低层级的工程师也无机会跃进式的 接触项目架构、技术经理人也不能轻易对产品有决策性影响,到这时,人的能力才真正的被限制在方圆之内,容不得越界,这种环境下人的成长非常缓慢。根本不会 有像今天互联网乱局之中所提倡的创新、革命、成长和思想解放。简单讲,一旦产业定型,就不太需要很多“创造”了,更多的是“维护”。所以,我个人宁愿互联 网IT“黑暗”的中世纪越久越好,至少对于年轻气盛程序员来说,黑暗的丛林环境才是真正的自然进化最理想的土壤,这时我想起了狄更斯在“双城记”中的开 篇。

  “这是最好的时代,这是最坏的时代;这是智慧的时代,这是愚蠢的时代;这是信仰的时期,这是怀疑的时期;这是光明的季节,这是黑暗的季节;这是希望之春,这是失望之冬;人们面前有着各样事物,人们面前一无所有;人们正在直登天堂,人们正在直下地狱”。

  【半路出家的危与机】

  然而,不管怎样,信心的树立不是一蹴而就的,对于转行做前端的人来说更是如此。俗话说,隔行入隔山。每个行业自有其道,自然不是想做就做。前端 技术领域半路出家者非常多,我们来分析一下转行的心理。第一,看到前端技术入门简单、互联网对前端技术的需求缺口巨大;第二,前端技术所见即所得、感觉学 习起来很快;第三,我身边的某某转行作前端看上去不错、我似乎也可以;第四,我不喜欢我现在做的工作、想换行业、正好前端技术上手较快,就选他吧;第五, 我真的喜欢做Web前端,为它付出再多都是值得的。

  转行者的心态比较容易走两个极端,一是只看到新行业的好,二是只觉得原工作很糟糕。但不管是什么行业的转行,对自己的职业规划的思考都应当先行一步。即务必首先清晰的回答这些问题:

  1,我能做什么?
  2,我不能做什么?
  3,我的优势是什么?
  4,我的劣势是什么?
  5,做新行业对我有何好处?
  6,换行会让我付出何种代价?
  7,如何定义转行成功?

  因为面试的时候一定会被这些问题所挑战。如果支支吾吾说不清楚,要么是对自己未来不负责任,要么骨子里就是草根一族,习惯做什么都蜻蜓点水浅尝 辄止,也难让人信服你的转行是一个权衡再三看起来合理的选择。我无法帮每个人回答这些问题,但至少有两点是确定的,第一,Web前端技术是一个朝阳行业, 绝对值得义无反顾的坚持下去;第二,你将经历从未有过的枯燥、苛刻的历练,所谓痛苦的“行弗乱其所为“阶段。不过话说回来,经历过高考的人,还怕个屁啊。

  有心之人自有城府、懂得放弃,看得清大势中的危机、识得懂繁华里的机遇。尤其当立足于Web前端技术时,这种感觉就愈发强烈。因为国内外前端技 术领域从2000年至今一直非常活跃,前端技术前进的步伐也很快,对于一些人来说,不管你是在大公司供职还是创业,不管你是在接外包项目还是自己写开源项 目,从转行到跟得上新技术的脚步是有一些方法和“捷径”的。

  第一,梳理知识架构

  我们知道知识积累有两种思路,第一种是先构建知识面、建立技术体系的大局观,即构建树干,然后分别深入每一个知识点,即构建枝叶,最终形成大 树。第二种是先收集知识点,越多越好,最后用一根线索将这些知识点串接起来,同样形成大树。第一种方法比较适合科班秀才,第二种方法则更适合转行作前端的 人,即实践先行,理论升华在后。比如对“IE6怪异模式“这条线索来说,要首先将遇到的IE6下的样式bug收集起来,每个bug都力争写一个简单的 demo复现之,等到你收集到第100个bug的时候,再笨的人都能看出一些规律,这时就会自然的理解IE的hasLayout、BFC和各种bug的原 因、你就成为了IE6的hack专家了,当你成为100个知识线索的专家的时候,你已经可以称得上“资深”的水平了。我们知道,10个人中有9个是坚持不 下来的,他们会以项目忙等各种理由万般推托,将自己硬生生的限制在草根一族,坐等被淘汰。所以,对于立志作前端的人来说,这种点滴积累和梳理知识非常重 要。

  第二,分解目标

  将手头的工作分解为几部分来看待,1,基本技能,2,项目经验,3,沟通能力,4,主动性和影响力。想清楚做一件事情你想在哪方面得到历练,比 如,我之前在做第一次淘宝彩票常规性重构的时候(正好是一次视觉和交互上的全新设计),我清楚的明白这次重构的目的是锻炼自己在架构准富应用时的模块解偶 能力,寻找在其他项目中架构的共通之处,所以我宁愿加班或花更多精力做这个事情,当然更没打算向业务方多解释什么,这件事情对我来说纯粹是技能的锻炼。而 经过这一次重构之后,我意外的发现对业务的理解更透彻深入、更清晰的把握用户体验上的瓶颈所在。如果一开始就把这次常规改版当成一个普通的项目按部就班的 做,我只能说,你也能按时完成项目,按时发布,但真真浪费了一次宝贵的锻炼机会,项目总结时也难有“动心忍性”的体会。

  所以,每个项目的每个事情都应当认真对待,甚至要超出认真的对待,想清楚做好每件事对于自己哪方面有所提升?哪怕是一个bug的解决,即便不是 自己的问题也不要草草踢出去了事,而是分析出问题原因,给出方案,有目的involve各方知晓……,正规的对待每个不起眼的小事,时间久了历练了心智, 这时如果突然遇到一个p0级的严重线上bug(比如淘宝首页白屏,够严重的了吧)也不会立即乱了方寸,这也是我上文提到的心有城府自然淡定万倍,而这种淡 定的气场对身边浮躁的人来说也是一种震慑和疗伤,影响力自然而然就形成了。

  第三,作分享

  做分享这事儿真的是一本万利。有心的人一定要逼着自己做分享,而且要做好。首先,自己了解的知识不叫掌握,只有理解并表达出来能让别人理解才叫 掌握,比如如果你解释不清楚hasLayout,多半说明自己没理解,如果你搞不懂双飞翼的使用场景,可能真的不知道布局的核心要素。再者,作分享绝对锻 炼知识点的提炼能力和表达能力,我们作为工程师不知道多少次和强硬的需求方pk,被击败的一塌糊涂。也反映出工程师很难提炼出通俗易懂的语言将技术要点表 述清楚。而做ppt和分享正是锻炼这种能力,将自己的观点提炼出要点和线索,分享次数多了,自然熟能生巧。档次也再慢慢提高。另一方面,逼迫自己站在公众 场合里大声讲话,本来就是提高自信的一种锻炼。

  这时,你或许会问,我讲的东西大家都明白,我讲的是不是多余,我第一次讲讲不好怎么办,大家会不会像看玩猴似的看我“这SB,讲这么烂还上来讲”?要是讲不好我以后再讲没人听怎么办,我今后怎么做人啊?

  老实说,这是一道坎,任何人都要跨过去的,谁都一样,你敢鼓起勇气在大庭广众之下向爱人表白,就没勇气对自己的职业宿命说不?其实勇敢的跨越这 一步,你会意外的收获他人的掌声和赞许,这些掌声和赞许不是送给你所分享的内容,而是送给你的认真和勇气。这个心结过不去,那就老老实实呆在自己的象牙塔 里遗老终生,当一辈子工程师里的钻石王老五吧。

  【匠人多福】

  如果你能耐心读到这里,心里一定有一个疑问,上面说的都是技术上能力上怎样怎样,那我所做项目不给力又当如何?如果项目不挣钱、黄了、裁了,我的努力不就白费了吗?我又有什么绩效和价值呢?

  没错,有这种想法的人不在少数。特别是刚出道的校招同学往往更加心高气傲,以为自己有改变世界的本事,一定要参与一个牛逼的团队做一款光鲜靓丽受人追捧能给自己脸上贴金的项目。如果你有这种想法,趁早打消掉这个念头,当然,我们这里先不讨论创业的情形。

  第一,如果你刚毕业就加入一个牛逼团队,说难听点,你就是团队中其他人眼中的“猪一样的队友”,不创造价值且拖项目后腿(显然大家都要照顾你的成长啊),按照271理论,你没有理由不是这个1。至少相当长一段时间内是这样。

  第二,你在所谓牛逼团队中的创造性受限,因为创新多来自于团队中的“资深“和大牛们,你参与讨论但观点通常不会被采纳,他们只会给你这个菜鸟分活干,想想看,你如何能花两到三年就超越身边的大牛们?甚至连拉近与他们的距离都难。

  第三,如果身在牛逼团队,自然心理对周围的牛人们有所期待,希望他们能灌输给你一些牛逼的知识和牛逼的理念。这种思想上的惰性在职场生涯之初是非常危险的。要知道技术和知识本身是很简单和淳朴的,只不过披上了一个光鲜项目的外衣而让人感觉与众不同。

  第四,由简入奢易,由奢入简难,做过一个看似光彩的项目,心理再难放平稳,去踏实的做一个看上去不那么酷的产品。这种浮躁心态会严重影响今后的职业发展和成长。

  第五,光鲜靓丽的项目被各种老大关注,是难容忍犯错误的,傻瓜都知道犯错误在成长之初的重要性。

  就我所看到的情形看,一开始加入看似很牛的项目组,三年后得到的成长,比那些开始加入一个不被重视的项目的同学要小很多,而后者在能力上的弹性 却更大。所以,道理很简单,你是要把一个很酷的项目做的和之前差不多酷,还是把一个不酷的项目做的很酷?项目是不是因为你的加入而变得与众不同了?

  从这个角度讲,不管是转行的新人还是刚出道的秀才,最好将自己当作“匠人”来对待,你的工作是“打磨”你的项目,并在这个过程中收获经验和成 长。付出的是勤奋,锻炼的是手艺,磨练的是心智。因此,你的价值来自于你“活儿“的质量,“活儿”的质量来自于你接手的项目之前和之后的差别。做好活儿是 匠人应有的职业心态。想通这一点,内心自然少一些纠结,才会对自己对项目的贡献度有客观的认识,不会感觉被项目所绑架。

  做一名多福的匠人,拥有了金刚钻、就不怕揽不到瓷器活儿。但对于人的成长来说,如果说“项目”重要但不关键,那么什么才是关键呢?这个话题还会在接下来的“伯乐与千里马”这篇中给出答案。 

  【若干年后】

  现在,让我们回过头回答一下“青春饭”的问题。在“青春饭”小节中提到,“程序员到三十岁之后需要转行或者转管理吗?”

  上文提到,工业化生产的四个领域,1,创意,2,生产线,3,高级技工,4,技术管理。Web前端技术也是如此,可以在这四个领域找到各自的归宿。

  第一,“创意“

  即和产品需求越走越近,拥有良好的产品感,对产品需求、设计交互把握准确,能够用适当的技术方案推动产品用户体验,属于“架构师”的范畴,因为 职能更加靠前,偏“出主意”型的。这种人更贴近用户,需要活跃的思维、广阔眼界、厚实的项目经验。更多的影响产品体验方面的决策。

  第二,“生产线“

  即前端基础设施建设,优化前端开发流程,开发工具,包括开发环境、打包上线自动化、和各种监控平台和数据收集等,属于“技术支持”的范畴,相比于很多企业粗犷难用的平台工具,前端技术方面的基础设施建设基础还需更加夯实,因为这是高效生产的基本保证。

  第三,“高级技工“

  即高级前端开发工程师,专职做项目,将产品做精做透,用代码将产品用户体验推向极致,偏“实战”型的,是项目的中坚力量,直接产出成果,影响产品效益。属于项目里的“资深”。

  第四,“技术管理“

  即做技术经理,这才是多数人所理解的“管理”,其实就是带团队、靠团队拿成果。这类人具有敏感的技术情结,在技术风潮中把握方向,能够指导培训新人,为各个业务输出前端人才,偏“教练”型的,促进新技术对业务的影响。并有意识的开辟新的技术领域。

  可见,转管理可不是想当然,也不是所谓做项目变资深了就能转管理,转了也不一定能做好。根据“彼得原理”,即人总是倾向于晋升到他所不能胜任的岗位,这时就又陷入“帕金森”定律所隐喻的恶性循环之中,直到你带的团队整个垮掉。

  所以,转管理应当是一件非常慎重的事情,不是所谓程序员混不下去就转管理这么简单。但不管怎样,有一件事情是需要尤其要想清楚,即,转了管理,技术就丢了吗?我们在第七日“伯乐与千里马”中再深入聊聊这个事儿。

  第七日,伯乐与千里马

  【师兄们的抉择 1】

  千里马常有,而伯乐不常有。——韩愈,“马说”。

  一个人这辈子能遇到一个好师兄是一种缘分,可遇不可求。很多人工作中的幸福感似乎也源自这种被认同,被师兄的了解和认同,有人能直言不讳的指出 你的不足,帮你发现机会,并将最适合你做的事情分配给你,这是莫大的幸运,但如此幸运的人十之一二,大多数人因为缺少伯乐的提点,渐渐辱于“奴隶人之手 “,潜力渐失,毁于中庸。

  在前端技术领域,这种情况很普遍也很特殊,当然有很多客观原因。即前端技术进入公众视野时间不长,有实力的伯乐更加是凤毛麟角。更何况,Web 前端技术还有着一些江湖气,知识点过于琐碎,技术价值观的博弈也难分伯仲,即全局的系统的知识结构并未成体系,这些因素也客观上影响了“正统“前端技术的 沉淀,奇技淫巧被滥用,前端技术知识的传承也过于泛泛,新人很难看清时局把握主次,加之业务上的压力,未免过早导致技术动作变形。而这些问题也无法全赖自 己全盘消化,若有人指点迷津,情况要好上万倍。因此,前端技术领域,为自己觅得一个靠谱的师兄,重要性要盖过项目、团队、公司、甚至薪水。

  这也是上文所说的“项目不重要,师兄才重要“的原因。说到这里就有一个问题,每个人都问下自己,你是想当师弟呢还是想当师兄呢?当师兄有什么好处呢?

  没错,很多师兄都是被师兄,甚至没有做好当师兄的准备,更进一步说,不少经理人也都是“被经理人“,没有做好准备就被推到了管理岗位。带人是耗 精力的,师兄要做很多思想斗争才舍得把这些宝贵的精力放在那些菜鸟身上,这不是一个技术问题,而是一个道德问题。要记住,没有谁应该无缘无故把自己所掌握 技能给你倾囊相授,如此皆命也。读到这里,作为菜鸟,作为学徒,作为新人,作为师弟,你做到对这份命运的足够尊重了吗?

  尊师重教的传统美德并没有在技术领域得以很好的延续。也正因为此,人才梯队难建立起来,但对于师兄来说,却是有更多机遇的。

  【师兄们的抉择 2】

  作为师兄,不管是主动还是被动,肯定会想当师兄对我有什么提升?对于初次做师兄的人来说,最大的提升在于两方面,1,任务分解,2,问题分析。

  第一,任务分解,作为师兄要给师弟派分任务,就涉及到任务分解,分解这事儿往低了说,就是派活,往高了说,其实就是做“架构”,比如一个页面, 按照什么思路进行模块划分,模块划分是否适合单人开发,如何控制共用样式和共用脚本,我需要为他提供什么接口,如何控制他的代码并入整个页面时不会影响整 体页面代码的熵值,这些都是实打实的“架构“应该包含的问题,而从小页面开始就做这种锻炼,做的多了,“架构感”自然就形成了。

  第二,问题分析,在之前自己写代码都是单打独斗,什么都是用代码解决问题,但一旦涉及协作,就要强迫自己分析问题,或者说给徒弟分析问题,告诉 他应当用什么方法来解决问题,当说到“方法”时,脑子里定形成了一个方案,按照这个方案路子走一定能解决问题。分析问题比写代码要更抽象、更高效,因为在 脑子里构建方案要比写代码要快,思考也会更加缜密,当锻炼的多了,思考越来越快,代码的草稿也很快就在脑海中形成了,这也是我们说为什么很多人不写代码但 编码思路和水平都很高的原因。

  这些工作方法对了,积累多了,就是提高。对于技术经理人来说,也是同样的道理。所以,就像在第五日的“得与失”部分提到的那样,转身师兄、变身管理并不意味着“失“掉技术饭碗,而是一种进步。

  【做自己的伯乐】

  那么,在前端技术领域里什么样的人才算千里马,其实人人都是千里马,人人都可以发掘自己的潜力,如果上面的文字你能读懂,能认可,这种自我发掘已经开始了,没有一个好伯乐又何妨呢?做一个勤快的小码农,少一些势利的纷争,很快会发现,自己才是最好的伯乐。

  但这并不是说,他人对自己的观点不重要,有时甚至要综合各种声音,所以,多找身边的大牛们聊聊天,多找你的师兄和主管,不管他们给你的建议是多么形而上,总有一些声音对你是有益的,多收集,有好处。

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

  【谁推动了历史前进,英雄?还是人民?】

  “做地球上最牛的UED!”,这是淘宝UED创立之初的口号,现在被渐渐淡忘了,因为微博上的一些讨论,又想起了这份曾经美好的初衷。玉伯也感 叹道:“这愿景曾吸引了多少好汉前往投奔呀。只可惜短短几年间,这愿景好像越来越远了”。问题是,要做好一个团队,靠的是个人、还是整体?愿景是越来越远 了吗?

  是谁推动了历史的前进,是英雄?还是人民?微观来看,是英雄,宏观来看,是人民。再放大了看,是互联网大潮之崛起推动了前端技术的进步,时势需要UED、需要用户体验。

  所以,UED团队的创立发展受这些积极的外因影响,赶上了好时候,成员也跟着沾光。然而,我并不关心这个口号,我只关心体制内的关键人物,那些 带动整个团队水涨船高的人们。往往我们发现,某些人的高度代表了整个团队的高度,个体的影响力代表了整个团队的影响力,某个人的水平代表了整个团队的水 平。支付宝、淘宝、腾讯、百度、盛大,都是如此。而我们作为普通的个体,正是要励志成为这种人,成为真真用技术推动用户体验前进的尖刀人物。

  这时我想起了很多人在知乎上的问题,关于跳槽、关于转行、关于创业、关于各种UED团队。我想,读得懂我上面的文字,你心理也许会有自己的答案。

  【归宿】

  最后,还有一个不得不说的问题,即归属问题,前端开发应当归属于UED还是技术部门?应当说,当前Web前端技术的价值体现在“用户体验“上。 是用户体验这块阵地最后一道坎。也就是说,前端工程师应当重点考虑我所作的页面的感官体验。这是需要一些灵感和感性的,应当看到帅气优雅的界面会心有所 动、或者实现一款精巧的小组件时萌生一阵快意。这种所见即所得的美妙编程体验正是其他后端工程师无法体验到的。因此,这种精确到像素级的精工雕琢虽然不直 接决定产品生死,但却是提升产品品味和时尚感的要素。物质越来越丰富的今天,大众的更高诉求不就是品味和时尚吗?

  如果将前端归到技术部门,一方面和“设计“离的更远,代码写的规规矩矩但渐缺少了灵性,另一方面作为工程师又缺少计算机专业课的功底,才真正丧 失了优势所在,如果有一天,前端工程师的平均水平足够高,清一色的计算机科班出身,似乎更合适归入到技术部门。所以,Web前端工程师是“工程师“,需要 科学严谨的编程能力,但身处UED所应当具备的美感和灵性是万不可被剥夺去的。

  还有一点,Web前端工程师作为UED之中最具实践精神和逻辑思维的工种,是能够将技术对设计的影响发挥到最大,可以催生出大量的创造和革新的,这一点也是传统后端工程师所不具备的。

  第九日,前端技术体系

  现在越来越感觉到前端技术需要成体系的积累,一方面可以规范我们的前端技术培训,另一方面,作为知识线索为新人做指引,省的走弯路,避免陷入奇技淫巧的深坑之中不能自拔。

  之前我整理了一下“前端技术知识结构”,罗列的比较散,但也基本表述清楚了我的观点。今年上半年也在整个研发中心组织了一次前端技术培训,对于前端技术的演化规律也有过整理,都放在了这个ppt中,希望对大家有所帮助。

posted @ 2013-01-16 12:53 杨军威 阅读(289) | 评论 (0)编辑 收藏

js面向对象

【一】 面向对象的基本概念

  面向对象的英文全称叫做Object Oriented,简称OO。OO其实包括OOA(Object Oriented Analysis,面向对象分析)、OOD(Object Oriented Design,面向对象设计)和OOP(Object Oriented Programming,面向对象的程序设计)。

  通常所说的面向对象是指OOP, OOP是一种围绕真实世界的概念来组织模型的程序设计方法,它采用对象来描述问题空间的实体。在使用计算机解决问题时,对象是作为计算机模拟真实世界的一个抽象,一个对象就是一个物理实体或逻辑实体,它反映了系统为之保存信息和(或)与它交互的能力。使其具有自己的属性和行为, 从而简化对复杂事物的描述,更有利于工程的可维护性和扩展性。

  OOP同结构化程序设计相比最大的区别就在于: 前者首先关心的是所要处理的数据,而后者首先关心的是功能。

  【二】 面向对象三个基本特征

  封装 (Encapsulation) 将数据以及相关的操作组织在一起,成为独立的构件。外部无法直接访问这些封装了的数据,从而保证了这些数据的正确性。封装的目的是为了内部数据表现形式和实现细节的隐藏,信息隐藏是为了减少系统各部分间的依赖性,各部分间必须通过明确的通道传送信息,也就是对象间的接口.这样一来,隐藏了部分内部的细节,极大方便系统的开发,维护和扩展。

  继承 (Inheritance) 继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。一个新类可以从现有的类中派生,这个过程称为类的继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。派生类可以从它的基类那里继承方法和实例变量,并且派生类可以修改或增加新的方法使之更适合特殊的需求。继承性很好地解决了软件的可重用性问题。

  多态 (Polymorphism) 多态是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是允许类与类之间相同方法名的指针得以调用, 这样很好地解决了应用程序函数同名问题。实现多态,有二种方式,覆盖,重载。

  【三】 Javascript 面向对象

  javascript本身是一种基于对象(object-based)的语言,我们日常编码过程中用到的所有东西几乎都是对象(Number, String, Boolean, etc.)。但是,相对于一些流行的面向对象语言(C++, C#, java),它又不是一种真正的面向对象编程(OOP)语言,因为它的语法中没有class的概念。

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

  几种对象封装的方法

  • 继承
  • 多态体现

  之一、几种对象封装的方法

  1. 对象封装 – 原始模式

  假定我们把猫看成一个对象,它有”name”和”color”两个属性, “etc” 行为。

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

  现在,我们需要根据这个原型对象的规格(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 ,... 

  不方便创建多个实例对象,扩展性差, 实例(cat1, cat2)之间找不到联系。…

  2. 对象封装 – 构造函数模式

  “构造函数”,就是一个普通函数,但是内部使用了 `this` 变量。对函数使用 `new` 运算符,就能生成实例,并且 `this` 变量会绑定在实例对象上。

  使用构造器创建出来的对象会有一个 `constructor` 属性,指向它们的构造函数。

  `Class` 只是一个模板,创建出来的来实例都是由模板生成。

  比如,猫的原型对象现在可以这样写:

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` 属性,指向它们的构造函数 `Cat`。 var cat2 = Cat('Smokey', 'white'); console.log(cat2); // undefined 

  3. 对象封装 – Prototype 模式

  `prototype` 是 `Function` 对象的一个属性,这个属性指向另一个对象。 这个对象的所有属性和方法,都会被构造函数的实例继承。

  同时 `prototype` 又存在一个指向构造函数的引用 `constructor`,这样就成功的构成一个循环引用的原型链结构。

  我们可以把那些不变的属性和方法,直接定义在 `prototype` 对象上, 节省内存开销。

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)

  将持有共性特点的属性或行为抽象出一个基本类, 可以按不同层次结构的业务分组抽象出多个基础类。

  Cat, Bird

  1. 继承 – 构造函数绑定

  使用call或apply方法,将父对象的构造函数绑定在子对象上。

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属性,指向它的构造函数 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 

  需要创建父类实列来实现 `prototype` 继承

  3. 继承 (Inheritance) – 利用空对象作为中介实现原型继承

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

  我们将上面的方法,封装成一个函数,便于使用。

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 继承 – 借住工具方法实现继承

/** @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); 

  之三、多态

  1. 多态 – 通过重写原型方法来实现方法重名调用

/** @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. 多态 (Polymorphism) – 原型继承 `prototype` 链上的方法、属性查找

  【四】总结 Summary

  Constructor

  Prototype

  Inheritance

posted @ 2013-01-16 12:52 杨军威 阅读(343) | 评论 (0)编辑 收藏

仅列出标题
共43页: First 上一页 32 33 34 35 36 37 38 39 40 下一页 Last 

导航

统计

常用链接

留言簿

随笔档案

搜索

最新评论

阅读排行榜

评论排行榜