乐者为王

Do one thing, and do it well.

JPA + Spring 2入门

JPA(Java Persistence API)是EJB 3.0 新引入的一个把Java数据对象映射成关系数据库对象的数据持久化编程模型,它弥补了JDBC、ORM、EJB 2等在Java对象持久化方面的不足之处,并且非常易于使用。JPA可以被当作一个单独的 POJO(Plain Old Java Object)持续化使用,或者被集成到任意Java EE兼容的容器或其它的轻量级框架(例如Spring)等一起使用。

配置JPA

Spring提供了两种方法创建JPA的EntityManagerFactory对象。

方法1:

1
2
3
4
5
<beans>
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="persistenceUnit" />
    </bean>
</beans>

FactoryBean创建的EntityManagerFactory适用于仅通过JPA进行数据访问的环境。由于使用了PersistenceProvider自动侦测机制,所以只能从默认路径classpath:META-INF/persistence.xml中读取配置信息。

方法2:

1
2
3
4
5
6
7
8
<beans>
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="someDataSource" />
        <property name="loadTimeWeaver">
            <bean class="org.springframework.instrument.classloading.SimpleLoadTimeWeaver" />
        </property>
    </bean>
</beans>

FactoryBean提供了对JPA EntityManagerFactory的完整控制,非常适合那种有简单定制需要的环境。你可以处理多个persistence.xml配置文件;覆盖persistence.xml文件的默认路径;可以传递Spring托管的JDBC DataSource给JPA PersistenceProvider,用来替代persistence.xml中的JDBC配置(这个Spring托管的DataSource通常被作为nonJtaDataSource传送给PersistenceProvider,并且覆盖persistence.xml中相同的nonJtaDataSource)。

数据访问

基于JPA的DAO可以通过三种方式进行数据访问JpaDaoSupport,JpaTemplate和plain JPA。其中JpaTemplate是plain JPA的封装,而JpaDaoSupport又是JpaTemplate的封装。无疑,使用不对Spring产生任何依赖的Plain JPA的API进行编程是最好选择。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ProductDaoImpl implements ProductDao {
    private EntityManager entityManager = null;

    @PersistenceContext
    public void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    public Collection loadProductsByCategory(String category) {
         Query query = em.createQuery("from Product as p where p.category = :category");
         query.setParameter("category", category);
         return query.getResultList();
    }
}

需要注意的是,必须激活PersistenceAnnotationBeanPostProcessor功能才能让Spring识别@PersistenceContext注解。

1
2
3
4
5
6
<beans>
    <!-- JPA annotations bean post processor -->
    <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />

    <bean id="productDao" class="com.codemany.netlink.dao.impl.ProductDaoImpl" />
</beans>

异常转化

Spring提供了一个允许通过使用@Repository注解进行透明的异常转化的解决方案。

1
2
@Repository
public class ProductDaoImpl implements ProductDao {
1
2
3
<beans>
    <!-- Exception translation bean post processor -->
    <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

后置处理器将自动的寻找所有的异常转化器(PersistenceExceptionTranslator这个接口的实现类)并通知所有打上@Repository注解的bean,从而能够使得被找到的异常转化器能够在抛出异常时做相应的异常转化工作。

总的来说,DAO能够基于普通的Java持久层API和注解来实现,但同样也能享受到由Spring管理事务、IoC和透明的异常转化(转化成为Spring的异常体系)等好处。

重新认识button元素

英文原文:http://www.particletree.com/features/rediscovering-the-button-element/

简介

对每个应用程序设计者来说,为你的用户创建一套一致的界面是个不懈的斗争。在Web上构建一致性尤为艰难,因为跨越浏览器和操作系统的视觉渲染差异是完全不同的,并且在什么能做和不能做上几乎是随心所欲的。在你处理 表单元素时这个问题变得更为明显。在标准外观的战争里,它们中最大的输家是臭名昭著的提交按钮。

现状是,带有type="submit"的输入要么是太丑陋(Firefox),有点缺陷(Internet Explorer),要么完全缺乏弹性(Safari)。大多数的解决方案是使用图片输入和自己创建这个该死的东西。这是不幸的,因为那样做以后每当我们急需一个新按钮时工作就被归结为启用Photoshop的繁琐任务。我们需要的是更好的东西——一些对于设计者来说更加灵活和有意义的东西。我们很幸运,因为解决方案已经存在,它所需要的是一点点爱。我的朋友们,让我向你介绍我的小朋友:<button>元素。

输入 vs 按钮

这里是标准的提交按钮标记:

1
<input type="submit" value="Submit" />

它在浏览器三兄弟里看起来像这样:

input-submit

嗯。这里是创建一个用于提交的按钮元素时使用的标记:

1
<button type="submit">Submit</button>

并且它看起来像这样:

button-tag

这些按钮与上面对等物的工作和行为方式完全相同。除提交表单外,你可以让它们禁用,添加一个accesskey甚至指定一个tabindex。除了Safari里的按钮看起来有视觉差异(它没有把不自然的aqua界面放到按钮上,这对我们是有利的),关于<button>标签最酷的事情是你可以在它们里面放置有用的HTML元素,像图片:

1
<button type="submit"><img src="" alt="" /> Submit</button>

它们看起来像这样:

button-images

非常好。(好吧,它们有点丑,但是我说过它们需要一点点爱。)实际上,根据W3C所说,这些特殊的视觉差异正是<button>元素被创建的原因。

Buttons created with the BUTTON element function just like buttons created with the INPUT element, but they offer richer rendering possibilities: the BUTTON element may have content. For example, a BUTTON element that contains an image functions like and may resemble an INPUT element whose type is set to “image”, but the BUTTON element type allows content.

The Button Element - W3C

所以这里我们正在寻找一种设计解决方案,好在拥有海量资料的互联网上有一段标记可以帮助我们解决这个问题。这很方便,然而不幸的是大多数设计者和开发者甚至不知道该元素存在。现在,在我直接用按钮元素替换所有在Wufoo中的图片输入之前,我决定标记和CSS必须满足几个需求:

需求

  1. 它们必须看起来像按钮。
  2. 它们必须在浏览器里看起来相同。
  3. 我用于按钮的样式需要同样能被使用在链接上(因为在Wufoo中的交互总是由一个表单提交或者从链接来的一个Ajax调用引发,这些东西很可能并排排列,我需要它们具有相同的视觉重量感)。
  4. 在许多不同情况下使用时标记需要保持弹性和易于修改。
  5. 我应该能够有效地使用图标和颜色来传递关于这种交互将要发生的信息。

带着这些适当的挑战,我潜进CSS,在解决一些跨浏览器的挑战之后,得出了以下内容(你也可以在Wufoo上看到所有的这些内容):

结果

buttons

没有什么疯狂的。简单,但是有效。现在,我喜欢这种处理按钮的方式是我可以使用FAMFAMFAM的1000个图标库去说明大量可笑的想法和动作而不必每一次我需要一些新东西时都要从Photoshop生成。如果我们快速查看下标记,你会注意到在那里的最后两个按钮实际上是链接:

1
2
3
4
5
6
7
8
9
10
11
<div class="buttons">
    <button type="submit" class="positive">
        <img src="/images/icons/tick.png" alt=""/> Save
    </button>
    <a href="/password/reset/">
        <img src="/images/icons/key.png" alt=""/> Change Password
    </a>
    <a href="#" class="negative">
        <img src="/images/icons/cross.png" alt=""/> Cancel
    </a>
</div>

这很有用的原因是因为在Web应用中许多动作是由REST驱动的,所以只要通过链接把用户请求发送到某个特定的URL就会发起它们需要做的事情。使用可以在这两种类型的元素(链接和按钮)上工作的样式,为我们提供了灵活性,以保持我们的交互方式看起来是一致的和适当的,不管它是通过Ajax完成还是一个标准的提交。

仅仅是一些简短的题外话。你可能想知道我为什么把那些图标图片中的alt属性留成空白。它可能会令一些人感到惊讶,虽然每个图片的alt属性是必需的,实际上描述它们不是必需的。空的alt属性完全有效,并且帮助屏幕阅读器知道哪些信息事实上要被忽略,在你试图寻找下一个适当的可操作条目时节省你用户宝贵的时间。因为图标实际上是多余的,我宁愿不要浪费用户的时间听到我用于可视化的图片发生了什么事。它们只会听到“Submit”而不是“Checkmark Submit”,这实际上会让事情有点困惑。

CSS

在大多数情况下,样式化这些按钮的CSS是相当直白的。让人抓破脑袋的跨浏览器的不一致性导致下列代码中填充的数量不一致,但没什么不可能,你们很幸运,这个问题已经解决了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/* BUTTONS */
.buttons a, .buttons button {
    display: block;
    float: left;
    margin: 0 7px 0 0;
    background-color: #f5f5f5;
    border: 1px solid #dedede;
    border-top: 1px solid #eee;
    border-left: 1px solid #eee;
    font-family: "Lucida Grande", Tahoma, Arial, Verdana, sans-serif;
    font-size: 100%;
    line-height: 130%;
    text-decoration: none;
    font-weight: bold;
    color: #565656;
    cursor: pointer;
    padding: 5px 10px 6px 7px;    /* Links */
}
.buttons button {
    width: auto;
    overflow: visible;
    padding: 4px 10px 3px 7px;    /* IE6 */
}
.buttons button[type] {
    padding: 5px 10px 5px 7px;    /* Firefox */
    line-height: 17px;    /* Safari */
}
*:first-child+html button[type] {
    padding: 4px 10px 3px 7px;    /* IE7 */
}
.buttons button img, .buttons a img {
    margin: 0 3px -3px 0 !important;
    padding: 0;
    border: none;
    width: 16px;
    height: 16px;
}

当致力于不一致性的时候有件事出现了,就是Internet Explorer中关于显示长按钮有个渲染缺陷的事实。你可以在Jehiah.cz阅读相关内容,但它是什么导致了以上代码中一些宽度和溢出的声明。

添加一些颜色

在Wufoo中,我们使悬停颜色蓝色成为中性动作,对于正面和负面内涵适当地使用绿色和红色。以下的代码是我们为处理按钮而创建的样式,这些按钮意味着显示像添加和保存这样的正面交互以及像取消和删除这样的负面交互。对我们来说这是一次不错的尝试,显然你可以根据自己的喜好选择颜色。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/* STANDARD */
button:hover, .buttons a:hover {
    background-color: #dff4ff;
    border: 1px solid #c2e1ef;
    color: #336699;
}
.buttons a:active {
    background-color: #6299c5;
    border: 1px solid #6299c5;
    color: #fff;
}
/* POSITIVE */
button.positive, .buttons a.positive {
    color: #529214;
}
.buttons a.positive:hover, button.positive:hover {
    background-color: #e6efc2;
    border: 1px solid #c6d880;
    color: #529214;
}
.buttons a.positive:active {
    background-color: #529214;
    border: 1px solid #529214;
    color: #fff;
}
/* NEGATIVE */
.buttons a.negative, button.negative {
    color: #d12f19;
}
.buttons a.negative:hover, button.negative:hover {
    background: #fbe3e4;
    border: 1px solid #fbc2c4;
    color: #d12f19;
}
.buttons a.negative:active {
    background-color: #d12f19;
    border: 1px solid #d12f19;
    color: #fff;
}

总结

最后,这只是我们决定如何用Wufoo处理事情,它是为我们制定的开发流程。当然,这不是玩这个游戏的唯一方法。有许多你可以给它添加趣味的方法(使用渐变!)和改变周围的事物(在标记中的图片上使用图片替换)。因为<button>标签几乎可以处理在它里面的任何标记,你可以添加一些<span>标签,然后使用Alex Griffioen最近写的方法来创建真正漂亮的圆角渐变作品。老实说,我希望对于许多与他们应用中可复用表单界面斗争的设计者来说这只是一个很好的起点。或许甚至,我希望你再次看看这个经常被浪费的表单元素,在不经思考就为提交采用输入和PSD之前要二思。

在Visual C#中调试C++的DLL

在Visual C#中调试C++的DLL很简单,只要在C#项目属性的Debug一项中钩上“Enable unmanaged code debugging”,然后你就可以像调试一般程序那样调试DLL了。

VC++动态链接库(DLL)编程入门

DLL是一个包含可由多个程序同时使用的代码和数据的集合。例如,在Windows操作系统中,Comdlg32动态链接库 执行与对话框有关的常见函数。因此,每个程序都可以使用该DLL中包含的功能来实现“打开”对话框。这有助于促进代码复用和内存的有效使用。通过使用DLL,程序可以实现模块化,由相对独立的组件组成。例如,一个计帐程序可以按模块来销售。可以在运行时将各个模块加载到主程序中(如果安装了相应模块)。因为模块是彼此独立的,所以程序的加载速度更快,而且模块只在相应的功能被请求时才加载。

DLL的优点

  1. 使用较少的资源。当多个程序使用同一个函数库时,DLL可以减少在磁盘和物理内存中加载的代码的重复量。这不仅可以大大影响在前台运行的程序,而且可以大大影响其它在Windows操作系统上运行的程序;
  2. 简化部署和安装。当DLL中的函数需要更新或修复时,只要函数的参数和返回值没有更改,就不需重新编译或重新建立程序与该DLL的链接。此外,如果多个程序使用同一个DLL,那么多个程序都将从该更新或修复中获益;
  3. 支持多语言程序。只要程序遵循函数的调用约定,用不同编程语言编写的程序就可以调用相同的DLL函数。程序与DLL函数在下列方面必须是兼容的:函数期望其参数被推送到堆栈上的顺序,是函数还是应用程序负责清理堆栈,以及寄存器中是否传递了任何参数;
  4. 使国际版本的创建轻松完成。通过将资源放到DLL中,创建应用程序的国际版本变得容易得多。可将用于应用程序的每个语言版本的字符串放到单独的DLL资源文件中,并使不同的语言版本加载合适的资源。

DLL的类型(Kinds of DLLs)

Visual C++支持三种类型的DLL,它们分别是Non-MFC DLL、MFC Regular DLL和MFC Extension DLL:

  1. Non-MFC DLL指的是不用MFC的类库结构,直接用C语言写的DLL,其导出的函数是标准的C接口,能被MFC或非MFC编写的客户程序调用;
  2. Extension DLL支持C++接口,也就是说它导出C++函数或者整个类给客户程序。导出函数可以使用C++或MFC的数据形式作为参数或返回值,当导出整个类时,客户程序可以创建此类的对象或者从这些类进行派生。使用Extension DLL的一个问题就是该DLL仅能和MFC客户程序一起工作;
  3. Regular DLL和上述的Extension Dll一样,也是用MFC类库编写的,它的一个明显的特点是在源文件里有一个继承CWinApp的类(注意:此类DLL虽然从CWinApp派生,但没有消息循环)。Regular DLL有一个很大的限制就是,它只可以导出C风格的函数,但不能导出C++类、成员函数或重载函数。调用Regular DLL的客户程序不必是MFC应用程序,也可以是在Visual C++、Dephi、Visual Basic等环境下开发的客户程序。

DLL的加载

客户程序使用DLL的方式有两种:隐式链接(静态加载或加载时动态链接)和显式链接(动态加载或运行时动态链接)。

为了隐式链接到DLL,客户程序必须从DLL的提供程序获取下列项:

  1. 包含导出函数或C++类声明的头文件(.h文件);
  2. 要链接的导入库(.lib文件);
  3. 实际的DLL(.dll文件)。

使用DLL的客户程序必须include头文件(此头文件包含每个DLL中的导出函数或C++类),并且链接到此DLL的创建者所提供的导入库。

1
2
3
4
5
// Cacl.cpp
DLLAPI int Sum(int a, int b)
{
    return a + b;
}
1
2
3
4
5
6
7
8
// Cacl.h
#ifdef CACL_EXPORTS
#define DLLAPI __declspec(dllexport)
#else
#define DLLAPI __declspec(dllimport)
#endif

DLLAPI int Sum(int a, int b);
1
2
// Client.cpp
DLLAPI int Sum(int a, int b);

为了显式链接到DLL,客户程序必须在运行时通过函数调显式加载DLL:

  1. 调用LoadLibrary加载DLL和获取模块句柄;
  2. 调用GetProcAddress获取指向客户程序要调用的每个导出函数的函数指针(由于客户程序是通过指针调用DLL的函数,编译器不生成外部引用,故无需与导入库链接);
  3. 使用完DLL后调用FreeLibrary释放资源。
1
2
3
4
5
6
7
8
9
10
11
12
// Client.cpp
HINSTANCE hDLL = LoadLibrary("demo");
if (hDLL != NULL)
{
    LPFNDLLFUNC1 lpfnDllFunc1 = GetProcAddress(hDLL, "Sum");
    if (lpfnDllFunc1 == NULL)
    {
        FreeLibrary(hDLL);
        return SOME_ERROR_CODE;
    }
    return lpfnDllFunc1(dwParam, uParam);
}

客户程序如何找到DLL

如果用LoadLibrary显式链接DLL的话,可以指定DLL的全路径名。如果没有指定路径名,或者使用了隐式链接,则系统将使用下面的搜索序列来定位DLL:

  1. 包含客户可执行文件所在的目录;
  2. 当前目录;
  3. Windows系统目录(可用GetSystemDirectory函数获取);
  4. Windows目录(可用GetWindowsDirectory函数获取);
  5. 在Path环境变量里列出的目录(注意:不使用LIBPATH环境变量)。

导出DLL函数

DLL文件的布局与EXE文件非常相似,但有一个重要差异:DLL文件包含导出表。导出表包含DLL导出到客户程序的每个函数的名称。只有导出表中的函数可由客户程序访问。DLL中的任何其它函数都是DLL私有的。通过使用带/EXPORTS选项的dumpbin工具,可以查看DLL的导出表。有两种方法可以从DLL导出函数:

方法1:在定义中使用__declspec(dllexport)关键词。

1
2
3
4
5
6
7
// 导出函数
__declspec(dllexport) void __cdecl Function();

// 导出类中的所有公共数据成员和成员函数
class __declspec(dllexport) CExampleExport : public CObject
{
}

方法2:在生成DLL时,创建一个后缀名为def的模块定义文件(如果希望按序号而不是按名称从DLL导出函数,则使用此方法)。

实例DLL和客户程序代码:

1
2
3
4
5
6
7
8
// Cacl.h
#ifdef CACL_EXPORTS
#define DLLAPI __declspec(dllexport)
#else
#define DLLAPI __declspec(dllimport)
#endif

DLLAPI int Sum(int a, int b);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Cacl.cpp
#include <windows.h>
#include "Cacl.h"

BOOL APIENTRY DllMain(HANDLE hModule, DWORD reason, LPVOID lpReserved)
{
    switch (reason)
    {
        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
    }
    return TRUE;
}

DLLAPI int Sum(int a, int b)
{
    return a + b;
}
1
2
3
4
5
6
7
8
9
// Client.cpp
#include <stdio.h>
#include "Cacl.h"

int main(int argc, char* argv[])
{
    printf("Sum = %d\n", Sum(5, 3));
    return 0;
}

如何调试DLL

调试DLL很容易,只要从DLL工程启动调试器即可。第一次这样做的时候,调试器会请求给出客户EXE程序的路径。之后,每次从调试器运行DLL时,调试器会自动装入客户EXE程序,而EXE用搜索序列找到DLL。

批量更新表中某个字段的值到另一个字段的存储过程(MySQL版)

对博客做了一些修改,需要将entries表中的updatetime字段值替换成pubtime字段的值,为此写了一个MySQL存储过程,其中涉及游标的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
DELIMITER //

CREATE PROCEDURE sp_batch_replace_field_value_with_another()
BEGIN
    DECLARE p_id INT;
    DECLARE p_pubtime DATETIME;
    DECLARE p_done BOOL;
    DECLARE p_cursor CURSOR FOR SELECT id, pubtime FROM entries;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET p_done = true;

    OPEN p_cursor;
        REPEAT
            FETCH p_cursor INTO p_id, p_pubtime;
            UPDATE entries SET updatetime = p_pubtime WHERE id = p_id;
            UNTIL p_done = true
        END REPEAT;
    CLOSE p_cursor;
END//

DELIMITER ;

第一次在SQLyog中写存储过程,在修改了存储过程后竟然不知道如何保存。点击File -> Save却是保存为另外一个文件,而不是直接保存修改的存储过程。经过多番尝试后发现按F5键就可以了。

com.sybase.jdbc.SybSQLException: The parameter '@user' in the procedure 'sp_adm_chk_lvl' was not declared as an OUTPUT parameter

运行环境:WebLogic 8.16 + Sybase ASE 12.5

问题:The New, Save from File menu are grayed out

分析打印出来的日志文件找到有以下异常出现:

1
2
3
4
5
org.springframework.jdbc.UncategorizedSQLException: CallableStatementCallback
    ; uncategorized SQLException for SQL [{? = call sp_adm_chk_lvl(?, ?, ?, ?, ?, ?, ?)}]
    ; SQL state [ZZZZZ]; error code [276]
    ; The parameter '@user' in the procedure 'sp_adm_chk_lvl' was not declared as an OUTPUT parameter.
    ; nested exception is com.sybase.jdbc.SybSQLException: The parameter '@user' in the procedure 'sp_adm_chk_lvl' was not declared as an OUTPUT parameter.

搜索到一篇出现类似问题的文章 http://www.ibm.com/support/docview.wss?uid=swg21174150 ,其中有这么一段文字:

1
2
3
4
If you call CallableStatement.clearParameters() when the statement has a ?=rpc return value,
we forget that there is a return value and the next time the rpc is executed we send too many
input parameters, and they are all shifted to the right. This problem has been corrected,
and jConnect now has the expected behavior for the clearParameters() method.

因此猜测菜单变灰的问题是由于使用了不正确的JDBC Driver版本。在Weblogic Console发现Connection Pools中Driver Name是com.sybase.jdbc.SybDriver,而正确的设置应该是com.sybase.jdbc2.jdbc.SybDriver,修改Driver Name后New, Save菜单项果然可以正常使用了。

双色球(Union Lotto)模拟摇奖器

双色球基本规则:

  1. 彩票投注区分为红色球号码区和蓝色球号码区;
  2. 每注投注号码由6个红色球号码和1个蓝色球号码组成。红色球号码从1-33中选择;蓝色球号码从1-16中选择;
  3. 摇奖时先摇出6个红色球号码,再摇出1个蓝色球号码,摇出的红色球号码按从小到大的顺序和蓝色球号码一起公布。

双色球模拟摇奖器代码:

1
2
3
4
5
6
7
# union_lotto.rb
red_balls = Array.new(33) {|i| i + 1}
blue_balls = Array.new(16) {|i| i + 1}
1.upto(6) do
  print red_balls.delete_at(rand(red_balls.length)), ' '
end
puts '- ' + blue_balls.delete_at(rand(blue_balls.length)).to_s

Cannot delete or update a parent row: a foreign key constraint fails

在SQLyog中Import Data或Empty Table时,出现:

1
2
Error No. 1451
Cannot delete or update a parent row: a foreign key constraint fails

查看官方文档Setting FOREIGN_KEY_CHECKS to 0 can also be useful for ignoring foreign key constraints during LOAD DATA and ALTER TABLE operations.知道这可能是MySQL在InnoDB中设置了foreign key关联,造成无法更新或删除数据。可以通过设置FOREIGN_KEY_CHECKS变量来避免这种情况。

1
SET FOREIGN_KEY_CHECKS = 0;

JavaScript中的undefined

JavaScript有6种类型,undefined、null、boolean、number、string和object。其中,null类型只有一个值null。undefined类型也只有一个值,即undefined,当声明的变量没有被赋值的时候,该变量的默认值就是undefined。

1
var foo;

上面这行代码声明了变量foo,没有赋值。脚本引擎在处理代码的时候会给该变量赋予值undefined。实际上,该行代码等价于

1
var foo = undefined;

可以用下面的代码测试:

1
2
3
4
var foo;
alert(foo == undefined);    // true
alert(foo === undefined);    // true
alert(typeof(foo));    // undefined

JavaScript另一个有趣的方面是在使用变量之前不必声明。例如:

1
2
3
var foo = "Hello ";
bar = foo + "world!";
alert(bar);    // Hello world!

变量bar并没有用var运算符定义,这里只是使用了它,就像已经声明过它一样。脚本引擎遇到未声明过的标识符时,用该变量名创建一个全局变量,并将其初始化为指定的值(这里是foo + "world")。这是该语言的便利之处,不过如果不能紧密跟踪变量,这样做也很危险。考虑下面的代码:

1
2
3
4
5
var foo;
// Make sure this variable isn't defined
//var bar;
alert(foo);
alert(bar);

执行的结果是:

undefined

undefined-error

alert(bar)出现错误是因为bar被创建成全局变量后没有被初始化,还处在未初始化(uninitialized)状态。但是,typeof运算符并不真正区分这两种值。考虑下面的代码:

1
2
3
4
5
var foo;
// Make sure this variable isn't defined
//var bar;
alert(typeof(foo));    // undefined
alert(typeof(bar));    // undefined

前面的代码对两个变量的输出都是“undefined”,即使变量bar在使用前没有被声明。

当函数无明确返回值时,返回的也是值undefined,如下所示:

1
2
3
function foobar() { }
alert(foobar() == undefined);    // true
alert(foobar() === undefined);    // true

ECMAScript认为undefined是从null派生出来的,所以把它们定义为相等的:

1
alert(null == undefined);    // true

尽管这两个值相等,但它们的含义不同。undefined是声明了变量但未赋值时赋予该变量的值,null则用于表示尚未存在的对象(但从技术上来说,null仍然是原始值)。如果要区分两者,要使用===或typeof运算符。

CSS盒模型

平面示意图

box-model-01

3D示意图(原图出自:hicksdesign)

box-model-02

Flash示意模型