Sunday, December 21, 2008

InnoDB和MyISAM的差别(mysql事务处理)

Filed under: Mysql |

Posted on 1月 13th, 2008 由 Darren

InnoDB 和MyISAM是在使用MySQL最常用的两个表类型,各有优缺点,视具体应用而定。基本的差别为:MyISAM类型不支持事务处理等高级处理,而 InnoDB类型支持。MyISAM类型的表强调的是性能,其执行数度比InnoDB类型更快,但是不提供事务支持,而InnoDB提供事务支持已经外部键等高级数据库功能。

MyIASM是IASM表的新版本,有如下扩展:
二进制层次的可移植性。
NULL列索引。
对变长行比ISAM表有更少的碎片。
支持大文件。
更好的索引压缩。
更好的键吗统计分布。
更好和更快的auto_increment处理。

1.MySQL最大的优势在于MyISAM引擎下的简单SELECT,INSERT和UPDATE快速操作
2.MyISAM类型的数据文件可以在不同操作系统中COPY,这点很重要,布署的时候方便点。

以下是一些细节和具体实现的差别:

1.InnoDB不支持FULLTEXT类型的索引。
2.InnoDB 中不保存表的具体行数,也就是说,执行select count(*) from table时,InnoDB要扫描一遍整个表来计算有多少行,但是MyISAM只要简单的读出保存好的行数即可。注意的是,当count(*)语句包含 where条件时,两种表的操作是一样的。
3.对于AUTO_INCREMENT类型的字段,InnoDB中必须包含只有该字段的索引,但是在MyISAM表中,可以和其他字段一起建立联合索引。
4.DELETE FROM table时,InnoDB不会重新建立表,而是一行一行的删除。
5.LOAD TABLE FROM MASTER操作对InnoDB是不起作用的,解决方法是首先把InnoDB表改成MyISAM表,导入数据后再改成InnoDB表,但是对于使用的额外的InnoDB特性(例如外键)的表不适用。

另外,InnoDB表的行锁也不是绝对的,如果在执行一个SQL语句时MySQL不能确定要扫描的范围,InnoDB表同样会锁全表,例如update table set num=1 where name like “%aaa%”

以暂对存储引擎的认识,觉得 InnoDB 支持外键,在数据量可以用“庞大”来形容时,在有良好的 INDEX 的基础上,InnoDB 的查询速度应该比 MyISAM 要快。
在 Falcon 有稳定版本前,我想 MyISAM 是一个可用的选择方案。

任何一种表都不是万能的,只用恰当的针对业务类型来选择合适的表类型,才能最大的发挥MySQL的性能优势。

Tuesday, December 16, 2008

PHP > Postal Code Distance Calculator

 

Two very simple functions that work together to calculate the distance between two different Canadian postal codes.

Alternatively you could pay $600 for a database of all Canadian postal codes with longitudes and latitudes, but I don't think that's why your reading this page. This method is easy to implement, connects to a constantly updated database, and did I mention--it's free?

How it works

The one function, "getCoord", uses Google Maps API's XML feed to fetch the longitude and latitude of each postal code, the second, "calcDistance", does some freaky math to calculate the distance in kilometers.

DON'T FORGET: You'll need to signup to use the Google Maps API at http://www.google.com/apis/maps/signup.html before you can use the getCoord function. Take the key Google gives you and store it in the constant 'KEY'.

Drawbacks

I used to use GeoCoder do the same calculations which only allowed for 100 lookups a day. Now with Google we have a 50,000 lookup limit. Which I doubt you'll ever surpass.

<?php
/**
* GETCOORD
* Uses Google Maps to resolve the coordinates of a postal code

* @param   String   $postal   Postal code to lookup
* @return  Array    Returns array with latitude and longitude
* @return  Boolean  False if an error occurred
*/
// ---- | REQUIRED GOOGLE MAPS KEY | --------
// Get yours here: http://www.google.com/apis/maps/signup.html
define('KEY', '---fill me in---');
function getCoord($postal)
{
$d = file_get_contents('http://maps.google.com/maps/geo?q=' . $postal . '&output=xml&key=' . KEY);
    if (!$d)
        return false; // Failed to open connection
$coord = new SimpleXMLElement($d);
    if ((string) $coord->Response->Status->code != '200')
        return false; // Invalid status code
list($lng, $lat) = explode(',', (string) $coord->Response->Placemark->Point->coordinates);
    return array('Lat' => (float) $lat, 'Lng' => (float) $lng); 
}
/**
* CALCDISTANCE
* Calculates the distance between to postal codes

* @param   String   $postal1   Starting postal code
* @param   String   $postal2   Ending postal code
* @return  Float    Returns distance in kilometers
* @return  Boolean  False if an error occurred
*/
function calcDistance($postal1, $postal2)
{
$dst1 = getCoord($postal1);
$dst2 = getCoord($postal2);
    if (!$dst1 or !$dst2)
        return false; // Invalid postal codes
$kms = rad2deg(acos(sin(deg2rad($dst1['Lat'])) * sin(deg2rad($dst2['Lat'])) +  
cos(deg2rad($dst1['Lat'])) * cos(deg2rad($dst2['Lat'])) * 
cos(deg2rad($dst1['Lng'] - $dst2['Lng'])))) * 60 * 1.1515 * 1.609344; 
    return $kms;
}
echo calcDistance('r3g3j6', 'r0c3e0') . ' kms'; // prints 37.... kms
?>

Saturday, December 13, 2008

加拿大信用卡的选择

在加拿大,每个银行无一例外都有信用卡业务,对於很多人来说,其实并不清楚每个信用卡的功能和不同,也不知道在何种情况下使用哪张信用卡为好,其实,信用卡不用求多,但求精和有互补性。

选择信用卡有以下关键因素:

1。有无年费

2。是否攒点或现金回扣

3。有无购物保修期延长和90天失窃保险

4。有无租车优惠和租车期保险

5。有无特色业务,如ERS(Emergency Road Service)拖车服务

6。有无旅行人身保险

以上各点均非常重要,如果不知道,也就白白放弃了自己的利益。

有年费的卡,当然包含的东西会多一点,如攒点或现金回报率高一点,保险多一点,但是,其实有太多东西是免费卡都有的,只是免费卡有这没那,而年费卡保含面广,用年费卡的前提是你必须是大款,月洒万金不足惜的那种,否则,年费都挣不回来。其实,根本在於信用卡公司在免费卡上赚的少,所以没钱买更多的保险给用户,但是,我们完全可以通过组合互补来得到,所以,笔者只推荐免费卡。

除了年费,第二重要的是有无攒点或现金回扣,此类回报卡的特点是你可以在任何地方消费,如买菜,看电影,都可在卡上得回扣。

根据经验,现金回扣更实在,而攒点的回报率普遍极低,并且换取实物时的点数不可控制,一句话,骗人的招术。合理的办法是申请一个现金回扣信用卡和该公司或商店的一个纯计点卡,如ESSO,AIR MILE,HBC 等等, 用了信用卡得了回扣后再同时用计点卡攒点, 当然,点数是少了点,但当场有现金实惠,总利益远达于记点信用卡。

对於现金回扣的信用卡,目前基本上都是“UP TO 1%”, 这其实和真正的1%回扣是有很大区别的,前者基本是要花费3000元以后才开始有1%回报。真正的零元起的回报只有三家,CANADIANTIRE 的OPTION MASTER卡(1%),CAA MASTER卡(1%)和 TD BANK 的GM卡(3%),但限制是OPTION里攒的回扣钱必须在CANADIANTIRE用,CAA MASTER卡里攒的回扣钱必须用在年费,剩下的用的地方很少,而GM卡有3%,但卡里攒的回扣钱必须只能在买GM公司车的时候用。综合而论,OPTION是最好的选择,因为CANADIANTIRE回扣高,东西多,实在没啥买的时候,换换机油,买买钓鱼证都可以。

  许多人不知道,用某类信用卡买任何东西,如电器,家具等等,信用卡自动为所买的东西延长了保修期,一般为保修期翻倍,直至最多一年,例如,这样东西原有3个月保修,那么用信用卡买就有6个月,如果原有3年保修,那么用信用卡买就有4年。这很重要,因为最后一年的损坏机率最大。伴随着保修期延长的,大多是90天失窃和全毁保险,如果你买俩新自行车,第二天给偷了,凭报案记录,信用卡会赔给你。所以你要注意你的卡有没有此保险。

  对於租车优惠和保险,很多信用卡有租车优惠,例如RBC的免年费金卡和白金卡,可以得到NATIONAL租车行的20%优惠,和其他租车行 10%优惠,TD等银行的一些信用卡都有。更重要的是,如果你的自己车保险不含租车保险的话,租车是要付每天十几块钱的保险费的,如果用此类信用卡,可以免一半,即自身保可免,对方车损保险不免。

  有些信用卡,有一些很好的选项,ERS(EMERGENCY ROADSIDE SUPPORT) 是最有意义的一个,相信很多人知道或者在用CAA的ERS,其实有一些其他MASTER 卡通过付费加入AUOT CLUB是可以得到相同或更好的服务的。TD SELECT 和OPTION 就可以,以下为比较列表:

年费- 服务次数- 回报- 公里 -特色待遇 -备注

  CAA $70--4次;--1%--5km--5%CouchTard加油,有其他联锁折扣店-- 回扣钱只能用在下一年的年费,剩下的可用地方很少,CouchTard有油加的很少,全岛41家,大多偏远,且用其他信用卡也 可得 2%,很多联锁店不用CAA卡也可以在 网下载 coupon
SELECT $40--6次--无 --20km--保险非常好
OPTION $56--3次和无限次去CanadianTire--1%--10km--一次免费换油,4张价值$15换机油券,免费补胎,在 CanadianTire买东西回扣率更高,在CanadianTire加汽油回扣率更高--回扣钱只能用在 CanadianTire,有油加的店也很少.

  最后是人身旅行保险,当然有比没有好,RBC的金卡白金卡和TD SELECT卡都达到最多50万的保险,飞机和开车的旅行均含。

所以,笔者经过比较所有银行的所有信用卡,“去伪存真” ,仔细分析后,觉得CANADIANTIRE OPTION(加上$56AUTO CLUB选项)+RBC金卡或TD SELECT+记点卡(AIR MILS,ESSO,HBC等)配合使用为最佳组合。当然,申请银行的信用卡不一定要有该银行的帐户才能申请。

Friday, December 5, 2008

Prototype and script.aculo.us终极揭秘 推荐序

推荐序

2005年对于Web开发来说是一个伟大的年份。在这一年中,有两项技术异军突起,一项是Ajax,另一项是Ruby on Rails。这两项技术的出现改变了Web开发的面貌,甚至打乱了JavaEE前进的步伐。多年以来,JavaEE设计者们为自己所设计的无所不包的复杂架构而陶醉,新的buzz word层出不穷,一出来就会得到广泛的关注,相关的图书也会热卖。辉煌的JavaEE版图中居然还有完全被忽略的死角,这是JavaEE设计者们始料不及的。事实上他们在JavaEE的架构中从来就没有考虑过浏览器端的处理能力,浏览器对于JavaEE来说是完全无智能的瘦客户端。他们这样设计情有可原,因为他们只能把设计局限在Java能够控制的区域内,在Java Applet彻底失败之后,Java能够控制的区域退守到了服务器端。毫无疑问,JavaEE取得了巨大的成功,但是这种完全基于服务器端的解决方案很难向用户提供优秀的交互体验,同时也很难实现最佳的可伸缩性(Scalability)。

Ajax是用户和市场的选择,这个由群众创造出来的buzz word时常响在JavaEE设计者的耳边,令他们非常烦躁。甚至有人喊出了"JavaEE without Ajax"的口号,和2004年Rod Johnson所提出的"J2EE without EJB"相对应。但是仔细考察这个"without Ajax",我们却发现它和"without EJB"说的并不是一个意思。"without EJB"的意思就是不使用EJB,而"without Ajax"的意思却不是不使用Ajax,而是设法将Ajax隐藏掉,使得普通的JavaEE开发者不需要学习Ajax。在这个口号的发明者所设计的JSF 框架中,内嵌了一个Ajax组件库ExtJS,看来他们还是需要Ajax的帮助的。所谓的"JavaEE without Ajax"其实是一个伪命题,只是用来吸引眼球的噱头,主要的目的是为了迎合那些讨厌Ajax、对于浏览器端脚本编程没有兴趣的JavaEE开发者。其实无论开发Web应用,还是开发B/S结构的企业应用,交互体验都是非常重要的。在交互体验越来越受到重视的今天,想要把Ajax排除在外,可以说是一件不可能的任务。

尽管Ajax开发确实很重要,但无须讳言的是Ajax开发,特别是DHTML开发至今仍然是一件很复杂的工作,必须具备专业的技能。除了高超的开发技能之外,还需要开发者对于Web可用性和交互设计具有足够的理解。这超出了Web页面设计师和制作人员的能力范围;而对于服务器端开发者来说,这些显然也不是他们的特长(他们的特长是处理业务逻辑和与数据库打交道,而不是界面开发和交互设计)。在Web页面设计师和服务器端开发者之间出现了巨大的断层,这使得高水平的Ajax开发者一下子变得炙手可热。

JavaEE设计者们简化Ajax开发的努力是可以理解的。但是我们需要讨论清楚一个问题:究竟怎样做才能真正简化Ajax开发?

在我看来,JavaEE社区简化Ajax开发的努力都不是很成功。将ExtJS嵌入到JSF框架中,确实可以实现一些简单的交互需求,但是这种做法其实阉割掉了Ajax的主要能力--直接在浏览器端处理用户的事件。JSF的事件模型是完全位于服务器端的,界面的任何改变都需要发送事件到服务器端做处理。即使引入了ExtJS,也不可能改变这一点。在Struts2/WebWork和jMaki中通过使用taglib集成Ajax组件库的做法也会令人感觉笨拙而不自然。之所以集成的效果不理想,是因为传统的JavaEE框架和Ajax组件库的核心架构设计都没有考虑到对方的需求,它们在架构设计上存在着内在的冲突,拉郎配的结果是可想而知的。这些JavaEE表现层框架集成Ajax组件库,是希望将Ajax组件库改造为JavaEE世界的顺民,服从 JavaEE框架的架构约束,但是这样做不可避免地会削弱Ajax组件库的能力。要简化Ajax开发并且充分用好Ajax技术,仅仅使用taglib来对 Ajax组件库做封装是不够的,服务器端的架构也必须加以改造,甚至需要设计出一套全新的架构,这是传统的JavaEE表现层框架无法做到的。 Google的GWT同时具有浏览器端和服务器端,似乎是一个很好的选择,但是GWT主要是为实现一类One Page的Ajax应用而设计的,它模仿的是桌面应用的交互模型,而绝大多数的Web应用是不可能采用这种交互模型的。DWR也是一个跨浏览器端和服务器端的框架,但是DWR的RPC风格的API是我所不喜欢的。RPC架构和REST架构在可伸缩性方面的差距是巨大的,Ajax应用的最佳架构是REST而不是RPC,同时REST在简化编程模型方面的效果也要比RPC更好。
要简化Ajax开发,我认为需要满足以下3个要求:

1. 由专业的DHTML开发者开发出更加全面和强大的Widget库。这部分的开发应该由专业人士来做,这样就可以把负担从普通的Web开发者身上卸掉。开源软件是开发Widget库的必由之路。

2. Ajax组件库要与服务器端框架做更加紧密的集成。服务器端框架的架构需要加以改造以适应Ajax和REST的需求,不能指望以不变应万变。

3. 简化的编程模型。这包括两个方面:a)通过与服务器端框架紧密集成,使得服务器端开发者仅仅使用服务器端编程语言就能够实现一些普通的Ajax需求;b)更加复杂的交互需求如果必须要做DHTML开发,需要大幅降低DHTML开发的难度。

当我把眼光投向了Ruby on Rails之后,包括Ajax开发在内,一切都变得容易了很多。Rails通过其RJS模板集成了两个Ajax组件库Prototype和基于 Prototype的script.aculo.us。Rails集成这两个组件库的方式显得非常自然,因为这两个组件库正是Rails和Ajax相结合的产物,它们都是由Rails的核心开发人员所开发的,充分考虑到了Rails在架构方面的需求。现在这两个组件库已经长大成人,完全可以独立应用在非 Rails环境中。Rails + Prototype/script.aculo.us满足了上述3个要求,因此是目前做Ajax开发的最佳组合。当然,上述3个要求每一个都还有很大的改进余地,但是Rails + Prototype/script.aculo.us走在了正确的道路上,前景是非常光明的。

随着Ajax技术的普及,传统的DHTML组件库恢复了生机,可以加以改造以适应新的交互需求。这些DHTML组件获得了一个时髦的新名称--Widget。我一般是将Ajax组件库分成3部分:

1. 基础库。包括:a)对于JavaScript语言的增强,为目前浏览器所支持的JavaScript 1.x版提供继承、包管理等面向对象编程所需要的支持;b)封装HTML DOM的API,提供编程模型更加简化的API。

2. Web Remoting库,通过XMLHttpRequest/IFrame/JSONP等机制与服务器交互,从服务器获取数据。

3. 丰富的Widget库,可用来实现各种交互需求,以改善用户的交互体验。

Prototype库实现了第1部分和第2部分,script.aculo.us库实现了第3部分。在这3部分中,开发工作量最大的是Widget 库。正是因为市场上对于Widget库有着巨大的需求,在2007年涌现出了大量Widget库,开源的包括script.aculo.us、Dojo、 YUI、ExtJS、Mootools、jQuery UI、Spry、Tibco GI、Qooxdoo;私有的包括ASP.NET Ajax、Dorado、Zimbra,等等,全部列举下来不下二三十个。Apple公司在这一年推出了iPhone手机,其中的Widget全部是使用 DHTML开发的,达到了交互体验的极致,同时也展示出Widget开发的美好前景。很多人说2007年是Widget年,这个说法是有道理的。到了 2008年,Google公司推出了Open Social,其核心是Google所提供的Widget开发API和Widget的部署容器。Google管这些Widget叫做Google Gadget,Gadget将Widget的开发水平推向了新的高度。

无论是基于现有的Widget开发Ajax功能,还是开发新的Widget,Prototype/script.aculo.us都是我们手中的一件利器。网上的很多投票都显示出,Prototype/script.aculo.us组合是最受欢迎的Ajax组件库。因为Prototype大受欢迎,其爱好者基于Prototype还开发了很多其他的组件库,Prototype家族成员的数量还在不断增加。很多Ajax组件库最大的问题是缺乏详尽的文档,API参考并不能代替文档。从文档的数量和质量来说,Prototype/script.aculo.us的文档在所有Ajax组件库中都是最棒的。本书的出版,为爱好者深入学习这两个组件库提供了极大的便利。本书的副标题是"You Never Know JavaScript Could Do This!",确实,正是通过这些优秀的Ajax组件库、这些勇于探索的Ajax顶尖高手们,我们才知道浏览器端脚本编程的世界原来是如此灿烂。基于 Prototype/script.aculo.us做Ajax开发是一种很愉快的体验。学习越深入,这种愉快的感觉就越强烈。那么,你还在等什么呢?

上海印客网首席架构师

2008年7月6日于上海

《JavaScript王者归来》序

       在人类漫漫的历史长河里,很难找到第二个由简单逻辑和抽象符号组合而成的,具有如此宏大信息量和丰富多彩内涵的领域。从某种意义上说,当你翻开这本书的时候,你已经踏入了一个任由你制定规则的未知世界。尽管你面对的仅仅是程序设计领域的冰山一角,但你将透过它,去领悟“道”的奥秘。在接下来的一段时间内,你会同我一起,掌握一种简单而优雅的神秘语言,学会如何将你的意志作用于它。这种语言中所蕴涵着的亘古之力,将为你开启通往神秘世界的大门……

1.1为什么选择JavaScript?

       在一些人眼里,程序设计是一件神秘而浪漫的艺术工作,对他们来说,一旦选定某种编程语言,就会像一个忠贞的信徒一样坚持用它来完成任何事情,然而我不是浪漫的艺匠,大多数人也都不是,很多时候我们学习一种新技术的唯一目的,只是为了把手中的事情做得更好。所以,当你面对一项陌生的技术时,需要问的第一个问题往往是,我为什么选择它,它对我来说,真的如我所想的那么重要吗?

好,让我们带着问题开始。

1.1.1 用户的偏好:B/S模式

       如果你坚持站在专业人员的角度,你就很难理解为什么B/S模式会那么受欢迎。如果你是一个资深的程序员,有时候你甚至会对那些B/S模式的东西有一点点反感。因为在你看来,浏览器、表单、DOM和其他一切与B/S沾边的东西,大多是行为古怪而难以驾驭的。以你的经验,你会发现实现同样的交互,用B/S来做通常会比用任何一种客户端程序来做要困难得多。

       如果你尝试站在用户的角度,你会发现为什么大多数最终用户对B/S模式却是如此的青睐。至少你不必去下载和安装一个额外的程序到你的电脑上,不必为反复执行安装程序而困扰,不必整天被新的升级补丁打断工作,不必理会注册表、磁盘空间和一切对普通用户来说有点头疼的概念。如果你的工作地点不是固定的办公室,你日常工作的PC也不是固定的一台或者两台,那么,B/S的意义对你而言或许比想象的还要大。

大多数情况下,客户更偏好使用浏览器,而不是那些看起来比较专业的软件界面,专业人员则恰恰相反。

       总之,用户的需求让B/S模式有了存在的理由,而迅速发展的互联网技术则加速了B/S应用的普及。随着一些优秀的Web应用产品出现,不但唤起了用户和业内人士对Ajax技术的关注,也令Web领域内的一个曾经被无数人忽视的脚本语言——JavaScript进入了有远见的开发人员和IT经理人的视线。于是在你的手边,也多了现在这本教程。

编写本书的时候,在TIOBE编程社区最新公布的数据中,JavaScript在世界程序开发语言中排名第十,这意味着JavaScript已经正式成为一种被广泛应用的热门语言。

1.1.2 在什么情况下用JavaScript

       一路发展到今天,JavaScript的应用范围已经大大超出一般人的想象,但是,最初的JavaScript是作为嵌入浏览器的脚本语言而存在,而它所提供的那些用以表示Web浏览器窗口及其内容的对象简单实用,功能强大,使得Web应用增色不少,以至于直到今天,在大多数人眼里,JavaScript 表现最出色的领域依然是用户的浏览器,即我们所说的Web应用的客户端。客户端浏览器的JavaScript应用也正是本书讨论的重点内容。

作为一名专业程序员,当你在面对客户的时候,经常需要判断哪些交互需求是适合于JavaScript来实现的。而作为一名程序爱好者或者是网页设计师,你也需要了解哪些能够带给人惊喜的特效是能够由JavaScript来实现的。总之一句话,除了掌握JavaScript本身,我们需要学会的另一项重要技能是,在正确的时候、正确的地方使用JavaScript。对于JavaScript初学者来说学会判断正确使用的时机有时候甚至比学会语言本身更加困难。

作为项目经理,我经常接受来自客户的抱怨。因此我很清楚我们的JavaScript在带给客户好处的同时制造了太多的麻烦,相当多的灾难是由被错误使用的 JavaScript引起的。一些代码本不应该出现在那个位置,而另一些代码则根本就不应当出现。我曾经寻访过问题的根源,发现一个主要的原因由于 JavaScript的过于强大(在后面的小节中我们将会提到,另一个同样重要的原因是“脚本诱惑”),甚至超越了浏览器的制约范围,于是麻烦就不可避免的产生了,这就像你将一个魔鬼放入一个根本就不可能关住它的盒子里,那么你也就无法预料魔鬼会做出任何超出预期的举动。

*  毫无疑问,正确的做法是:不要放出魔鬼。所以,JavaScript程序员需要学会的第一个技巧就是掌握在什么情况下使用JavaScript才是安全的。

       在什么情况下用JavaScript?给出一个简单的答案是:在任何不得不用的场合使用,除此以外,不要在任何场合使用!无懈可击的应用是不用,除非你确实无法找到一个更有效更安全的替代方案。也许这个答案会让读到这里的读者有些郁闷,但是,我要很严肃地提醒各位,由于JavaScript比大多数人想象的要复杂和强大得多,所以它也比大多数人想象得要危险得多。在我的朋友圈子里,许多资深的JavaScript程序员(包括我在内)偶尔也不得不为自己一时疏忽而做出的错误决定让整个项目团队在“脚本泥潭”中挣扎好一阵子。所以这个建议从某种意义上说也是专家们的血泪教训。最后向大家陈述一个令人欣慰的事实,即使是像前面所说的这样,在Web应用领域,JavaScript的应用范围也仍然是相当广泛的。

Ø         在本节的最后三个小节里,我们将进一步展开讨论关于JavaScript使用的话题。

1.1.3 对JavaScript的一些误解

       JavaScript是一个相当容易误解和混淆的主题,因此在对它进一步研究之前,有必要澄清一些长期存在的有关该语言的误解。

1.1.3.1 JavaScript和Java

这是最容易引起误会的一个地方,这个Java-前缀似乎暗示了JavaScript和Java的关系,也就是JavaScript是Java的一个子集。看上去这个名称就故意制造混乱,然后随之而来的是误解。事实上,这两种语言是完全不相干的。

JavaScript和Java的语法很相似,就像Java和C的语法相似一样。但它不是Java的子集就像Java也不是C的子集一样。在应用上,Java要远比原先设想的好得多(Java原称Oak)。

JavaScript 的创造者是Brendan Eich,最早的版本在NetScape 2中实现。在编写本书时,Brendan Eich在Mozilla公司任职,他本人也是JavaScript的主要革新者。而更加有名的Java语言,则是出自Sun Microsystems公司的杰作。

JavaScript最初叫做LiveScript,这个名字本来并不是那样容易混淆,只是到最后才被改名为JavaScript,据说起同Java相似的名字纯粹是一种行销策略。

尽管JavaScript和Java完全不相干,但是事实上从某种程度上说它们是很好的搭档。JavaScript可以控制浏览器的行为和内容,但是却不能绘图和执行连接(这一点事实上并不是绝对的,通过模拟是可以做到的)。而Java虽然不能在总体上控制浏览器,但是却可以绘图、执行连接和多线程。客户端的JavaScript可以和嵌入网页的Java applet 进行交互,并且能够对它执行控制,从这一意义上来说,JavaScript真的可以脚本化Java。

1.1.3.2披着C外衣的Lisp

JavaScript的C风格的语法,包括大括号和复杂的for 语句,让它看起来好像是一个普通的过程式语言。这是一个误导,因为JavaScript和函数式语言如Lisp和Scheme有更多的共同之处。它用数组代替了列表,用对象代替了属性列表。函数是第一型的。而且有闭包。你不需要平衡那些括号就可以用λ算子。

Ø         关于JavaScript闭包和函数式的内容,在本书的第23章中会有更详细的介绍。

1.1.3.3思维定势

JavaScript 是原被设计在Netscape Navigator 中运行的。它的成功让它成为几乎所有浏览器的标准配置。这导致了思维定势。认为JavaScript是依赖于浏览器的脚本语言。其实,这也是一个误解。 JavaScript也适合很多和Web无关的应用程序。

早些年在学校的时候,我和我的实验室搭档曾经研究过将JavaScript作为一种PDA控制芯片的动态脚本语言的可行性,而在我们查阅资料的过程中发现一些对基于嵌入式环境的动态脚本语言实现的尝试,我们有理由相信,JavaScript在某些特定的嵌入式应用领域中也能够表现得相当出色。

1.1.3.4业余爱好者

一个很糟糕的认知是:JavaScript过于简朴,以至于大部分写JavaScript的人都不是专业程序员。他们缺乏写好程序的修养。JavaScript有如此丰富的表达能力,他们可以任意用它来写代码,以任何形式。

事实上,上面这个认知是曾经的现实,不断提升的Web应用要求和Ajax彻底改变了这个现实。通过学习本书,你也会发现,掌握JavaScript依然需要相当高的专业程序员技巧,而不是一件非常简单的事情。不过这个曾经的现实却给JavaScript带来了一个坏名声──它是专门为外行设计的,不适合专业的程序员。这显然是另一个误解。

推广JavaScript最大的困难就在于消除专业程序员对它的偏见,在我的项目团队中许多有经验的J2EE程序员却对JavaScript停留在一知半解甚至茫然的境地,他/她们不愿意去学习和掌握JavaScript,认为这门脚本语言是和浏览器打交道的美工们该干的活儿,不是正经程序员需要掌握的技能。这对于Web应用开发来说,无疑是一个相当不利的因素。

1.1.3.5面向对象

JavaScript 是不是面向对象的?它拥有对象,可以包含数据和处理数据的方法。对象可以包含其它对象。它没有类(在JavaScript 2.0真正实现之前),但它却有构造器可以做类能做的事,包括扮演类变量和方法的容器的角色。它没有基于类的继承,但它有基于原型的继承。两个建立对象系统的方法是通过继承和通过聚合。JavaScript两个都有,但它的动态性质让它可以在聚合上超越。

一些批评说JavaScript不是真正面向对象的因为它不能提供信息的隐藏。也就是,对象不能有私有变量和私有方法:所有的成员都是公共的。但随后有人证明了JavaScript对象可以拥有私有变量和私有方法。另外还有批评说JavaScript不能提供继承,但随后有人证明了JavaScript不仅能支持传统的继承还能应用其它的代码复用模式。

说 JavaScript是一种基于对象的语言,是一种正确而略显保守的判断,而说JavaScript不面向对象,在我看来则是错误的认知。事实上有充足的理由证明JavaScript是一种的面向对象的语言,只是与传统的class-based OO(基于类的面向对象)相比,JavaScript有它与众不同的地方,这种独特性我们称它为prototype-based OO(基于原型的面向对象)。

Ø         关于JavaScript面向对象的内容,在本书的第21章中会有更详细的介绍。

1.1.3.6其他误解

       除了以上提到的几点之外,JavaScript还有许多容易令人迷惑和误解的特性,这些特性使得JavaScript成为世界上最被误解的编程语言。

Ø         如果读者对这方面有兴趣,可以详细阅读下面这篇文章

http://javascript.crockford.com/javascript.html  [Douglas Crockford]

1.1.4 警惕!脚本诱惑

       前面我们提到过,许多专业程序员拒绝去了解如何正确使用JavaScript,另一些则是缺乏对JavaScript足够的认知和应用经验。但是在B/S 应用中,相当多的情况下,要求开发人员不得不采用JavaScript。于是,一个问题产生了,大量的JavaScript代码拷贝出现在页面的这个或者那个地方,其中的大部分是不必要的,另一部分可能有缺陷。我们的开人员没有办法(也没有意识到)去判断这些代码是否必要,以及使用它们会带来哪些问题。

*  如果你的B/S应用中的JavaScript不是由专业的JavaScript程序员来维护的,那么当你对你的开发团队进行一次小小的代码走查时,你甚至可能会发现90%的JavaScript代码被错误地使用,这些错误使用的代码浪费了用户大量的网络带宽、内存和CPU资源,提升了对客户端配置的要求,降低了系统的稳定性,甚至导致许多本来可以避免的安全问题。

       由于浏览器的JavaScript可以方便地被复制粘贴,因此,一个特效或者交互方式往往在真正评估它的必要性之前便被采用——客户想要它,有人使用过它,程序员复制它,而它就出现在那儿,表面上看起来很完美,于是,所谓的脚本诱惑就产生了。

       事实上,在我们真正使用JavaScript之前,需要反复问自己一个重要问题是,究竟是因为有人想要它,还是因为真正有人需要它。在你驾驭 JavaScript马车之前,你必须学会抵制脚本诱惑,把你的脚本用在必要的地方,永远保持你的Web界面简洁,风格一致。

*  在用户眼里,简洁一致的风格与提供强大而不常用的功能和看起来很COOL而实际上没有什么功用的界面特效相比起来,前者更能令他们觉得专业。毕竟,大部分用户和你我一样,掌握一个陌生的环境和新的技能只是为了能够将事情做得更快更好。除非你要提供的是一个类似于Qzone之类的娱乐程序,你永远也不要大量地使用不必要的JavaScript。

1.1.5 隐藏在简单表象下的复杂度

       专业人员不重视JavaScript的一个重要原因是,他们觉得JavaScript是如此的简单,以至于不愿意花精力去学习(或者认为不用学习就能掌握)。前面提到过的,这实际上是一种误解。事实上,在脚本语言中,JavaScript属于相当复杂的一门语言,它的复杂程度未必逊色于Perl和 Python。

另一个业内的偏见是脚本语言都是比较简单的,实际上,一门语言是否脚本语言往往是它的设计目标决定的,简单与复杂并不是区分脚本语言和非脚本语言的标准。JavaScript即使放到非脚本语言中来衡量,也是一门相当复杂的语言。

       之所以很多人觉得JavaScript过于简单,是因为他们大量使用的是一些JavaScript中看似简单的文法,解决的是一些看似简单的问题,真正复杂而又适合JavaScript的领域却很少有人选择JavaScript,真正强大的用法很少被涉及。JavaScript复杂的本质被一个个简单应用的表象所隐藏。

       我曾经给一些坚持认为JavaScript过于简单的开发人员写过一段小代码,结果令他们中的大部分内行人大惊失色,那段代码看起来大致像下面这个样子:

var a = [-1,-1,1,-3,-3,-3,2,2,-2,-2,3,-1,-1];

function f(s, e)

{

       var ret = [];

       for(var i in s){

              ret.push(e(s[i]));

       }

       return ret;

}

var b = f(a, function(n){return n>0?n:0});

alert(b);

*  这是本书中出现的第一段JavaScript代码,也许现在你看来,它有那么一点点令人迷惑,但是不要紧,在本书后面的章节中,你会慢慢理解这段代码的含义以及它的无穷妙味。而现在你完全可以跳过它的实际内容,只要需要知道这是一段外表看起来简单的魔法代码就够了。

       因为这段代码而尖叫的不仅仅包括我的这些程序员朋友,事实上,更兴奋的是另一些电子领域的朋友,他们写信给我反馈说,在此之前他们从来没有见到过如此形式简洁而优雅的数字高通滤波器,更令人欣喜的是,它的阈值甚至是可调节的:

var b = f(a, function(n){return n>=-1?n:0});

如果你想要,它也很容易支持低通滤波:     

var b = f(a, function(n){return n<0?n:0});

       用一个小小的堆栈或者其他伎俩,你也可以构造出一族差分或者其他更为复杂的数字设备,而它们明显形式相近并且结构优雅。

       总之,不要被简单的表象所迷惑,JavaScript的复杂度往往很大程度上取决于你的设计思路和你的使用技巧。JavaScript的确是一门可以被复杂使用的程序设计语言。

1.1.6 令人迷惑的选择:锦上添花还是雪中送炭

       本节最后的这个话题在前面已经被隐讳地提到过多次,实际上,本小节围绕的话题依然是什么时候使用JavaScript。一种比较极端的观点是在必须的时候采用,也就是前面所说的不得不用的场合,另一种比较温和一点的观点则坚持在需要的时候使用,这种观点认为当我们可以依靠JavaScript令事情变得更好的时候,我们就采用它。

事实上,就我个人而言,比较支持“必须论”,这是因为从我以往的经验来看,JavaScript是难以驾驭的,太多的问题由使用JavaScript不当而产生,其中的一部分相当令人困扰,彻底解决它们的办法就是尽可能降低JavaScript的使用频率,也尽可能将它用在真正适合它的地方。当然万事没有绝对,在何时使用 JavaScript永远是一个难题,然而不管怎么说同“锦上添花”相比,JavaScript程序员也许应当更多考虑的是如何“雪中送炭”。

1.1.7回到问题上来

       本节要解决的问题是为什么选择JavaScript,然而在相当多的篇幅里,我们都在试图寻找一些少用和不用JavaScript的理由,尽管如此,抛开大部分不适合JavaScript的位置和时机,浏览器上依然会经常地见到JavaScript的身影,对于浏览器来说,JavaScript实在是一个不可缺少的修饰。

你再也找不到任何一种优雅简朴的脚本语言如此适合于在浏览器中生存。在本书的第2章,我们将具体接触嵌入浏览器中的JavaScript。

       最后,用一句话小结本节的内容——我们之所以选择JavaScript,是因为:Web应用需要JavaScript,我们的浏览器、我们的程序员和我们的用户离不开它。

为什么Applet不会回来

我想知道为什么Javascript能够战胜Applet,找到了如下答案:

From: http://www.javaeye.com/topic/208508

     在上个世纪90年代,sun和微软的关系曾经十分甜蜜,当时的applet也是前景一片光明。然而随后sun选择了挑战微软,除了指控微软向java添加非平台中立的特性以外,在Java1.2中,sun加入了平台无关的UI界面Swing.而也正是从java1.2开始,这个星球上最大的浏览器厂商决定对Java applet进行抵制,微软的jvm版本再没有超过1.1。而java1.2之后sun的jvm在ie上的拙劣表现,也最终导致applet的陨落。

      来到21世纪,sun想要通过新的java 6 update 10和JavaFX重夺RIA市场。然而这样的尝试最终也会失败,这与applet,flash,silverlight这些技术无关,applet终将失败的原因是:sun不是一个足够强大的公司,而这一点是毫无悬念的。

Ted Neward 的话,我觉得有一定道理,但是,基于很多原因(真的有很多),我认为Applet(JavaFX)还是有机会的。当然因为我本身对Java很有感情,所以也许主观了吧。

Thursday, December 4, 2008

cakephp+AJAX打造多级动态树形菜单~~

说是用cakephp,其实也没用到cakephp的ajax helper,只是喜欢cakephp的MVC和ORM功能~

敏捷开发日益被人关注~比起JAVA的struts、hibernate无比复杂的配置文件,cakephp的mvc和orm功能仅需要满足它的一些约定就行了~就像RoR的“约定大于配置”一样~

都说ajax是过渡时期的产品,我觉得很奇怪,ajax不就只是一个XMLHttpRequest么?难道JAVASCRIPT对DOM的操作也属于AJAX?如果是这样的话AJAX怎么会只是过渡时期的产品?

不说废话了,来看看我们的AJAX+CAKEPHP多级动态树形菜单吧~~

先建立数据库表,mysql下

DROP TABLE IF EXISTS `categorys`;
CREATE TABLE IF NOT EXISTS `categorys` (
`id` int(10) unsigned NOT NULL auto_increment,//主键
`parentid` int(10) unsigned NOT NULL,//树形菜单,父结点ID号
`path` varchar(200) NOT NULL,//访问路径
`ordernum` int(11) NOT NULL,//排序号,可能用得上
`subscount` int(10) unsigned NOT NULL,//子结点个数
`name` varchar(15) NOT NULL,//结点名字
`contentable` tinyint(1) NOT NULL,//该结点下是否有非结点内容标记
`workable` tinyint(1) NOT NULL,//该结点是否工作标记
PRIMARY KEY  (`id`),
UNIQUE KEY `path` (`path`),
UNIQUE KEY `name` (`name`)
) ENGINE=MyISAM  DEFAULT CHARSET=gbk AUTO_INCREMENT=53 ;

将树形菜单存放数据按上面字段注释的要求填入~

下面这个是前台代码:

<script src=”../js/prototype.js”></script>
<script type=”text/javascript”>
var childNodeId;
var childValue;
function gettype(nodeId,nodeValue){
childNodeId = nodeId;
childValue = nodeValue;
var temp;
temp=$(”node”+nodeValue+”"+nodeId).innerHTML;
if (temp==”"){
$(”node”+nodeValue+”"+nodeId).innerHTML=”<span align=\”center\”><img src=’../img/common/tree/load.gif’ />    数据读取中…</span><br>”;
getChildTree();
}
else {
showHide();
}
}

function getChildTree(){
var url = “/admin/listcates/”+childNodeId+”?timestamp=”+new Date().getTime();

var myAjax = new Ajax.Request(
url,{
method:”GET”,
onComplete: showResponse
}
);
}

function showResponse(xmlHttp){
var tmp = “node”+childValue+”"+childNodeId;
var tmpimg = “img”+childValue+”"+childNodeId;
$(tmp).innerHTML = xmlHttp.responseText;
$(tmpimg).src = “../img/common/tree/open.gif”;
}

function showHide(){
var tmp = “node”+childValue+”"+childNodeId;
var tmpimg = “img”+childValue+”"+childNodeId;
if($(tmp).style.display==”block” || $(tmp).style.display==”"){
$(tmp).style.display = “none”;
$(tmpimg).src=”../img/common/tree/close.gif”;
}
else{
$(tmp).style.display =”block”;
$(tmpimg).src=”../img/common/tree/open.gif”;
}
}

function addsubject(parentid){
var tmpvalue=prompt(’请输入新的分类名’,”);
if(tmpvalue){
var url = “/admin/newsubject/”+tmpvalue;
var pars = “parentid=”+parentid+”&timestamp=”+new Date().getTime();
var ajax = new Ajax.Request(
url,{
method: ‘get’,
parameters: pars,
onComplete:viewadd
}
);
}
}
function viewadd(xmlHttp){
alert(xmlHttp.responseText);
}

function delsubject(parentid){
var tmpvalue=confirm(’确定删除这个分类吗?’);
if(tmpvalue){
alert(parentid);
}
}
</script>
<table>
<div id=”treebody”>
<span id=”node00″></span>
<script language=”javascript”>gettype(0,0)</script>
</div>
</table>

后台主要代码如下:

function listcates($parentid){
$condition = array(’parentid’=>$parentid);
$db_cates = $this->Cate->findAll($condition);
//pr($db_cates);
header(’Content-Type:text/html;charset=GB2312′);
//$parentid+=1;
if($db_cates != null){
echo “<ul style=’list-style-type:none;’>”;
foreach($db_cates as $key=>$db_cate){
if($db_cate['Cate']['subscount']!=0){
$tmpimg = “/img/common/tree/close.gif”;
echo ”
<li onclick=’gettype({$db_cate['Cate']['id']},{$parentid})’>
<img id=’img{$parentid}{$db_cate['Cate']['id']}’ src=’{$tmpimg}’ />&nbsp;{$db_cate['Cate']['name']}

</li>
<div id=’node{$parentid}{$db_cate['Cate']['id']}’></div>
“;
}
else{
$tmpimg = “/img/common/tree/file.gif”;
echo ”
<li>
<img id=’img{$parentid}{$db_cate['Cate']['id']}’ src=’{$tmpimg}’ />&nbsp;{$db_cate['Cate']['name']}
&nbsp;<img src=’/img/common/tree/new.gif’ onclick=’addsubject({$db_cate['Cate']['id']})’/>
&nbsp;<img src=’/img/common/tree/del.gif’ onclick=’delsubject({$db_cate['Cate']['id']})’/>
</li>

“;
}
}
if($parentid!=0) echo “<li onclick=’addsubject({$parentid})’><img src=’/img/common/tree/add.gif’/>&nbsp;<small>增加分类< /small></li>”;
echo “</ul>”;
}
exit();
}

Wednesday, December 3, 2008

DOM example 2

<html>
<head>

<script type="text/javascript">
window.onload = function() {
var node = document.getElementById("aassdd");
node.onclick=getEventTrigger;
}

function getEventTrigger(evt)
  {

//  e = e¦¦event;
//alert("The source element's id is: "+ (e.srcElement¦¦e.target).id)

if(evt)
x= evt.target;
else //IE
x = window.event.srcElement;

//  x=e.srcElement¦¦event.target;

  alert("The id of the triggered element: "
  + x.id);
  x = x.nextSibling;
  x = x.lastChild;
  alert("The id of the triggered element: "
  + x.nodeValue);

  }
</script>

</head>
<body >

<p id="aassdd" onclick="getEventTrigger">Click on this paragraph. An alert box will show which element triggered the

event.</p><p id="aa" onclick="getEventTrigger">AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.</p><p id="dd"

onclick="getEventTrigger">Click on this paragraph. An alert box willshow which element triggered the event.</p>

</body>
</html>

DOM example 1

<html>
<head>
<title>Page</title>
<script>
window.onload = function() {
var node = document.getElementById("testimage");
node.onclick=testFunction;
}

function testFunction(evt) {

if(evt)
alert("evt.target.id="+evt.target.id);
else //IE
alert("window.event.srcElement.id="+window.event.srcElement.id);
}
</script>

</head>

<body>
<img id="testimage" src='someimage.jpg'/>
</body>
</html>

精通JavaScript

http://www.17xie.com/read-60506.html

在上一次Web开发领域的技术浪潮中,文档对象模型(Document Object Model,DOM)无疑是开发者能用来提升用户体验的最重要技术之一。

用DOM脚本编程为页面增加分离式(unobtrusive)JavaScript,既能为你的用户提供各种各样时髦的增效,同时又不会影响那些使用不支持或禁用了JavaScript的浏览器的人们。此外,DOM脚本编程还能精细地分离和轻松地管理你的代码。

幸运的是,所有的现代浏览器都支持DOM和当前HTML文档的内置HTML DOM表达。有了这些后就可以通过JavaScript轻松访问它们,从而为现代Web开发者提供了巨大的便利。理解如何使用并尽情发挥这些技术,你的下一个Web应用程序将会领先一筹。

这一章探讨关于DOM的一系列话题。假定你是DOM新手,我们将从基础开始,并涉及所有重要的概念。而对已经熟悉了DOM的读者,我保证能提供一批你会喜欢的高级技术。

2.1 DOM简介

DOM是一个表达XML文档的标准(由W3C制定的)。它未必是最快的方式,也未必是最轻量级的或者最容易使用的,但确是应用最广泛的,大部分Web开发的编程语言(比如Java、Perl、PHP、 Ruby、Python和JavaScript)都提供了相应的DOM实现。DOM给开发者提供了一种定位XML层级结构的直观方法。就算你尚未完全熟悉 XML也没关系,HTML文档(实际上从浏览器角度来说就是XML文档)已经有一个可用的DOM表达,你会为此而备受鼓舞。

 

 

2.2 遍历DOM

可以把XML的DOM表达方式看作是一棵导航树。一切术语都跟家谱术语(父、子、兄弟)类似。而与家谱的不同之处在于,XML文档从一个独立的根节点(称作文档元素,document element)开始,它包含指向子节点的指针。每一个子节点都包含指针指向它的父节点、相邻节点和子节点。

DOM还使用了一些特殊的术语来描述XML树里的对象种类。DOM树中的每个对象都是一个节点(node),每个节点可以有一个类型(type),比如元素、文本或者文档。要进一步学习下去,我们必须了解 DOM文档是如何表现和如何定位的。请参考以下简单的HTML片段,让我们来研究研究它的DOM结构是如何工作的:

<p><strong>Hello</strong> how are you doing?</p>

这个片段的每一部分被分解为独立的DOM节点指针,指向其亲戚(父、子、兄弟)。使用图谱来表示的话,它应该如图5-1所示:片断的每部分(圆角框描述元素,直角框描述文本节点)和它的引用。

图5-1 节点间的关系

每个独立的DOM节点都包含指向它的相关节点的一系列指针。你需要使用这些指针来学习如何遍历DOM。所有可用的指针如图5-2所示。DOM节点指针的各个属性,分别是指向另一个DOM元素的指针(如果不存在则为null)。

图5-2 使用指针遍历DOM树

只需使用不同的指针,就可以定位页面上的任何元素或者文本块。要理解它在实际环境中是如何工作的,最好的方式就是实践一下。代码清单5-1是一个简单的HTML页。

代码清单5-1 简单HTML页面,或者说是简单的XML文档

    <title>Introduction to the DOM</title>

    <h1>Introduction to the DOM</h1>

    <p class="test">There are a number of reasons why the

DOM is awesome, here are some:</p>

    <ul>

      <li id="everywhere">It can be found everywhere.</li>

      <li class="test">It's easy to use.</li>

      <li class="test">It can help you to find what you want, really quickly.</li>

    </ul>

在这个文档中,根元素是

document.documentElement

根节点与其他DOM节点一样,同样拥有所有用于定位的指针。使用这些指针你就可以浏览整个文档,定位到任何一个你要找的元素。比如,要获取<h1>元素,你可能会使用如下方法:

//不起作用

document.documentElement.firstChild.nextSibling.firstChild

这是我们遇到的第一个难题:DOM指针不仅可以指向元素,也可以指向文本节点。在这里,代码并不会真正指向<h1>元素,而是指向了<title>元素。为什么会发生这样的事?原因在于XML存在一个有争议的地方:空格。或许你已经注意到,在

l    当试图只使用指针来遍历DOM时,精细编写的HTML标记居然会产生混乱。

l    仅使用DOM指针来定位文档可能会过于烦琐和不切实际。

l    通常,你不需要直接存取文本节点,只存取包含文本节点的元素就行了。

这让我们遇到了难题:还有其他更好的方式来寻找文档中的节点吗?当然有!在你的工具箱中创建一些辅助函数,你就可以有效提升现有的方法,并且让DOM的定位更为方便。

2.2.1 处理DOM中的空格

回到我们的HTML文档例子。之前,你试图定位到一个<h1>元素去,但由于捣乱的文本节点使得困难重重。如果只是一个比较独立的元素还好说,但是要继续找<h1>元素的下一个元素呢?你仍然会遭遇这个麻烦的空格bug,使你必须用.nextSibling.nextSibling 来跳过<h1>和<p>之间的行结束符,空格一个都不能少。代码清单5-2所示是一个用以处理空格bug的补救办法。这个特别的技术删除所有空格——当然只是DOM文档中的文本节点,让DOM的遍历更容易。这样做不仅不会对你的HTML渲染产生副作用,还会让定位DOM变得更容易。但是需要注意的是,这个函数的结果并不是持久的,它需要在HTML文档的每一次加载时都重新执行一遍。

代码清单5-2 XML文档空格bug的补救方案

function cleanWhitespace( element ) {

  // 如果不提供参数,则处理整个HTML文档

  element = element || document;

  // 使用第一个子节点作为开始指针

  var cur = element.firstChild;

  // 一直到没有子节点为止

  while ( cur != null ) {

    // 如果节点是文本节点,并且只包含空格

    if ( cur.nodeType == 3 && ! /\S/.test(cur.nodeValue) ) {

      // 删除这个文本节点

      element.removeChild( cur );

      // 否则,它就是一个元素

    } else if ( cur.nodeType == 1 ) {

      // 递归整个文档

      cleanWhitespace( cur );

    }

    cur = cur.nextSibling; // 遍历子节点

  }

}

假设你要在前面例子中使用这个函数来查找位于<h1>元素后的元素。那么代码应该类似这样:

cleanWhitespace();

// 查找H1元素

document.documentElement

.firstChild // 查找Head元素

.nextSibling // 查找

.firstChild // 得到H1元素

.nextSibling // 得到相邻的段落(p)

该技术有优点也有缺点。最大的优点在于,你可以保证DOM文档的遍历在一定程度上的稳定性。但明显性能太差,想想必须遍历每个DOM元素和文本节点,目的只是为了找出包含空格的文本节点。假设你有一个包含大量内容的文档,它可能会严重降低网站的加载速度。此外,每次为文档注入新的HTML,你都需要重新扫描 DOM中的新内容,确保没有增加新的有空格填充的文本节点。

此外此函数重要的方面是节点类型的使用。节点的类型可以由检查它的nodeType属性来确定。可能会出现好几种值,但你经常会碰到的是以下3个:

l    元素(nodeType=1):匹配XML文件中的大部分元素。比如,<li>、<a>、<p>和

l    文本(nodeType=3):匹配文档内的所有文本块。当使用previousSibling和nextSibling来遍历DOM结构时,你会经常碰到元素内和元素间的文本块。

l    文档(nodeType=9):匹配文档的根元素。比如,在HTML文档内,它是

此外,你可以用常量来表明不同的DOM节点类型(但只是对非IE浏览器有用)。与其去记住1、3或9,还不如直接直观地使用document.ELEMENT_NODE、 document.TEXT_NODE或者document.DOCUMENT_NODE。因为经常清空DOM的空格会让人感到厌烦,所以你应该探索其他遍历DOM结构更有效的方法。

2.2.2 简单的DOM遍历

可以使用纯粹的DOM遍历规则(每个遍历方向都有指针)来开发一些更适合你的HTMLDOM文档遍历函数。大部分Web开发者在大多数情况下仅仅需要遍历DOM元素而非相邻的文本节点,该规则就是基于这样的事实而制定的。以下一系列的辅助函数可以帮助你,它们能够取代标准的previousSibling、nextSibling、firstChild、 lastChild和parentNode。代码清单5-3展示的函数,返回的是当前元素的前一个元素,如果前一个元素不存在则是null,类似于元素的 previousSibling属性。

代码清单5-3 查找相关元素的前一个兄弟元素的函数

function prev( elem ) {

  do {

    elem = elem.previousSibling;

  } while ( elem && elem.nodeType != 1 );

  return elem;

}

代码清单5-4展示的函数,返回的是当前元素的下一个元素,如果下一个元素不存在则是null,类似于元素的nextSibling属性。

代码清单5-4 查找相关元素的下一个兄弟元素的函数

function next( elem ) {

  do {

    elem = elem.nextSibling;

  } while ( elem && elem.nodeType != 1 );

  return elem;

}

代码清单5-5展示的函数,返回的是当前元素的第一个子元素,类似于firstChild元素属性。

代码清单5-5 查找元素第一个子元素的函数

function first( elem ) {

  elem = elem.firstChild;

  return elem && elem.nodeType != 1 ?

next ( elem ) : elem;

}

代码清单5-6展示的函数,返回的是当前元素的最后一个子元素,类似lastChild元素属性。

代码清单5-6 查找元素最后一个子元素的函数

function last( elem ) {

  elem = elem.lastChild;

  return elem && elem.nodeType != 1 ?

  prev ( elem ) : elem;

}

代码清单5-7展示的函数,返回当前元素的父元素,类似parentNode元素属性。你可以一次用一个数字来操纵多个父元素,例如parent(elem,2)就等同于parent(parent(elem))。

代码清单5-7 查找元素父元素的函数

function parent( elem, num ) {

num = num || 1;

for ( var i = 0; i < num; i++ )

if ( elem != null ) elem = elem.parentNode;

return elem;

}

使用这些新函数你就可以迅速遍历DOM文档了,而且不必再为元素间的文本操心。比如,需要查找<h1>元素的下一个元素,现在可以这么做了:

// 查找<h1>元素的下一个元素

next( first( document.body ) )

在这段代码内有两件事值得注意。首先,此处有一个新的引用:document.body。所有的现代浏览器都在HTML DOM文档内通过body属性提供一个对

2.2.3 绑定到每一个HTML元素

Firefox和Opera中存在一个强大的对象原型(object prototype)叫HTMLElement,它允许你为每一个HTML DOM元素绑定函数和数据。上一节所描述的函数显得特别愚钝,但它们应该可以更清晰的。一个完美的方案是,为HTMLElement的原型直接绑定函数,由此每个独立的HTML DOM元素也直接绑定了你的函数。为了能让上一节描述的函数继续有效,你必须做出3个改变:

(1)  你需要在函数的顶部增加一行代码,使用this来引用元素,而不是从参数的变量中获取它。

(2) 你需要删除不再需要的元素参数。

(3)  你需要把函数绑定到HTMLElement原型,由此DOM中的各个HTML元素可以使用该函数。

新的next函数如代码清单5-8所示。

代码清单5-8 为所有HTML DOM元素动态绑定新的DOM遍历函数

HTMLElement.prototype.next = function() {

  var elem = this;

  do {

    elem = elem.nextSibling;

  } while ( elem && elem.nodeType != 1 );

  return elem;

};

现在你可以这样来使用新的next函数(和经过同样调整后的其他函数)了:

// 一个简单的例子 —— 获取<p>元素

document.body.first().next()

这让你的代码更加清晰和易于理解。现在你可以按照自然想法的顺序来编写代码了,JavaScript总体上也更清晰了。如果你对这种编程风格感兴趣,我特别推荐尝试一下jQuery JavaScript库(http://jquery.com),它大量使用到这种技术。

注意 因为只有3个现代浏览器(Firefox、Safari和Opera)支持HTMLElement,要让它也能在IE中工作,必须给予特别的处理。有一个可以直接使用的库,由Jason Karl Davis(http://browser-land.org)编写,它为两个不支持的浏览器提供了访问HTMLElement的方法。可以从这找到该库的具体信息:http://www.browserland.org/scripts/htmlelement/.

2.2.4 标准的DOM方法

所有的现代DOM实现都包含一系列让编程更轻松的方法。结合一些自定义函数,遍历DOM可以是一种平滑的体验。那么让我们从JavaScript DOM的两个强大方法开始:

l    getElementById("everywhere"):该方法只能运行在document对象下,从所有中找出ID是everywhere的元素。这是一个非常强大的函数,并且是迅速的访问一个元素的最快方法。

l    getElementsByTagName("li"):该方法能运行在任何元素下,找出该元素下的所有标签名为li的后代元素,并返回一个NodeList(节点列表)。

警告 getElementById本来会按照你的预期工作:查找所有元素,揪出一个包含id的属性,且值为指定值的元素。但是,如果你加载一个远程XML 文档,并使用getElementById(或是使用除了JavaScript之外的实现DOM的语言),它默认并不会使用id属性。这是由于设计上的问题,一个XML文档如需明确指出id属性是什么,通常使用XML的定义或配置。

警告 getElementsByTagName返回一个节点NodeList.该结构跟普通的JavaScript数组非常相似,但一个重要的不同之处在于:它并不支持.push()、.pop()、.shift()等JavaScript数组的常用方法。使用getElementsByTagName时请务必记住这点,这能把你从很多混乱中解救出来。

这两个方法在所有现代的浏览器都可用,对定位到指定元素有非常大的帮助。回头看看我们前面尝试查找<h1>元素的例子,现在可以这么做了:

document.getElementsByTagName("h1")[0]

这行代码保证有效并返回文档中的第一个<h1>元素。再回头看看例子文档,假设你要获取所有<li>元素并为之加上边框。

var li = document.getElementsByTagName("li");

for ( var j = 0; j < li.length; j++ ) {

  li[j].style.border = "1px solid #000";

}

最后,需要使第一个<li>元素的文本加粗,假设它已经有了一个关联它的id:

document.getElementById("everywhere").style.fontWeight = 'bold';

你可能已经注意到获取指定ID的单个元素需要冗长的代码,依靠标签名获取元素也如此。其实你可以封装一个函数来简化获取的过程:

function id(name) {

  return document.getElementById(name);

}

代码清单5-9展示了一个函数,它依靠HTML DOM内的文档标签来定位元素。该函数带一个或两个参数。如果只传入一个标签名作为参数,函数将会遍历整个文档。另外你可以传入一个DOM上下文元素作为第一个参数的辅助,则函数只遍历该参数元素下的标签。

代码清单5-9 依靠HTML DOM文档标签定位元素的函数

function tag(name, elem) {

  // 如果不提供上下文元素,则遍历整个文档

  return (elem || document).getElementsByTagName(name);

}

让我们重新回到寻找<h1>元素后的第一个元素的问题。十分幸运的是,现在的代码可以比之前任何一次的都要精炼:

// 查找<h1>元素后的第一个元素

next( tag("h1")[0] );

以上这些函数为你在DOM文档中获取元素提供了强大而迅速的解决方法。在进一步学习修改DOM的方法之前,首先需要大致了解脚本首次执行时DOM的加载问题。

 

2.3 等待HTML DOM的加载

处理HTML DOM文档存在的一个难题是,JavaScript可以在DOM完全加载之前执行,这会给你的代码引发不少的潜在问题。浏览器的渲染和操作顺序大致如以下列表:

l    HTML解析完毕。

l    外部脚本和样式表加载完毕。

l    脚本在文档内解析并执行。

l    HTML DOM完全构造起来。

l    图片和外部内容加载。

l    网页完成加载。

在网页头部并且从外部文件加载的脚本会在HTML真正构造之前执行。如前所述,这是个至关重要的问题,因为这两处执行的脚本并不能访问还不存在的DOM。幸好,我们还有若干的补救办法。

2.3.1 等待整个页面的加载

目前,最常用的技术是完全等待整个页面加载完毕才执行DOM操作。这种技术只需利用window对象的load事件来绑定一个函数,页面加载完毕即可触发。我们将会在第6章更详细地讨论事件。代码清单5-10展示了一个页面完成加载后执行DOM相关代码的例子。

代码清单5-10 使用addEvent函数为window.onload属性绑定回调函数

// 直到页面加载完毕

// (使用的addEvent, 下一章会有阐述)

addEvent(window, "load", function() {

    // 执行HTML DOM操作

    next( id("everywhere") ).style.background = 'blue';

});

最简单的操作却是最慢的。在加载过程的顺序列表中,你会注意到页面的加载完毕与否完全被最后一步所掌控。这就是说,如果你页面有很多的图片、视频等,用户可能得等上一段时间JavaScript才执行。

2.3.2 等待大部分DOM的加载

第二种技术相当绕弯子,所以并不是十分推荐使用。如果你还记得,前面说过行内的脚本在DOM构造后就可立即执行。这是准真理。只有在DOM构造后,执行到该位置上脚本才真正执行。这意味着在你在页面中途嵌入的行内脚本只能访问该位置之前的DOM。所以,在页面最后元素之前嵌入脚本,你就可以在DOM中访问这个元素之前全部的元素,成为一条模拟DOM加载的伪路子。该方法典型的实现如代码清单5-11所示。

代码清单5-11 依靠在HTML DOM最后插入一个

    <h1>Testing DOM Loading</h1>

    <!-- 这里是大量的HTML -->

在这个例子中,你必须把行内脚本作为DOM 的最后一个元素,以便它最后一个被解析和执行。它唯一执行的是初始化函数,该函数应该包含你需要操作的DOM的相关代码。这个解决方案的最大问题在于混乱:你为HTML增添无关标记的理由仅仅是检查DOM是否已经执行。该技术通常被认为是不合理的,因为你为页面增加额外的代码的目的只是检查加载状态而已。

2.3.3 判断DOM何时加载完毕

最后一种技术可用以监听DOM加载状态,可能是最复杂的(从实现角度来看),但也是最有效的。在该技术中,你既能像绑定window加载事件那般简单,又能获得行内脚本技术那样的速度。

这项技术在不堵塞浏览器加载的情况下尽可能快地检查HTML DOM文档是否已经加载了执行所必须的属性。以下是检查HTML DOM是否可用的几个要点:

(1) document:你需要知道DOM文档是否已经加载。若能足够快地检查,运气好的话你会看到undefined。

(2) document.getElementsByTagName和documents.getElementById:频繁使用docum- ent.getElementsByTagName和document.getElementById函数检查文档,当存在这些函数则表明已完成加载。

(3) document.body:作为额外补充,检查

使用这些检查就足够判断DOM是否可用了(“足够”在此表示可能会有一定毫秒级的时间差)。这个方法几乎没有瑕疵。单独使用前述检查,脚本应该可以在现代浏览器中运行得相对良好。但是,最近 Firefox实现了缓存改进,使得window 加载事件实际上可以在脚本能检查到DOM是否可用之前触发。为了能发挥这个优势,我同时为window加载事件附加检查,以期能获得更快的执行速度。

最后,domReady函数集合了所有需要在DOM可用时就执行的函数的引用。一旦DOM被认为是可用的,就调用这些引用并按顺序一一执行。代码清单5-12展示了一个监听DOM何时加载完毕的函数。

代码清单5-12 监听DOM是否可用的函数

function domReady( f ) {

  // 假如 DOM 已经加载,马上执行函数

  if ( domReady.done ) return f();

  // 假如我们已经增加了一个函数

  if ( domReady.timer ) {

    // 把它加入待执行函数清单中

    domReady.ready.push( f );

  } else {

    // 为页面加载完毕绑定一个事件,

    // 以防它最先完成。使用addEvent(该函数见下一章)。

    addEvent( window, "load", isDOMReady );

    // 初始化待执行函数的数组

    domReady.ready = [ f ];

   // 尽可能快地检查DOM是否已可用

    domReady.timer = setInterval( isDOMReady, 13 );

  }

}

// 检查DOM是否已可操作

function isDOMReady() {

  // 如果我们能判断出DOM已可用,忽略

  if ( domReady.done ) return false;

  // 检查若干函数和元素是否可用

  if ( document && document.getElementsByTagName &&

document.getElementById && document.body )

{

    // 如果可用,我们可以停止检查

    clearInterval( domReady.timer );

    domReady.timer = null;

    // 执行所有正等待的函数

    for ( var i = 0; i < domReady.ready.length; i++ )

      domReady.ready[i]();

    // 记录我们在此已经完成

    domReady.ready = null;

    domReady.done = true;

  }

}

现在该来看看这在HTML文档中是如何执行的。 domReady函数应该按addEvent函数(第6章将讨论)的方式使用,文档可操作时即绑定你需要执行的函数。代码清单5-13展示了如何使用 domReady函数来监听DOM是否可用。在这个例子中,假设已经将domReady函数写到一个名为domready.js的外部文件中。

代码清单5-13 使用domReady函数来确定何时DOM可操作和修改

    <title>Testing DOM Loading</title>

  <h1>Testing DOM Loading</h1>

  <!--这里是大量的HTML -->

现在你已经掌握若干定位一般XML DOM文档与如何补救HTML DOM文档加载难题的方法,另一个问题也该摆上台面了:还有更好的方法来查找HTML文档中的元素吗?当然,答案是十分肯定的。

2.4 在HTML文档中查找元素

在HTML文档中与在XML文档中查找元素通常情况下还是有很大不同的。现代HTML事实上是XML的一个子集,这么说来似乎有点自相矛盾。但HTML文档包含了许多本质的不同,而这正是你可以充分利用的地方。

对JavaScript/HTML开发者来说,最重要的两个优势是利用类和CSS选择器(selector)。将这熟记于心,你就可创建一系列强大的函数,使DOM的操作更简单、更易理解。

2.4.1 通过类的值查找元素

通过类名字定位元素是一种很普遍的技术,2003年由Simon Willison(http://simon.incutio. com)普及,而原创的则是Andrew Hayward(http://www.mooncalf.me.uk)。该技术直截了当:遍历所有的元素(或者某元素的所有后代元素)直至找到指定类的元素。一种可能的实现方法如代码清单5-14所示。

代码清单5-14 找出全部有指定类值的元素的函数

function hasClass(name,type) {

  var r = [];

  // 定位到类值上(允许多类值)

  var re = new RegExp("(^|\\s)" + name + "(\\s|$)");

  // 限制类型的查找,或者遍历所有元素

  var e = document.getElementsByTagName(type || "*");

  for ( var j = 0; j < e.length; j++ )

    // 如果元素拥有指定类,把它添加到函数的返回值中

    if ( re.test(e[j]) ) r.push( e[j] );

  // 返回符合的元素列表

  return r;

}

现在你就可使用这个函数来在任意的或者指定类型(比如<li>或<p>)的元素中迅速查找有指定类值的元素。指定标签名会比遍历所有元素(*)的查找更快,因为需检索的目标更少。比如,在我们的HTML文档中,如果需要找出类值有test的所有元素你可以这么做:

hasClass("test")

如果只需找类值有test的<li>元素,则可以这么做:

hasClass("test","li")

当然,如果只需找出类值有test的<li>元素的第一个,则可以这么做:

hasClass("test","li")[0]

这个函数本身就已非常强大,一旦结合getElementById和getElementsByTagName,你就可以创建更强大的工具集合解决大部分棘手的DOM问题了。

2.4.2 使用CSS选择器查找元素

作为一个Web开发者,你应该已经知道选择HTML 元素的一种方案:CSS选择器。CSS选择器是用于赋予元素样式的表达式。随着CSS标准(1、2和3)的每次修订,选择器规范增加了越来越多的重要特点,由此开发者更容易精确定位到所需的元素。不幸的是,浏览器对CSS 2和CSS 3的实现慢得不可思议,所以你或许并不知道CSS中某些新奇酷的特点。如果你对这些感兴趣,建议你浏览W3C的这些主题页面:

l    CSS 1选择器:http://www.w3.org/TR/REC-CSS1#basic-concepts。

l    CSS 2选择器:http://www.w3.org/TR/REC-CSS2/selector.html。

l    CSS 3选择器:http://www.w3.org/TR/2005/WD-css3-selectors-20051215/。

每种选择器规范可用的特点基本上都差不多,每一个后续的版本也都包含前一版的所有特点,同时,每次新版都会增加一系列的新特点。举个例子,CSS 2包含了特性(attribute)选择器和子选择器,而CSS 3则提供了额外的语言支持、性质类型选择以及逻辑非等。比如,以下所有这些都是正确的CSS选择器。

l    #main<div>p:该表达式查找一个id为main的元素下所有的<div>元素,然后是这些元素下所有的<p>元素。它们都是CSS 1下的选择器。

l    div.items>p:该表达式查找所有的类值为items的<div>元素,然后定位到所有的子<p>元素。这是正确的CSS 2选择器。

l    div:not(.items):这则定位到所有没有值为items的类的所有<div>元素。这是正确的CSS 3选择器。

现在,你可能会觉得疑惑,实践中如果并不能使用它们来定位元素(而只能赋予样式)的话,为何会在此讨论CSS选择器。实际上很多前卫的开发者已经涉足开发能兼容CSS1甚至完全支持CSS 3的选择器的实现。使用这些库能让你顺利、方便地选择任意元素并对它们进行操作。

1.cssQuery

第一个完全支持CSS 1-3可用的公开库叫cssQuery,由Dean Edwards(http://dean.edwards.name)创立。其背后的动机很简单:你提供CSS选择器,cssQuery帮你找出所有匹配的元素。此外,cssQuery可以分解成多个子库,有一个是每个CSS选择器“管理员”,它甚至能执行CSS 3的语法,如果有必要的话。这个独特的库非常复杂但能运行在所有现代浏览器上(Dean是一位跨浏览器支持的忠实拥趸)。要应用整个库,你需要提供选择器,也可选择加入上下文元素以便加快搜索。以下是例子:

// 查找所有<div>元素的子<p>元素

cssQuery("div > p");

// 查找所有的<div>, <p>和<form>元素

cssQuery("div,p,form");

// 查找所有<p>元素和<div>元素,然后查找在这些元素内的a元素

var p = cssQuery("p,div");

cssQuery("a",p);

执行cssQuery函数会返回匹配的元素列表。由此你就可以像使用getElementsByTagName一样对元素进行操作。比如,为所有指向Google的链接增加边框,可按如下方法做:

// 为所有指向Google的链接增加边框

var g = cssQuery("a[href^='google.com']");

for ( var i = 0; i < g.length; i++ ) {

  g[i].style.border = "1px dashed red";

}

关于cssQuery的更多信息可以在Dean Edwards的网站上获取,那里同时也提供完整的源码下载:http://dean.edwards.name/my/cssQuery/.

提示 Dean Edwards是一位JavaScript魔术师,他的代码令人惊讶。强烈建议你稍加浏览他的cssQuery库,看看他的JavaScript扩展性写得多么好。

2.jQuery

尽管jQuery是JavaScript库世界的新成员,但它提供了一些新颖而引人注目的JavaScript编程方式。我最初只想把jQuery写成一个“简便”的CSS选择器库,类似于 cssQuery,直到Dean Edwards发布了卓越的cssQuery库,迫使我开辟了另一个不同的方向。这个库提供了完整的CSS 1-3的支持,同时也有基本的XPath支持。在此之上,它还提供了进一步定位和操作DOM的能力。跟cssQuery一样,jQuery完全支持现代浏览器。以下是使用混合CSS和XPath的jQuery自定义方式来选择元素的例子:

// 查找所有类值为'links', 同时内有p元素的<div>元素

$("div.links[p]")

// 查找所有<p>, <div>元素的所有子孙元素

$("p,div").find("*")

// 查找指向Google的所有偶数链接

$("a[@href^='google.com']:even")

现在,如需进一步使用jQuery元素选择返回的结果,你有两种方式。第一,你可以运行$("expression").get()来获取匹配元素的列表——这跟cssQuery完全一致。第二,你可以使用jQuery内置的特殊函数来操作CSS和DOM。所以,回到使用cssQuery为指向Google的链接加边框的例子上,你现在就可以这么做:

// 为所有指向Google的链接增加边框

$("a[@href^=google.com]").css("border","1px dashed red");

你可以从jQuery项目网站上找到大量的例子、演示和文档等,此外,还有可定制的下载:http://jquery.com/。

注意 应该指出的是,cssQuery或jQuery实际上并不只是能定位HTML文档而已,它们可以在任何XML文档上使用。至于纯粹的XML形式定位,请继续阅读下面的XPath部分。

2.4.3 XPath

XPath表达式是一种不可思议的定位XML文档的强大的方式。自问世几年来,几乎可以肯定DOM实现的背后必有XPath。尽管相对冗长,但XPath表达式比起CSS选择器来,能做的事情更多也更强大。表5-1并行比较了一些CSS选择器和XPath表达式的某些不同。

表5-1 CSS 3选择器与XPath表达式的比较

目 标

CSS 3

XPath

所有元素

*

//*

所有<p>元素

p

//p

所有子元素

p> *

//p/*

由ID获取元素

#foo

//*[@id='foo']

由类获取元素

.foo

//*[contains(@class, 'foo')]

由特性(attribute)获取元素

*[title]

//*[@title]

<p>的第一个子元素

p >*:first-child

//p/*[0]

所有拥有子元素的<p>

不支持

//p[a]

下一个元素

p + *

//p/下一兄弟元素::*[0]

如果前面这些表达式激发了你的兴趣,推荐你浏览XPath的两个规范(尽管现代浏览器通常只完全支持XPath 1.0)以初步了解它们是如何工作的:

l    XPath 1.0:http://www.w3.org/TR/xpath/。

l    XPath 2.0:http://www.w3.org/TR/xpath20/。

如果需要深入这个主题,我推荐你阅读O’Reilly出版的由Elliotte Harold和Scott Means所著的XML in a Nutshell2004),或者Apress出版的由Jeni Tennison所著的Beginning XSLT 2.0: From Novice to Professional2005)。此外,还有一些精彩的教程帮助你学习使用XPath:

l    W3Schools的XPath教程:http://w3schools.com/xpath/。

l    ZVON XPath 教程:http://zvon.org/xxl/XPathTutorial/General/examples.html。

目前,浏览器对XPath的支持各不相同:IE和 Mozilla都比较完整(虽然不一样)地支持XPath实现,而Safari和Opera的版本还在开发中。作为补救,有几种完全使用 JavaScript写的XPath实现方法。虽然它们通常会比较慢(跟基于浏览器的实现相比),但可以确保所有现代浏览器都能稳定运行:

l    XML for Script:http://xmljs.sf.net/。

l    Google AJAXSLT:http://goog-ajaxslt.sf.net/。

此外,一个名为Sarissa的项目(http://sarissa.sf.net/)打算为各种浏览建立一个通用的实现包装。它可以让你一次写成XML访问代码,同时仍能从支持的浏览器中获得更快的运行速度。这项技术的最大问题在于,Opera和Safari浏览器对XPath支持仍不足,走不出XPath先前实现的困境。

使用浏览器内置的XPath与使用得到广泛支持的纯粹的JavaScript解决方案比起来,通常被认为是试验性质的技术。尽管如此,XPath的使用和普及渐渐成长起来,可把它当作CSS选择器强有力的竞争对手。

现在你已经掌握了定位任意DOM元素或任意DOM元素集合的必要知识和工具,那么我们现在应该利用这些知识了:从特性的操作到增加和删除DOM元素。

2.5 获取元素的内容

所有的DOM元素无外乎包含以下三者之一:文本、元素、文本与元素的混合。一般来说,常见的是第一种和最后一种。在这一节中你将了解现有的获取元素内容的常用方法。

2.5.1 获取元素内的文本

文本框:  
图5-3 包含元素与文本的DOM结构例子获取元素内的文本可能会令DOM新手最容易迷惑,同时它也配合了HTML DOM文档和XML DOM文档。在图5-3所示的DOM结构例子中,有一个<p>主元素,它包含一个<strong>元素和一段文本块。<strong>元素本身同时包含文本块。

让我们来看看如何获取这些元素中的文本。<strong>元素很简单,因为它除了文本节点没别的干扰信息。

应该注意的是,所有非基于Mozilla的浏览器中都有一个叫作innerText的属性,可用它来获取元素内的文本。这十分方便。不幸的是,因为它在一个比较流行的浏览器上并不可用,同时在XML DOM文档中也不行,你还是需要开发一个可行的替代方案。

获取元素内文本内容的一个窍门是,需要记住元素并不是直接包含文本的,而包含在一个子文本节点中,这可能有点让人不习惯。假设变量strongElem包换一个<strong>元素的引用,让我们来看看代码清单5-15是如何使用DOM从元素内抽出文本的。

代码清单5-15 获取<strong>元素的文本内容

// 非Mozilla浏览器:

strongElem.innerTex

// 其他的浏览器:

strongElem.firstChild.nodeValue

了解如何从单个元素中获取文本内容后,你需要继续学习如何从<p>元素中获取经过组合的文本内容。为此,你或许需要开发一个通用的函数来获取任意元素的文本内容,而不用考虑元素究竟嵌套了几层,如代码清单5-16所示。调用text()将返回元素及其所有后代元素包含的文本内容。

代码清单5-16 一个获取元素文本内容的通用函数

function text(e) {

  var t = "";

  // 如果传入的是元素,则继续遍历其子元素,

  // 否则假定它是一个数组

  e = e.childNodes || e;

  // 遍历所有字节点

  for ( var j = 0; j < e.length; j++ ) {

    // 如果不是元素,追加其文本值

    // 否则,递归遍历所有元素的子节点

    t += e[j].nodeType != 1 ?

      e[j].nodeValue : text(e[j].childNodes);

  }

  // 返回匹配的文本

  return t;

}

使用这个能够获取任何元素文本内容的函数,在上面这个例子中,你就可以从<p>元素中获取它的文本内容了,代码大致如下:

// 获取<p>元素的文本内容

text( pElem );

这个函数还有一个显著的好处,那就是它可以确保在HTML和XML DOM文档中都能工作,那就是说你现在拥有了一个能获取任何元素文本内容的兼容方法。

2.5.2 获取元素内的HTML

与获取元素内的文本比起来,获取元素内的HTML是一项更容易上手的DOM任务。令人高兴的是,由于一个由IE小组开发的特色方法,所有的现代浏览器现在都实现了HTML DOM元素的一个额外属性:innerHTML。使用这个属性你就可以从一个元素中提取所有HTML和文本了。此外,使用innerHTML十分快——通常会比递归查找元素内的所有文本内容快得多。但是并非事事称心如意。它取决于浏览器如何去实现innerHTML属性,而且因为并没有标准,每种浏览器都可以返回它自以为是的内容。例如,以下是一些使用innerHTML属性的怪异bug:

l    基于Mozilla的浏览器在innerHTML声明中并不会返回<style>元素。

l    IE返回的元素字符都是大写的,如果你想保持一致性可能会感到失望。

l    innerHTML作为一个只能用在HTML DOM文档的元素中的属性,若在XML DOM文档中使用的话只会返回null值。

使用innerHTML属性是很直观的,存取该属性会返回一个包含该元素的HTML内容的字符串。如果元素仅包含文本而不包含任何子元素,则返回的字符串只包含文本。我们通过调试图5-2中两个元素来看它是如何工作的:

// 获取<strong>元素的innerHTML

// 应返回"Hello"

strongElem.innerHTML

// 获取<p>元素的innerHTML

// 应返回"<strong>Hello</strong> how are you doing?"

pElem.innerHTML

如果你确定元素只包含文本,这个方法也可用来取代前面那个获取元素文本的复杂方法,并且十分简便。另一方面,能获取元素内的HTML内容,你就可以利用就地编辑的特点来构建一些动态的时髦应用。在第10章我们会讨论这个主题。

2.6 操作元素特性

几乎跟获取元素内容一样,获取和设置元素特性(attribute)的值也是最频繁的操作之一。通常,元素自带的特性列表会随着元素自身的XML表示信息预先加载,并存储成关联数组备用,例如以下这个网页的HTML片段:

<form name="myForm" action="/test.cgi" method="POST">

...

</form>

一旦加载到DOM中,表示HTML表单元素的变量formElem就会有一个关联数组,它是名/值特性对。其结果大致如下:

formElem.attributes = {

  name: "myForm",

  action: "/test.cgi",

  method: "POST"

};

检查元素特性是否存在对于使用特性数组来说是最简单不过的,但还是存在一个问题:不知何故Safari不支持。最为严重的是,IE也不支持可能是最有用的hasAttribute函数。那么如何才能检查某个特性是否存在呢?一个可行的办法是使用getAttribute函数(我将会在下一节讲述)并且检查返回值是否为null,如代码清单5-17所示。

代码清单5-17 检查元素是否有用一个指定的特性

function hasAttribute( elem, name ) {

  return elem.getAttribute(name) != null;

}

有了这个函数,并了解特性如何使用,那么你就可以获取和设置特性的值了。

获取和设置特性的值

根据你所使用的DOM文档的类型,从元素中获取特性的信息有两种方法。如果你希望保险并且总是兼容通用XML DOM的方式,那么可用getAttribute和setAttribute。可以使用如下方法:

// 获取特性

id("everywhere").getAttribute("id")

// 设置特性的值

tag("input")[0].setAttribute("value","Your Name");

除了这对标准的 getAttribute/setAttribute外,HTML DOM文档还有作为快速特性获取器(getter)/设置器(setter)的额外属性集合。它们通常在现代浏览器的DOM实现中都可用(但只能给 HTML DOM文档打包票),使用它们十分有助于编写精炼的代码。以下的代码向你展示了如何使用DOM属性(property)同时取得和设置DOM特性:

// 快捷获取特性

tag("input")[0].value

// 快捷设置特性

tag("div")[0].id = "main";

你应该注意到,特性存在某些怪异的情形。最常碰到问题的情形之一是存取类名称特性。为了能在所有的浏览器中都生效,你必须使用elem.className来存取className特性,而不是使用看起来更合适的getAttribute("class")。这种情形也发生在for特性上,而它被另外起名为htmlFor。此外,这也会在一些CSS特性上发生:cssFloat和cssText。导致这些特别的命名约定是因为class、for、float和text等都是JavaScript的保留字。

为了补救这些怪异的情形并且简化获取、设置正确特性的处理流程,你应该使用一个辅助函数来处理这些特殊情形。代码清单5-18展示了一个获取和设置元素特性值的函数。使用两个参数来调用这个函数时,比如 attr(element,id),返回指定元素特性的值;而使用3个参数来调用的话,比如attr(element,class,test),则是设置特性的值并返回它的新值。

代码清单5-18 获取和设置元素特性的值

function attr(elem, name, value) {

  // 确保提供的 name 是正确的

  if ( !name || name.constructor != String ) return '';

  // 检查name是否处在怪异命名的情形中

  name = { 'for': 'htmlFor', 'class': 'className' }[name] || name;

  // 如果用户传入了value参数的话,那么

  if ( typeof value != 'undefined' ) {

    // 首先使用快捷方式

    elem[name] = value;

    // 可以的话,使用setAttribute

    if ( elem.setAttribute )

      elem.setAttribute(name,value);

  }

  // 返回特性的值

  return elem[name] || elem.getAttribute(name) || '';

}

不用关心具体实现,使用标准的方法来同时存取和改变特性是一个强大的工具。代码清单5-19展示了一些例子,从中你可以学习到如何在多种场合中使用attr函数来简化处理特性的流程。

代码清单5-19 在DOM元素中使用attr函数设置和获取特性的值

// 设置<h1>元素的class

attr( tag("h1")[0], "class", "header" );

// 设置各个<input>元素的值

var input = tag("input");

for ( var i = 0; i < input.length; i++ ) {

  attr( input[i], "value", "" );

}

// 为name的值是'invalid'的<input>元素增加边框

var input = tag("input");

for ( var i = 0; i < input.length; i++ ) {

  if ( attr( input[i], "name" ) == 'invalid' ) {

    input[i].style.border = "2px solid red";

  }

}

到目前为止,我们已经讨论了DOM中常用的特性(比如id、class、name等)的获取和设置。但是,还有一个派得上用场的技术,那就是设置和获取非传统的特性。比如,你可以增添一个新特性(它只对存取DOM版本的元素可见)然后再次获取,完全不用修改文档的实际属性。举个例子,假设你有一个术语的自定义列表,并需要在点击术语的时候展开它的描述,HTML的结构大致如代码清单5-20所示。

代码清单5-20 自定义列表的HTML,其中把描述事先隐藏起来

    <title>Expandable Definition List</title>

    <style>dd { display: none; }</style>

    <h1>Expandable Definition List</h1>

    <dl>

      <dt>Cats</dt>

      <dd>A furry, friendly, creature.</dd>

      <dt>Dog</dt>

      <dd>Like to play and run around.</dd>

      <dt>Mice</dt>

      <dd>Cats like to eat them.</dd>

    </dl>

我们将会在第6章详细讲解事件的细节,现在先尽量保持这些事件代码的简单。以下例子是一个能让你点击术语显示(或隐藏)描述的高效脚本。该脚本应在页面的头部或者从一个外部文件中引入。代码清单5-21展示了构建一个可扩展自定义列表的必要代码。

代码清单5-21 允许动态切换的自定义列表

// 直至DOM可用

domReady(function(){

    // 遍历所有的术语

    var dt = tag("dt");

    for ( var i = 0; i < dt.length; i++ ) {

    // 等待用户点击术语

    addEvent( dt[i], "click", function() {

      // 检查描述已经open与否

      var open = attr( this, "open" );

      // 切换描述的display

      next( this ).style.display = open ? 'none' : 'block';

      // 记录描述是否open

      attr( this, "open", open ? '' : 'yes' );

      });

    }

});

现在已经知道如何遍历DOM与如何检查及修改特性,接下来你需要学习如何创建新的DOM元素,插入到所需的地方,并删除不再需要的元素。

2.7 修改DOM

了解如何修改DOM后,你就可以干任何事了,从即时创建自定义的XML文档到建立接受用户键入的动态表单,几乎所有可能的操作。修改DOM有3个步骤:首先要知道怎么创建一个新元素,然后要知道如何把它插入DOM中,最后要学习如何删除它。

2.7.1 使用DOM创建节点

修改DOM的主要方法是使用createElement函数,它可以让你即刻创建新元素。但是,已经创建的新元素并不会马上插入到DOM中(刚开始使用DOM的人常会对此感到迷惑)。所以,我先把重点放在创建DOM元素上。

createElement方法带有元素的标记名称,并返回该元素的实际DOM引用,这里没有特性和样式。如果你要开发使用XSLT驱动的XHTML页面(或者使用正确的MIME侍服的XHTML页面)的应用程序,必须记住,你使用的是XML文档,所以创建的元素必须使用正确的XML命名空间来关联它们。为无缝地解决问题,你可以使用一个简单的函数,用它来测试你正使用的HTML DOM文档是否支持使用命名空间(XHTML DOM文档的一个特点)来创建新的元素。在这种情况下,你必须使用正确的XHTML命名空间来创建新的DOM元素,如代码清单5-22所示。

代码清单5-22 创建新DOM元素的通用函数

function create( elem ) {

  return document.createElementNS ?

    document.createElementNS( 'http://www.w3.org/1999/xhtml', elem ) :

document.createElement( elem );

}

例如,使用这个函数你就可以创建一个简单的<div>元素,并附上一些额外的信息:

var div = create("div");

div.className = "items";

div.id = "all";

此外需要注意的是,一个创建新文本节点的DOM方法叫做createTextNode。它需要传入一个参数,即你需要插入到节点中的文本,并返回已创建的文本节点。

使用新建立的DOM元素和文本节点,就可以把它们插入到DOM文档所需的位置中去了。

2.7.2 插入到DOM中

即使是经验丰富的DOM老手,有时也难免会感到插入元素到DOM中非常麻烦。在你的JavaScript工具库中准备以下两个函数可以让你把事情做好。

第一个函数是insertBefore,可以在另一个子元素前插入一个元素。使用该函数的方法类似如下例子:

parentOfBeforeNode.insertBefore( nodeToInsert, beforeNode );

我使用了一个助记诀窍辅助记忆它的参数顺序:“insert第一个元素,before第二个。”没错吧,你只需片刻就记住了它。

有了一个能在其他节点前插入节点(可以是元素,也可以是文本节点),你是否该考虑考虑:“如何插入一个父节点中最后一个子节点?”一个名为appendChild的函数可以做到这个。 appendChild调用一个元素参数,追加指定的节点到子节点列表中的最后一个。这个函数的用法大致如下:

parentElem.appendChild( nodeToInsert );

为避免死记硬背insertBefore和 appendChild的参数顺序,你可以使用我写的两个辅助函数解决问题,如代码清单5-23和代码清单5-24所示,参数以相关的元素/节点然后是需插入的元素/节点的顺序调用。此外,before函数还有一个传入父节点的可选项,它可以为你节约一些代码。最后,两个函数都允许你传入需要插入/追加的字符串并自动地帮你转化为文本节点。建议你提供父元素作为引用(防止elem变成null)。

代码清单5-23 在另一个元素之前插入元素的函数

function before( parent, before, elem ) {

  // 检查parent是否传入

  if ( elem == null ) {

    elem = before;

    before = parent;

    parent = before.parentNode;

  }

  parent.insertBefore( checkElem( elem ), before );

}

代码清单5-24 为另一个元素追加一个子元素的函数

function append( parent, elem ) {

  parent.appendChild( checkElem( elem ) );

}

代码清单5-25的辅助函数允许你插入元素和文本(自动转化为正确的文本节点)。

代码清单5-25 before和append的辅助函数

function checkElem( elem ) {

  // 如果只提供字符串,则把它转化成文本节点

  return elem && elem.constructor == String ? document.createTextNode( elem ) : elem;

}

现在,使用before和append函数,通过创建新的DOM元素,你就可以在DOM中增添更多的信息呈现给用户了,如代码清单5-26所示。

代码清单5-26 使用append和before函数

// 创建一个新的<li>元素

var li = create("li");

attr( li, "class", "new" );

// 创建文本内容并添加到<li>中

append( li, "Thanks for visiting!" );

// 把<li>插入到有序列表的顶端

before( first( tag("ol")[0] ), li );

// 运行这些语句会转化空<ol>

<ol></ol>

// 为以下:

<ol>

<li class='new'>Thanks for visiting!</li>

</ol>

一将这些信息“插入”到DOM(包括使用insertBefore和appendChild),它马上就会渲染并呈现在用户前。因此,你可用之作实时的反馈。这对于需要用户输入的交互应用尤其有用。

现在你看到的只是基于DOM的方法来创建和插入节点,进一步了解其他向DOM注入内容的方法将更有用。

2.7.3 注入HTML到DOM

比创建普通DOM元素并插入到DOM中更为流行的技术是,直接向文档注入HTML。这个最为简便的实现方法使用了前面讨论的innerHTML。除了从元素获取HTML之外,它同时也是设置元素内的HTML的一条途径。作为一个展示其简单性的例子,假设有一个空的<ol>元素并需要往里加入<li>,那么的代码大致如下:

// 给ol 元素加入部分li

tag("ol")[0].innerHTML = "<li>Cats.</li><li>Dogs.</li><li>Mice.</li>";

难道这不比繁复地创建大量的DOM元素和相应的文本节点要简单得多?更好的是,它还比使用DOM方法要快得多(根据http://quirksmode.org的说法)。但世事无完美,使用innerHTML注入方法也伴随着一些棘手的问题:

l    如前所述,XML DOM文档中并不存在innerHTML方法,就是说你得继续使用通用的DOM 创建方法。

l    使用客户端XSLT创建的XHTML文档也不存在innerHTML方法,因为它们也是纯粹的XML文档。

l    innerHTML完全删除了元素内容的任何节点,意味着没有像DOM方法一样追加或者插入的简便方法。

最后一条尤为棘手,因为在另一个元素之前插入元素或者追加到子元素列表末尾特别有用。但是,编织一些DOM魔法,去改造append和before方法,除了常规的DOM元素,还可以让它跟常规的HTML 字符串和平共处。改造过程分两步走。第一步建立一个checkElem函数,它可以处理HTML字符串、DOM元素以及DOM元素数组,如代码清单 5-27所示。

代码清单5-27 转化一个DOM节点/HTML字符串混合型参数数组为纯粹的DOM节点数组

function checkElem(a) {

  var r = [];

  // 如果参数不是数组, 则强行转换

  if ( a.constructor != Array ) a = [ a ];

  for ( var i = 0; i < a.length; i++ ) {

    // 如果是字符串

    if ( a[i].constructor == String ) {

      // 用一个临时元素来存放HTML

      var div = document.createElement("div");

     // 注入HTML, 转换成DOM结构

      div.innerHTML = a[i];

      // 提取DOM结构到临时 div 中

      for ( var j = 0; j < div.childNodes.length; j++ )

        r[r.length] = div.childNodes[j];

    } else if ( a[i].length ) { // If it's an array

      // 假定是DOM节点数组

      for ( var j = 0; j < a[i].length; j++ )

        r[r.length] = a[i][j];

    } else { // 否则,假定是DOM节点

      r[r.length] = a[i];

    }

  }

  return r;

}

第二步,你需要修改这两个插入函数以适应新的checkElem,接受数组元素,如代码清单5-28所示。

代码清单5-28 插入和追加内容到DOM的改进函数

function before( parent, before, elem ) {

  // 检查是否提供 parent 节点参数

  if ( elem == null ) {

    elem = before;

    before = parent;

    parent = before.parentNode;

  }

  // 获取元素的新数组

  var elems = checkElem( elem );

  // 向后遍历数组,

  // 因为我们向前插入元素

  for ( var i = elems.length - 1; i >= 0; i-- ) {

    parent.insertBefore( elems[i], before );

  }

}

function append( parent, elem ) {

  // 获取元素数组

  var elems = checkElem( elem );

  // 把它们所有都追加到元素中

  for ( var i = 0; i <= elems.length; i++ ) {

    parent.appendChild( elems[i] );

  }

}

现在,使用这些新函数为有序列表追加<li>就非常简单了:

append( tag("ol")[0], "<li>Mouse trap.</li>" );

// 运行该简单的一行就能追加HTML到这个<ol>中

<ol>

<li>Cats.</li>

<li>Dogs.</li>

<li>Mice.</li>

</ol>

// 把它变成了这样:

<ol>

<li>Cats.</li>

<li>Dogs.</li>

<li>Mice.</li>

<li>Mouse trap.</li>

</ol>

// 而用before()运行一个类似的语句

before( last( tag("ol")[0] ), "<li>Zebra.</li>" );

// 则会变成这样:

<ol>

<li>Cats.</li>

<li>Dogs.</li>

<li>Zebra.</li>

<li>Mice.</li>

</ol>

这将能帮助你开发简洁和清晰的代码。但是,如果从DOM中移动元素和删除节点要怎么办呢?当然,肯定也会有其他方法处理这些问题。

2.7.4 删除DOM节点

删除DOM节点的操作几乎与创建和插入一样频繁。根据用户的需求允许创建无限量的项目,那么允许用户能够删除它们之中再也不需要处理的部分也就变得很重要了。删除节点的能力可封装成一个函数:removeChild。它跟appendChild用法一致效果相反。该函数的用法大致如下:

NodeParent.removeChild( NodeToRemove );

记住这点,你就可以创建两个独立的函数来删除节点了,如代码清单5-29所示。

代码清单5-29 删除DOM节点的函数

// 删除一个独立的DOM节点

function remove( elem ) {

  if ( elem ) elem.parentNode.removeChild( elem );

}

代码清单5-30展示了从一个元素中删除所有子节点的函数,仅需要DOM元素的引用作为参数。

代码清单5-30 从一个元素中删除所有子节点的函数

// 在DOM中删除一个元素的所有子节点

function empty( elem ) {

  while ( elem.firstChild )

    remove( elem.firstChild );

}

举个例子,要删除上一节例子中添加的<li>,同时假设你已经给用户充足的时间来浏览<li>了,在没有提示的情况下被删除。可以使用以下的JavaScirpt代码来完成这个操作:

// 删除<ol>的最后一个<li>

remove( last( tag("ol")[0] ) )

// 将会把

<ol>

<li>Learn Javascript.</li>

<li>???</li>

<li>Profit!</li>

</ol>

// 转换成:

<ol>

<li>Learn Javascript.</li>

<li>???</li>

</ol>

// 如果要求执行empty()函数而不是remove()

empty( last( tag("ol")[0] ) )

// 它将会简单地清空<ol>, 只留下:

<ol></ol>

学习完删除DOM节点,你已经清楚了我们的文档对象模型是如何运作的,并学会如何充分运用它。

2.8 小结

在这一章里讨论了与文档对象模型相关的大量东西。不幸的是,其中有些主题比其他的要复杂得多(比如等待DOM加载),而且在一段时间里都一直会这么复杂。不过仅使用目前所学你也能搭建出任何动态的Web应用程序了。

如果想看看一些DOM脚本编程的实际运用,在附录A中包含了大量补充代码。此外,还可以从本书的网站http://jspro.org或Apress网站 http://www.apress.com的源代码/下载部分找到更多DOM脚本编程例子。接下来,你要把注意力转向分离式DOM脚本编程的下一个组成部分——事件。

Wednesday, November 19, 2008

Cake PHP 学习备忘录

 

2

Posted in Cakephp, VC & PHP by DYH1919(Freshow) at 04月 23, 2008
Tags: Cakephp   欢迎引用、转载、修改,在转载时附上本文URL即可。

你的数据库中的表也应该遵循下面的命名规则:

● cake使用的表名(Table name)应该有英文的复数形式组成,比如users, authors, articles. 注意,对应的model是单数形式

● 所有的表 都必须有一个主键叫做 id

● 如果你需要关联tables,使用外键比如 article_id 。表的名字是单数,必须是小写,比如id

● 另外,最好遵从下面的命名规则以便更方便的使用某些功能

Include a ‘created’ 列

Include a ‘modified’列

UltraEdit 32 中高亮显示 CakePHP 的 视图(View),.ctp ,同理可扩展其它自定义后缀:

找到 UltraEdit 32 的 安装目录,打开 wordfile.txt 找到 253 行,在后面添加上 CTP 就行了。

例如:/L3″HTML” HTML_LANG Nocase Noquote Block Comment On = <!– Block Comment Off = –> File Extensions = HTM HTML SHTML HTT HTA HTX CFM JSP PHP PHTML ASP CTP

在CakePHP 中使用自定义全局变量,常量,全局函数等全局性东西:(/app/config/bootstrap.php),另一个办法是在 app 目录重载 app_controller.php,这样可以让全局函数。

Bootstrapping CakePHP

If you have any additional configuration needs, use CakePHP’s bootstrap file, found in /app/config/bootstrap.php. This file is executed just after CakePHP’s core bootstrapping.

This file is ideal for a number of common bootstrapping tasks:

  1. •Defining convenience functions
  2. •Registering global constants
  3. •Defining additional model, view, and controller paths

数据库乱码

app\config\database.php中设置 ‘encoding’ => ‘utf8′;

更新单个表项或多个表项时要指明 id ,如果不指明则是插入新行

$this -> Controller -> id = $this -> Session -> read('id');

$this -> :Controller -> id = $this -> Session -> read(’id’);

$this -> :Controller -> saveField(’actived’,'Y’)

saveField() 和 save()

上面也是说在 CakePHP 中如何使用 Update 这样的效果

显示乱码:

可以修改 view 文件夹下的 模版文件,添加:

$html -> charset(”UTF-8″);

Session使用:

Controller :$this->Session->write($key,$value); $this->Session->read()…..

使用数组:$dataArray1 = array (’g'=>$generalfile,’b'=>$bigfile,’s’=>$smallfile,’m'=>$minifile);  $this -> Session -> write($dataArray1);

View:$session->read($key);…..

function afterSave()
{
// mail me when a new bookmark is added
mail('example@example.com', 'Bookmark saved to database');
return true;

}


不使用 layout: $this-> layout = false;


Model 名和数据库名不对应:var $useTable = 'Table Name';


==============


setFlash

string $flashMessage


string $layout = ‘default’


array $params


string $key = ‘flash’


将变量$flashMessage中的信息写入Session(提供给之后的flash()方法来获取)。


如果$layout设置为“default”,该消息被存储为’<div class=”message”> ‘.$flashMessage.’</div> ‘.如果$default设置为”,该消息就按原样保存。如果参数为其他任何值,该消息以 $layout 所指定的格式保存在Cake view里.



新增 我收集、整理的一些 CakePHP 学习资料:这里下载 [2008.5.6]


原文地址:http://www.oblank.com/?p=475 转载时请不要删除本地址





CakePHP V1.2 中 Session 丢失修正方法。



0



Posted in Cakephp, VC & PHP by DYH1919(Freshow) at 04月 15, 2008


Tags: Cakephp, session   欢迎引用、转载、修改,在转载时附上本文URL即可。



CakePHP V1.2 中,即使在 ./cake/app/config/core.php 设置 Session.start 值为 true




Configure::write(’Session.start’,true);




Session 也会在刷新,离开本页,甚至于在 Controller 中使用了 redirect() 时丢失,翻遍了互联网也没找到答案,询问 Cake Google Group 也没有很好的 fix 方案。今天再通过 Subversion 从 V1.2.0.6331 升级到了 V1.2.0.6661 ,可问题仍没有解决。 后来想到看一下 CakePHP 的 libs 库文件或许可以解决问题,于是找到了 ./cake/cake/libs/controller/components/ 目录下的 session.php 文件,发现里面有一个变量 var $__started = false; 将它改为 true 后再测试,Session 一切正常,不会再丢失。 比较理想的修正方法: 1,将 ./cake/app/config/core.php 的 session.start 设为 true:




Configure::write(’Session.start’,true);




2,拷贝 ./cake/cake/libs/controller/components/ 目录下的 session.php 到 ./cake/app/controller/components 下,然后将 session.php 中的 var $__started = false; 设值为 true即可:




var $__started = true;




为什么要复制文件到 ./cake/app… 目录下呢?这是因为 app 是用户目录,而 ./cake/cake…. 是 CakePHP 的核心文件,一般不能修改,以方便以后升级使用。复制到 app 目录,可以通过重载的方法实现 fix bug 。 顺便提一下,session components 是每个 Controller 的默认组件,使用时不需要再次包含进去。



下面这部分是写给外国佬看的,英语好的绕道:



How to fix session gone/lost in CakePHP V1.2.x



I have got the bug(maybe!) ,fix steps :



first:confige session autostart in ./cake/app/config/core.php



Configure::write(’Session.start’,true);



then,copy the ’session.php’ file in ./cake/cake/libs/controller/components/ To ./cake/app/controller/components And set the var __started value to true, as below:



var $__started = true;



it work fine for me, help hope it useful



GooglGroup 关于这个问题的修正的完整讨论看这里



原文地址:http://www.oblank.com/?p=450 转载时请不要删除本地址





Subversion 安装记录



0



Posted in Subversion, VC & PHP by DYH1919(Freshow) at 04月 15, 2008


Tags: Apache, Cakephp, Subversion   欢迎引用、转载、修改,在转载时附上本文URL即可。



CakePHP Session 一直不能持久化,几天来用蹩脚的英语在Cakephp Google Group 上求助得来的方法都不起作用,后来看到似乎是将 Cake 从 V1.2.0.6113 升级到了一个新版本才算解决了问题,在 CakePHP 的 Svn 看到已经有了 1.2.0.6661,希望也可以通过升级来解决,于是就得用到了 Subversion 这个版本控制软件。



说起 Subversion ,以前在 Eclipse 中使用过插件,但也就是这个插件让我的 Eclipse 崩溃,所以这次不准备使用插件了而改用客户端。



SVN 的最新版是 1.4.6 ,但不能与 Apache 2.2.x 搭配,我先是在官方提供的“built against Apache 2.2.x ”下载“svn-1.4.6-setup.exe”,安装发现根本不能在 Apache 2.2.4 上使用,又下载“svn-win32-1.4.6.zip”,按照网上一些教程进行手动设置,费掉了九牛二虎之力也没能解决。



最后又下载“Subversion modules svn-1.4.6 available”,但安装到最后才发现它的教程很模糊,其间又有提到集成方案“EasyCM_0.8.0_Server_Win32”,(包括Apache-2.0.59、subvertion-1.4.4、TortoiesSVN-1.4.4),安装时发现 Apache 和 subversion 版本都很低,没道理我在返回去使用低版本的软件(宁愿做只小白鼠!),所以都跳过只安装了 TortoiesSVN 和 Python 2.4。



后来又在官方的“built against Apache 2.2.x ”下载了一个 Python 2.4 ,最后发现已经可以使用了,不过网上提到教程那些 浏览器中访问什么什么的我这里同样不能,只是右键菜单多了很多 SVN 方面的操作,也是前面的 TortoiesSVN 图形化软件。



经实验,能够成功能过 Subversion 从 CakePHP 获取最新版。



总结一下:回顾这个安装过程,安装 EasyCM 的 TortoiesSVN 、Python2.4 和最后安装 “svn-python-1.4.6.win32-py2.4.exe” 是关键。

Sunday, November 16, 2008

如何在cakephp的布局文件中包含多个模板?

2008/10/23

cake中的布局(layout)确实不错,可惜不能包含多个模板文件(view)进来。
前几天在网上看到一个实现方法,http://www.xuzhousoft.cn/content/view/48/1/ ,看了一下代码,是用helper来实现的,感觉有点费劲。从代码看,其view文件中应该不支持$session,$html之类的了。
下面介绍一款简单实用的方法。先来看看cake的layout是如何实现下面这个的:

1 <?php echo $content_for_layout; ?>

在cake/libs/view/view.php中有个方法叫做: function renderLayout()其中有$data_for_layout如下:

01 <?php
02 $data_for_layout = array_merge($this->viewVars,
03 array(
04 'title_for_layout' => $pageTitle,
05 'content_for_layout' => $content_for_layout,
06 'scripts_for_layout' => join("\n\t", $this->__scripts),
07 'cakeDebug' => $debug
08 )
09 );
10 ?>

也就是说实现的数据都是准备好的,从这一点上来讲就很难扩展了,因为变量就那么几个啊。
下面介绍一个方法,看代码:

01 <?php
02 /**
03 * 引入其他view文件,在layout中支持多个view
04 *
05 * @param string $name 在view目录下的文件
06 * @return string
07 *
08 */
09 function template($name) {
10 $s = addslashes( ROOT.DS.APP_DIR.DS.'views'.DS.$name.'.ctp' );
11 return "require(\"$s\");";
12 }
13 ?>

将这个函数添加到你的项目中去,然后在你的layout中添加如下代码即可包含多个view:
view/layout/default.ctp

1 ...
2 <?php
3 //包含模板 include you template
4 eval(template('elements'.DS.'hello'));
5 ?>
6 ...

view/elements/hello.ctp

1 <?php
2 echo 'yes!';
3 //可以支持controller中声明的所有变量和helper
4 echo $session->read('Login.name');
5 ?>

呵呵,很简单吧!

Friday, November 14, 2008

Trouble shooting

Warning (2): Cannot modify header information - headers already sent by (output started at 


F:\Prj_PHP\1.2.x.x\app\config\routes.php:61) [CORE\cake\libs\controller\controller.php, line 587]


Code | Context



$status	=	"Location: http://localhost/posts/view/1"


header - [internal], line ??
Controller::header() - CORE\cake\libs\controller\controller.php, line 587
Controller::redirect() - CORE\cake\libs\controller\controller.php, line 568
BidsController::add() - APP\controllers\bids_controller.php, line 25
Object::dispatchMethod() - CORE\cake\libs\object.php, line 114
Dispatcher::_invoke() - CORE\cake\dispatcher.php, line 259
Dispatcher::dispatch() - CORE\cake\dispatcher.php, line 213
[main] - APP\webroot\index.php, line 90


(default) 4 queries took 53 ms



Nr

Query


Error


Affected


Num. rows


Took (ms)



 



I found some tips from internet:



> Well I'm not surely what exactly is going on but I did have a similar

> problem the other day.


> First of all I would recommend keeping DEBUG set to 2 until you've got


> rid of the issue - at least then if there's an error message you'll see


> it.


> My problem was simply some extra whitespace before the <?php tag in my


> controller. The redirect function uses the PHP header function which


> must be called before ANYTHING is sent to the browser. Extra whitespace


> outwith PHP tags in your controller, or in any other file being


> included will prevent the redirect. This a common reason for redirects


> not working.



 



And I finally found it hide in router.php, which have many blank spaces after  ?>

Thursday, November 13, 2008

什么是ActiveRecord

ActiveRecord是什么:

1. 每一个数据库表对应创建一个类.类的每一个对象实例对应于数据库中表的一行记录; 通常表的每个字段在类中都有相应的Field;

2. ActiveRecord同时负责把自己持久化. 在ActiveRecord中封装了对数据库的访问, 即CRUD;

3. ActiveRecord是一种领域模型

说实话,从去年开始用SubSonic, 我就一直有点困惑, 什么是ActiveRecord, 什么不是ActiveRecord, 以及应该怎么样使用它才对. 把我的一些想法写下来, 希望能和大家交流.

ActiveRecord是什么:
1. 每一个数据库表对应创建一个类.类的每一个对象实例对应于数据库中表的一行记录; 通常表的每个字段在类中都有相应的Field;
2. ActiveRecord同时负责把自己持久化. 在ActiveRecord中封装了对数据库的访问, 即CRUD;
3. ActiveRecord是一种领域模型(Domain Model), 封装了部分业务逻辑;
ActiveRecord不是什么:
1. Row Data Gateway
Row Data Gateway模式中每个对象也封装了数据库记录的状态和持久化到数据库的访问方法; 这两个有时候很难区分. 细微的区别在于Row Data Gateway不封装任何业务逻辑;
2. TableGateway
TableGateway是一种数据访问模式, 对每个表有一个类, 类的方法封装了对单个表的数据操作, 如CRUD; 方法的接受表字段的值作为参数;
比如说对表Person有DAOPerson, 有以下方法:
int Create(string name, bool isMale)
DataSet Find(int personId)
void Delete(int personId)
void Update(int personId, string name, bool isMale)
微软的很多代码示例中使用了此模式;
ActiveRecord的区别在于ActiveRecord的对象中保持了记录的值, 是有状态的, 而TableGateway是没有状态的, 只是一系列数据库访问方法的集合;
3. Table Module
Table Module是一种领域逻辑模式, 一个类对应于数据库中的一个表; Table Module通常和Table Gateway合作, 前者负责基本的业务逻辑, 后者负责数据库访问, 以达到逻辑层和持久化层的隔离; 微软的实例代码经常使用这两者, 如对表Person, 通常会定义两个类, PersonBL和PersonDB, 在PersonBL中处理验证等逻辑, 并调用PersonDB访问数据库, 层间调用使用DataSet或自定义数据传输对象传输数据
在业务逻辑比较简单并且有和表的一一对应时, ActiveRecord相对来说更简单, 因为它在一个类中包括了业务逻辑对象和数据访问, 而且不需要数据传输对象, 减少了维护的工作量;
和Table Module比较起来, ActiveRecord与数据库耦合更紧;
ActiveRecord适用于:
1. 业务逻辑比较简单;当你的类基本上和数据库中的表一一对应时, ActiveRecord是非常方便的, 即你的业务逻辑大多数是对单表操作;
2. 当发生跨表的操作时, 往往会配合使用事务脚本(Transaction Script), 把跨表事务提升到事务脚本中;
3. ActiveRecord最大优点是简单, 直观; 一个类就包括了数据访问和业务逻辑. 如果配合代码生成器使用就更方便了;
4. 这些优点使ActiveRecord特别适合web快速开发, 而正是快速开发框架ROR采用了ActiveRecord, 并且很多类ROR框架如Castle的纷纷效仿才使ActiveRecord重新进入大家视线;
我想这也是为什么Martin Fowler在PoEAA中早就提出了这个模式, 但是直到最近两三年ActiveRecord才热起来可能就是这个原因;
ActiveRecord不适合于
1. ActiveRecord虽然有业务逻辑, 但基本上都是基于单表的. 跨表逻辑一般会放到当发生跨表的操作时, 往往会配合使用事务脚本(Transaction Script)中. 如果对象间的关联越来越多, 你的事务脚本越来越庞大, 重复的代码越来越多, 你就要考虑Domain Model + O/R Mapper了;
2. ActiveRecord保存了数据, 使它有时候看上去像数据传输对象(DTO). 但是ActiveRecord有数据库访问能力, 不要把它当DTO用. 尤其在跨越进程边界调用的时候, 不能传递ActiveRecord对象;

Feedback

2008-05-09 11:04 by 怪怪

不错, 很准确, 赞一个。
另外, 这个模式只是在POEAA中介绍过, 并非Fowler提出的(其实Fowler这辈子, 基本没提出过什么自己的东西); 这种用法也早就普遍存在了, 只是最近一热炒, 就被人为放大了。
不过我个人认为,ActiveRecord虽然提供了便利性, 同时也会带来思维方式和实践方法上的禁锢和负面影响, 所以并不总是提高, 有时也会降低生产效率。这是因为从根本上讲, 有时候我们并不真的需要一个有着固定接口的对象或者类。
回头说到SubSonic, 说实话其作者可能比园子里一些做框架的同志水平还菜很多, 但是SubSonic在很多场景下确实轻量、好用, 这反而是我们的同志应该好好思考的了。

2008-05-09 11:56 by wanghualiang

@怪怪
为什么说作者水平菜?
是代码写的有问题还是架构不是很合理?
没看过源码,但用在项目上了,很简单很好用。

2008-05-09 12:26 by jackyw [未注册用户]

SubSonic提供了很好用的代码生成工具,框架也比较轻量级
是比较好的实现了ActiveRecord模式的框架2008-05-09 12:56 by lovecherry      

在SubSonic之前也看到过buildprovider做orm的demo,但是SubSonic对作者把它做成了产品

#7楼 [楼主]   回复  引用  查看    

2008-05-09 13:37 by kuber      

@怪怪
任何方法都有其要解决的问题域, 我想是因为最近两年Web 2.0热导致的快速的web应用开发大行其道使ActiveRecord被重新注意了.
另: 好久没有看见怪怪有新的文章了. 我很喜欢"闲言碎语"这篇文章. 不过里面好像还有很多未竟之言, 我还等着下文呢.

2008-05-09 13:44 by designbeauty

第一次听见有人说Rob菜的。。。,那正常的人应该是什么水平。
btw. subsonic真的很好用很强大。 楼主可以分享一下经验

2008-05-09 15:10 by 怪怪      

@金色海洋(jyk)
没时间啊..., 而且感觉江郎才尽, 白活不出什么有价值的内容了。 倒是今天早上无缘无故的感觉到一阵喜悦, 似乎有些事更明白了, 但暂时又说不清楚明白了什么。 反正接触的东西越多, 感觉需要学的东西和需要做的工作就更多了。
@kuber
嗯, 可以这么说。 不过其前提是在面向对象或者基于对象的设计和实现方式下。 我对AcitveRecord、 Row Data Gateway、 DTO等等一些方法的质疑, 实际上更多的是针对面向对象的一些痼疾了。
比如就这种CRUD较多的场景, 不采用这些方式, 很显然光写代码时的类型转换, 和数据字典存取的安全性和便捷性, 就够我们一呛。而采用这些方式呢, 通过对象我们就得到了一组固定的接口, 要么接口太多, 要么接口太大, 且不说什么性能和维护的问题,损失掉的灵活性和直接性也非常可惜; 如果再通过新增接口去适配不同的场景, 除了显配一下自己的面向对象功力, 很多时候只是徒增麻烦。
只是我也仅仅在探索阶段, 说些怪话的目的只是抛砖引玉, 呼吁大家一起来看看有啥新的路子没有,而不是局限于过去那些旧的观点和争论。那篇文章之所以写不下去了, 关键是在现有的语言框架下, 我还没找到能完全满足我的想法的设计和表达方式,感觉还需要琢磨一阵子。 如果仅仅是一些小的改进, 感觉说起来也没啥意思~
@designbeauty
呵呵, 菜不菜的也要看那些方面。 我是指对概念的理解和掌握的技巧这两方面。 我看过他的一些Video, 说实话此人对很多概念是相当的模糊; 也用过和改过一阵子SubSonic,这个东西涉及的技巧也绝对比不上园子里一些高手的东西。
不过就像@lovecherry说的, 人家在做产品这一块, 可一点也不菜, 我还是相当的佩服的。另外, 去你网站看了, 很漂亮啊, 至少我很喜欢这种风格, 唉感觉设计还是比技术需要更多的天赋。

2008-05-09 16:34 by good man      

其实在这里我们应该对别人多鼓励,不能说菜不菜,你应该
把别人的错误找出来,达到共同学习,一起进步啊

2008-05-09 16:37 by RicCC      

ActiveRecord的主要优点是一个过渡模式,当你没法从失血一步走到充血模型,或者不大确定是否该这样做时,是一种比较好的选择
另外就算使用充血模式的复杂项目,还是会存在很多适合ActiveRecord的对象
我想当你ActiveRecord使用熟练之后思路应该会不大一样,选择范围会比较开阔

2008-05-09 17:07 by 怪怪      

@good man
哈哈, 这不是个老外吗, 他又不会跑博客园来, 跑来也不认识中文~ 另外, 我一向对名人比对咱一般的兄弟要求严格的多, 可以说刻薄了~
@RicCC
写的比较乱, 重新编辑一下, 两点:
1. 在面向对象这个大前提下, AcitveRecord、 Row Data Gateway、 DTO等形式是否是必要的。
是不是一定要将数据包裹以任何一种形式的对象的方式来处理, 我觉得也是可以有多种判断方式的;比如对于最简单的CRUD系统,或许可以根据在于我们在展示与持久之间,需要(比如各种逻辑包括业务逻辑产生的)手工编码操作数据的次数(和工作)来进行选择?如果在其它地方下功夫(如操作和流程的自动化),可以使得没有对象化而导致的问题小于一个限度, 我们也许就没有必要对象化。
这个问题的根本在于首先要确认我们手头的这个活儿,是否在大模型(不是我们常说的模型)上真的需要将数据对象化, 还是可以用其它模型更好的解决。
2. 如果其实现的核心不是面向对象特性, 比如说利用FP的特性, 或者在支持TMP的语言里。
如果我能得到使用某一方式方法的一切好处, 避免它基于面向对象实现时的缺陷, 根据其特征, 也许各种方式方法还可以分别叫做 AcitveRecord、 Row Data Gateway、 DTO, 但由于不再带有面向对象令人不爽的阴暗方面,那么也许进行选择和判断就容易的多。
如果能够做到, 而且成本很低, 那么也许我们可以忽略这个问题, 长期的按一定规则使用某些方式方法, 这样就可以更加集中注意力解决其它问题。

Monday, November 3, 2008

加拿大老人的幸福生活从房屋开始

www.comefromchina.com
和中国老人们的消费观念不同,加拿大的老年人有钱有闲,穿的最漂亮的,最受尊敬和爱护的就是老人。老年人也自然一直是商家很看重的一片市场。房屋开发商当然 也不会放过这个市场,近年来更是投其所好,开发不少针对老年人的楼盘。公寓的居住生活正在悄然改变老年人退休后的生活方式。
八成人希望提前退休
加拿大老年人协会市场高级副主席David Cravit表示,50岁以上的人群正成为增长迅速、开销额度大的消费群体,他们正在改变着加拿大的经济前景,对各行各业都产生了不可或缺的影响。
很多公司都对这个市场制定了特殊战略,例如Home Depot就针对性的开发了“独立生活项目”,为房主提供灵活性的服务和产品,它可以提供和安装特殊的产品,例如斜坡、可调整的橱柜、活动吧台等,生活因此变的更加简单。和以前相比,现在50岁以上的老人可以更加弹性和舒适地生活。
不知道是代沟还是现实,最近有一项调查,“您希望在多少岁退休?”年龄段在30岁左右的被调查者中,只有20%的人希望在56岁至60岁退休;年龄段在50岁的被调查者中,只有12%的人希望在56岁至60岁退休。看来,现在的退休年龄已经不太接近现实了。
这已经成为一种生活趋势,在30至49岁的人群中,21%的人希望退休后可以过更有意义自由的生活,在50岁以上的人群中,有这种想法的占到36%。
根据对50岁以上人群的调查,34%的人表示,退休后最重要的就是多和家人相处;其次是投身于自己的兴趣爱好,这占到32%;16%的人希望去旅游。
地区和性别的差异也有不同的影响,居住在大西洋省的人,最愿意退休后把时间花在陪伴家人上,占到43%的比例;有趣的是,40%的女性表示,退休后的首要安排就是多和家人相处,而只有26%的男性有这样的想法,他们更多的关注自我,36%的男性希望可以从事自己的兴趣爱好,只有28%的女性这么打算。
大多数50岁以上的加拿大人都对现状表示满意。当被问及,“如果不用考虑钱的问题,退休后您想居住在什么地方?”59%的人希望仍居住在现有的环境里;17%的人希望可以两地迁徙居住;11%的人希望环游世界,不需要固定居所;只有3%的人希望能和全家迁移他国豪宅。
老人换房:省心获利
Royal LePage对50岁以上人群的调查显示,50岁以上的加拿大人超过一千万,这个人口统计的结果,对全国的房屋市场会有一定的影响。
针对这个人口变化的情况,Royal LePage地产投入一个新项目,组建了老年地产专门设计服务组(SRES),这个设计组是经过北美地产认证的。Royal LePage将成为加拿大第一个通过认证的机构,进而成为合格的供应者。这也使得它的经纪们能更好的面对市场的变化,满足50岁以上人群的需求。
Royal LePage地产主席及CEO Phil Soper称,以往的几代人,退休会带来的情况都可以预想到,而现在50岁以上的人群更有自己多样的想法,他们讨厌一成不变的模式,寻找更好的更适合自己的生活方式。而这个SRES项目就是第一时间接触市场,对50岁以上人群进行整体分析和服务,帮助他们计划、讨论销售房屋。
目前,有28%五十岁以上的加拿大人计划出售他们的房屋,以此作为他们老年生活重新安排的一个部分,在这部分人群中,卖房的主要原因如下:
房屋太难打理:67%;
享受房屋增值带来的利益:46%;
计划旅游,无力照顾房子:38%;
没有能力生活自理:30%;
无力支付目前的房屋:23%;
这些五十岁以上的潜在售房者对售房后的计划也不同:37%的人想搬进一个更小的房子,更容易管理,并可以坐享房屋的增值。86%的人想用增值的部分支持退休生活,48%的人想用于旅游,42%的人想留给孩子,7%的人打算用于购买休闲娱乐设施。
原来,50岁以上的人搬家,一般都是选择较小的房子,搬去与孩子同住,或者搬进有人照顾的老人院。而现在50岁以上的老人彻底改变这一状况,他们重新定义了这部分人群想要的生活。由于生命的不断延长,现在的老人有了更多的财富和更好的身体,50岁以上的人群有更多样的选择,而不会只选择一种最好的。
银行:倒按揭以房养老
面对这个大趋势,一些金融机构也有所动作,例如TD银行,针对这些成熟可靠的顾客,他们提供了更多新的服务和产品。现在比较流行的做法是倒按揭,即“住房反向抵押贷款”,说简单点就是“以房养老”。其放贷对象是有住房的老年人,以其自有住房作抵押,银行定期向借款人放贷,到期以出售住房的收入或其他资产还贷。其特点是分期放贷,一次偿还,贷款本金随着分期放贷而上升,负债增加,自有资产减少。由于这种方式与传统的按揭贷款相反,故被称为“倒按揭”。
据了解,这种模式是上世纪80年代中期美国新泽西州一家银行创立的。如今,美国模式的“倒按揭”在加拿大也很受欢迎。美国的倒按揭贷款放贷对象是62 岁以上的老年人,分三种形式发放,联邦住房局有保险的住房倒按揭贷款、联邦住房局无保险的倒按揭贷款、放贷者有保险的倒按揭贷款。
“倒按揭”可以使老人们每个月有充裕的养老金支配,外出旅游、聘请护理等,基本生活得到保障的同时,还能享受生活的乐趣。通过正确的理财,退休一点都不会影响收入和生活质量。
在婴儿潮中,55%的人坦言,房屋是他们退休后的重要收入来源。
低收入:老有所居
加拿大“老有所居”的问题是不是就不成问题了呢?答案应当是:基本上不成问题,但并非全部解决。这是因为:富裕国家也有贫困人口,有些老人(包括早年移民来加的华人)老来没有住房的情况也还是有的;即便是那些已经拥有自有住房的老人,因其年老,在身体健康状况和心理上出现新情况,新需求,因而在“住”的问题上也还需要政府和社会重新加以解决。
在多伦多,低收入人士可以申请房屋补助,获得廉租屋或住房津贴;老年人则主要由公款资助的大多市房屋管理局、大多市房屋公司和多伦多房屋机构等单位根据老人护理需求的程度,提供多种多样的居所:
Senior’s Apartment (高龄人士公寓单位)。入住这里的老人基本上能照顾自己。这些老人公寓多半混杂于其它公寓之间,以便获得照料。
Retirement Home (退休人士房屋)。入住老人大体上能照料自己,但每天需要约一个小时左右的医护照顾。这里有医护人员值班,或有医生定期到访。
Home for the Aged (老人屋)。入住者差不多都是没有独立生活能力的老人,饮食起居和健康状况都需要由他人来照顾。
Nursing Home (护理安老院)。这是提供给那些饮食起居和健康状况都需要长期照顾的老人居住的。主办者主要是私营公司或教会、慈善机构和社区团体,诸如为华人耆老和残障人士提供服务的非牟利机构——多伦多耆晖会等。
上述的老人居所无论是政府主办还是私营和社团主办的,全都不是免费的。当然,交不起房租的低收入老人可以申请住房补助。
对于移民赴加的“团聚父母”来说,住满十年之后,不但可以获得每人每月大约900加元左右的老人保障金,还可以根据身体健康状况、个人意愿和需求,申请入住这些老人公寓或老人屋等老人居所。
一直以来,加拿大都享受“老人天堂”的美誉。而这美好的生活,就从房屋开始。

Friday, October 24, 2008

胡耀邦是怎么玩弄华国锋、邓小平的

胡耀邦是怎么玩弄华国锋、邓小平的(图)? 2008-09-08 20:43:49

---历史本来就这么好看


润涛阎


8-25-08




(一)华国锋是怎么上叶剑英的当的?

这个问题必须搞明白,因为逮捕四人帮是历史大事件。这个事件不仅影响了历史的走向,也对后来登上历史舞台的各路豪杰们暴露自己的人品提供了历史机遇。按照政治常识论说,华国锋虽然在抗日战争时就参加了革命,算是从基层一步步走到了县委领导到地委领导再到省委领导,但从省委(部长)级到副总理、代总理、第一副主席还是在毛泽东临死之前很短时间内完成的,这最后一步是属于“坐飞机”上去的。

华国锋最大的特点是经历了那么多政治运动他基本上没受到过冲击。文革后期,党中央由两拨人马构成:文革得势派和被毛泽东整肃的“老一代无产阶级革命家。”华主席在文革中没有遭受冲击,但他也不是张春桥、江青等极左势力的一员,凭借忠厚老实,文革中并不积极整人的他却能扶摇直上,当上了毛主席临死前的第一副主席,法理上的接班人。所以,他跟老一辈无产阶级革命家们并没有冤;他跟文革得势的四人帮也没有仇。非但如此,毛泽东死前还给他临终嘱托:“有问题,找江青。”因为毛泽东知道华国锋跟江青没有仇恨,否则他说不出这样的嘱托的。

所以,毛泽东逝世的时候,华国锋属于中间派。按照毛泽东的安排,华国锋会被左右两派拉拢,稳稳当当执掌江山。左派硬过头了,就支持一下右派;右派硬过头了,就让左派压一压右派。只要两派中的任何一派垮台,华国锋都会被另一派取而代之。这个道理太简单,童嫂皆知,何况经历过一辈子政治运动的华国锋;这个道理太浅显,以至于浅显到了毛泽东觉得没必要指点华国锋。当时我便跟室友说,邓小平将来必然干掉华国锋。只是室友骂我信口开河(详情请看润涛阎:《华国锋为何丢了江山?》和《毛泽东为何放过邓小平?》等前文)。两年后的1979年,我的推测应验了。

那么,华国锋为何要逮捕四人帮?其实,有三大因素导致华国锋下决心干掉四人帮:

1. 华国锋上了叶剑英的当;
2. 江青对政治一窍不通,不买华国锋的帐;
3. 汪东兴的恐惧。


先说第一条:华国锋上了叶剑英的当

叶剑英在毛泽东死前被剥夺了军权,公开下达了中央文件让他在家“养病”而由陈锡联行驶军委日常工作的职权。毛泽东死后,叶剑英立刻感觉到了恐惧。他知道,如果华国锋跟四人帮联手,他和那些靠边站的老一辈无产阶级革命家们的下场能够体面的在家养老送终就算是好的了。虽然叶剑英是元帅,可四个野战军没有一个属于他的。因为他只是在后方当参谋未在战争中带兵打仗,他从未有过自己的嫡系,这也是毛泽东把老将们干掉后让他执掌军委日常工作的原因。毛泽东放心他这个军中的光杆司令。

叶剑英熟读兵书,当过黄埔军校教官,算是比较奸诈之人。毛泽东死前他想到过离开北京避难,但他没走,因为各大军区都不是他的嫡系。从另一方面想,两军相交勇者胜,鹿死谁手,还不知道呢,怎能离开北京?因为他知道华国锋属于中间人物,未必100%投靠四人帮。左思右想,想出了“离间计”便透过李先念等人给华国锋吹风,说四人帮要“篡党夺权”以增加华国锋对四人帮的心理恐惧感。

华国锋虽然忠厚老实,但他不傻。他自然知道什么叫挑拨离间。然而,说到这里必须谈上述第二条:江青对政治一窍不通。非但如此,她还专横跋扈。有了毛泽东临终给华国锋“有问题找江青”的嘱托(此嘱托是公审四人帮时江青讲出来的,那个时刻估计她造谣的可能性不大。此事也没有人出来反驳江青的说法),她就以“老娘”的口气跟华国锋说话,把华国锋当成儿皇帝看待,而且当面羞辱。

想到江青的言行,加上蠢货王洪文给上海民兵发子弹的荒唐举动,使得华国锋对四人帮搞政变的谣言信以为真。但他知道,四人帮在上海闹事是没意义的,只要他不去上海,就不会发生类似张学良在西安逮捕蒋介石的事变。蒋介石不去西安,张学良不可能去南京搞政变。所以,华国锋不害怕四人帮在上海闹事,他最害怕的是汪东兴与四人帮串通一气。华国锋立刻找到汪东兴,问及四人帮政变的谣言是否属实。汪东兴立刻发现了千载难逢的机遇,便提出甭管四人帮政变不政变,咱们先下手为强,你只要下令,逮捕四人帮的事我一人搞定。

那么,汪东兴为何要逮捕他的主子毛泽东的遗孀?这就必然要交待第三条:汪东兴的恐惧。

毛泽东从没停止过整人的政治运动,毛泽东杀人无数,因为他不担心自己被杀,根本原因是他有个看家狗汪东兴。毛泽东胆敢在中南海批斗刘少奇邓小平,就是因为有汪东兴这个看家狗。只要在中南海,即使他躺在床上双目失明,他照样可以想整谁就整谁,包括身体健康、党政军大权在握的邓小平。究其原因,就因为邓小平只要在中南海,他敢反毛泽东就时刻有被汪东兴逮捕的恐惧。所以,毛泽东一死,汪东兴立刻想到“党心军心民心都不在我们一边。”这是四五事件后毛泽东亲自告诉他的话语。

惹不起主人的时候,看家狗就成了替罪羊。

汪东兴知道,只要把毛泽东的老婆孩子干掉,他的罪恶就变成了功劳。功劳不功劳先放一边,跟着毛泽东整了那么多人,毛泽东死后他只要能活命就该谢天谢地了。毛泽东儿子是个傻子,但他有个不傻但没心计而且给张志新割喉咙的侄子。汪东兴为了自己的一条给毛泽东当过看家狗的命能在毛泽东死后保全下来,他主动担当了干掉毛泽东老婆孩子的宫廷政变角色。这就是为何后来邓小平让他交权,但答应给他政治局委员的待遇时,他不仅不反抗,还高高兴兴地接受了。因为这样的结局远远超过了他当初在毛泽东死时的想象。毛泽东死后他不被杀头当替罪羊就不错了。历史的诡异在于,不是看家狗汪东兴而是傻狗江青却成了毛泽东的替罪羊。

就这样,华国锋上了老江湖叶剑英的当、赶上了毛夫人江青对政治一窍不通、巧遇汪东兴看家狗为主人当替罪羊的恐惧,便“一举粉碎四人帮”成了临时的“英明领袖”。然而,事后华国锋对汪东兴说:“四人帮并没有像叶剑英说的搞阴谋诡计,要是搞,咱也搞不过人家呀。”他说的倒是实话,公审四人帮时也没有找到任何四人帮要搞政变的证据,哪怕是蛛丝马迹。此时,华国锋说出此番话表明他知道当初是上了叶剑英的当了。

上当,这很正常。没有当,哪里还有政治?没有当,哪里还有军事?华国锋让四人帮去参加会议,然后把人家逮捕,那不也是“当”吗?四人帮上了华国锋的当,华国锋上了叶剑英的当,那么,叶剑英要上谁的当呢?










(二)邓小平是怎么玩叶剑英的?

林彪死后,叶剑英代替了林彪在军队中的地位。毛泽东喜欢叶剑英的根本原因上面已经提到,就是叶剑英在军队中没有自己的嫡系。有嫡系的彭德怀,让毛泽东放心不下。他总认为长征的时候让他把指挥权交出来的主意是彭德怀出的,是彭德怀让林彪讲出来的。林彪有嫡系,毛泽东对他十分恐惧。只有叶剑英当军权,毛泽东才放心了下来。

叶剑英当权后,立刻建议毛泽东启用邓小平。叶剑英是第一个敢在毛泽东面前做这个建议的。毛泽东为文革花了那么多心血,打倒的第一号走资派是刘少奇,第二号走资派就是邓小平。即使毛泽东想用邓小平,如果没有元帅级别的人给这个台阶,毛泽东也没脸面提拔邓小平,这等于宣布自己文革搞错了。要知道,在那个“站队”、“划清界限”的年代,敢跟毛泽东提启用被打倒批臭的邓小平,这个胆量不是很多人具备的。叶剑英是有胆有识之士,在文革开始的时候他就带头大闹怀仁堂,被毛泽东称为“二月逆流”。

邓小平出来的时候,毛泽东还是对政治局委员们讲,听你(指着叶剑英说话)的建议,让邓小平出来工作了。毛泽东这么做明显地是表明他有了启用邓小平的台阶,这也算给叶剑英一个面子,更主要的是给自己启用打倒了的人一个说法。毛泽东说这话时邓小平本人也在场。

周恩来逝世,叶剑英建议毛泽东让邓小平给周恩来致悼词。毛泽东后来勉强同意了,其实此时毛泽东对邓小平搞翻案很反感了。主要是看在叶剑英的面子上。他还以为叶剑英会说服邓小平,以后不再搞翻案活动了。可悼念周总理的活动越来越大,最后导致四五事件。四五事件后,毛泽东决定跟邓小平分道扬镳,便把对邓小平的怨恨也撒在了叶剑英的身上,叶剑英提出养病,毛泽东趁机让他“养病”把军权收了回来。

叶剑英对邓小平是有恩的,为了邓小平他自己失势竟然毫不在乎。要知道,在毛泽东弥留之际的失势,极有可能导致死无葬身之地的悲惨结局。如果华国锋跟四人帮合谋,而不是跟叶剑英合谋,文的有四人帮,武的中南海有汪东兴,北京有紧跟华主席的吴德,再把一直不得势的三野粟裕拉出来执掌军权,华国锋的天下便稳稳当当了。叶剑英,邓小平等人按退休待遇就完了,咸鱼翻身非常困难。当然,历史不能假设。假设都是多余的废话。可是,写文章不能一句废话都没有,润涛阎又不是左丘明。

由于叶剑英对邓小平有恩,邓小平便利用一切机会跟叶剑英套近乎。这主要是当叶剑英把华国锋派李先念找他希望得到他的支持便动手逮捕四人帮的消息告诉了邓小平后,邓小平判断出华国锋想得到叶剑英的辅佐,邓小平才借机讨好叶剑英。应当指出:华国锋多此一举,这一手便让老一辈无产阶级革命家们看出他没有当主席的自信了。通过叶剑英等人的说情,邓小平又给华国锋写两封信,表明自己一定不遗余力地辅佐华主席。

本来华国锋对邓小平是有戒心的,但华国锋佩服毛泽东到了神的地步了,而毛泽东说叶剑英“吕端大事不糊涂”便在华国锋的脑子里理所当然地认为叶剑英是老谋深算了,加上叶剑英有恩于邓小平,邓小平骗华国锋可以,但他不能骗恩人叶剑英吧。叶剑英辅佐华国锋,邓小平要想干掉华国锋,必然要首先干掉叶剑英。所以,华国锋认为叶剑英辅佐自己是真心的,那么,即使邓小平想翻天,也无法迈过叶剑英这一关。这样,华国锋就不顾汪东兴吴德陈锡联纪登奎等人的奉劝而让邓小平再次出山了。

邓小平出山后立刻发现,要想当太上皇不把叶剑英的军权夺过来是不可能的。当时华国锋是军委主席,叶剑英是军委副主席,邓小平名列叶剑英之后。叶剑英年龄又比邓小平大一岁,属于大哥加恩人性质。叶剑英以为,自己没有政府工作的经历,有自己和邓小平二人辅佐,华国锋当傀儡,这样你好我好大家好。

然而,邓小平不是那么想的。他认为二人辅佐一人是不可能的,迟早要跟叶剑英摊牌。时间越晚,华(国锋)叶(剑英)的联盟越巩固。自己只能当老三了。第三者插足的名声不是太好。晚摊牌不如早摊牌。此时的邓小平便开始了夺叶剑英军权的筹划了。当叶剑英沉浸在无忧无虑的欢喜之中做八十大寿,邓小平已经设好了干掉叶剑英的一个个圈套了。

邓小平知道,要想干掉叶剑英,只能从军权着手。他还清楚,要是在战争年代,没有嫡系没有带兵打仗的经历,叶剑英只好乖乖地把军权交出来。所以,最佳途径就是打仗。跟谁打?当然一举解放台湾最好,只是海军不行,而且还要跟美国对决。北面的老大哥是动不得的,那就只有南边的越南了。打从美国从越南败退,越南就不怎么看得起中国,有点不尊重中国老二哥。但打越南毕竟是对外用兵,国际社会的反应是重中之重。邓小平立刻到美国访问,见到卡特总统后便告诉卡特,中国要教训一下越南,这个小国太猖狂了。当然邓小平认为打越南也是帮美国出出气,算是改善中美关系的礼物。

卡特听后吃惊不已,但考虑到中国和越南都是社会主义大家庭的成员,互相打起来,如同狗咬狗。美国当然不会干涉。那时的中国百废待兴,穷的老农民连棉裤都穿不上,离争霸世界还远着呢。

邓小平回国后立刻做越南在边境挑衅的舆论宣传工作。对越用兵就成了“自卫反击”之战了。有了这个战争性质,华国锋叶剑英即使想到了邓小平要夺军权,也没有办法了。国际上,美国不会出兵干涉,这一点,邓小平跟卡特当面谈好了。国内,老百姓都被忽悠过了,如果有谁阻止“自卫反击”那就是汉 奸行为了。

对越“自卫反击”战争,由于军委主席华国锋没有指挥的可能,没有指挥过大兵团作战的叶剑英也只好让位给邓小平了。此时,一野的彭德怀四野的林彪罗荣桓早死了,只剩下二野的邓小平和三野的粟裕了。邓小平不让粟裕参加任何军事指挥,把他凉在一边。等到邓小平在战争中夺取了实际军权后,再逼迫叶剑英交出军权,叶剑英只有点头的份了。到此时,叶剑英才知道邓小平是如此阴狠如此毒辣如此无情。

那么,有不少历史学家说叶剑英玩政治不是邓小平的对手,所以败给了邓小平。润涛阎对此结论不以为然。要讲清楚这个道理,有必要讲一个几年前美国发生的一个女人的故事。这个女人长得比较好看,为了防备万一,她出门总是把手枪带上。一次,碰上了一位五大憨粗的流氓。那流氓以为自己的胳膊比她的大腿都粗,她只好乖乖地就范。然而,他错了。她以迅雷不及掩耳之势从书包里掏出手枪,直指他的脑门。流氓只有哀求的份了。但她不久就被她弟弟给强奸了。原因很简单:她不认为她弟弟如此肮脏龌龊,如此丧尽天良。她没有防备呵护他、把他带大的弟弟。

同理,叶剑英知道邓小平会翻案,但华国锋并非左派分子,华国锋是处理四五天安门事件的公安部长,但他仍然给四五事件平了反。其实,引进外资的改革和对外开放是从华国锋开始的。后来批判华国锋引进外资引进西方科学技术是搞“洋跃进”而且华国锋当政后立刻到西方和日本访问,迅速打开国际大门。后来对华国锋的批判,统统都是栽赃陷害。历史终将有一天会还其本来面目的,只是时间问题而已。叶剑英认为既然邓小平要当太上皇,总要有人当傀儡,那何必换掉华国锋呢?

叶剑英更没有想到邓小平会夺他的军权,典型的恩将仇报。尽管叶剑英明白如果邓小平不干掉自己,邓小平的名分只好在自己之后。老邓连这点面子都不给恩人叶剑英,是出乎叶剑英和华国锋的意料的。叶剑英玩政治玩得过玩不过邓小平很难有定论,因为他根本就没打算跟邓小平玩。但有一点是肯定的:邓小平比叶剑英对朋友阴狠得多。叶剑英可以为朋友两肋插刀;邓小平可以往朋友两肋上插刀。从“厚黑学”角度看,叶剑英比邓小平“厚”表现在毛泽东死前叶剑英也能化险为夷,而邓小平命悬一线。叶剑英摸爬滚打一生,朋友不少,敌人不多。而邓小平到了晚年,基本上成了孤家寡人。连徐向前聂荣臻都看透了他的人品。从“黑”的角度讲,邓小平超过叶剑英十倍。


(三)胡耀邦是怎么玩华国锋的?

历史的资料表明,当年邓小平并不敢动华国锋。华国锋一举粉碎四人帮,就连叶剑英都认为华国锋绝不是窝囊废。邓小平给华国锋连写两封效忠信,内心里充满了对华国锋的佩服。那么,邓小平是怎么突然长出了干掉华国锋的豹子胆了的?润涛阎通过对那段历史的研究发现:玩弄华国锋的不是邓小平,而是胡耀邦!

起初,叶剑英的幕僚在毛泽东死的当时建议叶剑英接近华国锋。叶剑英说他不了解华国锋这个人,无法去沟通。华国锋言语不多,属于低调行事的人。当时的老一代无产阶级革命家们对华国锋都不了解,其中当然也包括邓小平。但有一人非常了解华国锋,他就是胡耀邦。

1962年,毛泽东派胡耀邦到湖南。在那里,胡耀邦跟华国锋共事一年半。在那段时间里,二人成了无话不谈的朋友。华国锋认为胡耀邦是爽朗性格靠得住的人;胡耀邦知道华国锋是个厚道老实人。

华国锋当权后便想到了当年好友胡耀邦,让他当有实权的中央组织部长。组织大权就交给了老朋友。胡耀邦当上组织部长第一件事情就是落实华国锋拨乱反正的指示,此时胡耀邦想到了薄一波。薄一波是文革中六十一叛徒集团案中活着的最高人物。胡耀邦认为,薄一波是有能力的,把他解放出来,就等于是薄一波的恩人了。叛徒集团案对薄一波来讲如同压在身上的磐石。

胡耀邦见了邓小平,看到邓小平有调动军队打越南夺了叶剑英实际军权的喜悦,也有不得不给毛主席的接班人华国锋卖命的苦衷。胡耀邦想起当年跟邓小平共事的岁月,那是邓小平复出后与时任科学院党委书记的胡耀邦一起搞了个“汇报提纲”而共同挨整的过程。毛泽东最后一次打倒邓小平时给他的罪状之一就是“三项指示为纲”其中之一就是“汇报提纲”而这个“汇报提纲”不是邓小平搞的,而是胡耀邦搞的。这就使他俩成了患难与共时经历了考验的战友了。

邓小平突然想起了胡耀邦当年曾经跟华国锋共过事,便问及华国锋其人。胡耀邦告诉邓小平,华国锋忠厚老实,说穿了就是个大草包。邓小平听后吃惊不已,觉得胡耀邦看人不准,也就没再细究。

胡耀邦知道,邓小平想让华国锋当傀儡但不知道华国锋是否肯交权。胡耀邦认为,邓小平老了,又想改变毛泽东的终身制搞年轻化,又想实际掌权,那必然是太上皇了。华国锋肯定会愿意当邓小平的傀儡的。问题是,自己比华国锋年龄还大,不可能有作为了,只能当个组织部长了。想到当年与华国锋共事时,怎么自己的心眼也比他多多了呀,怎么会让他当主席,自己当部长?邓小平能甘心,我胡耀邦不甘心啊。既生瑜何生亮!糟糕的是:我是瑜而他根本就不是亮啊。鼠可忍,狮不可忍!

在中央政治局开会的时候,胡耀邦突然给华国锋提点小意见。就像温水煮青蛙,一点点加温。华国锋一开始以为是朋友之间的互相提意见,便没有吭声。有则改之无则加勉吗。何况自己提出要搞党内民主,不能搞一言堂了。可是,邓小平立刻认同了胡耀邦跟自己说过的话,连陈云等老一辈无产阶级革命家们立刻发现华国锋不是个英明领袖。要是真的英明,当即给胡耀邦一个怒喝,大家都会跟着华主席对胡耀邦搞突然袭击无理取闹猛烈抨击。这可是效忠的好机会呀。然而,好面子的华国锋错过了这个机会,让猛虎们知道了华国锋就是一头窜到贵州的驴。

古人都说天上龙肉最香,地上驴肉最嫩。

邓小平本来还在考虑如何让华国锋当傀儡呢,胡耀邦都不认同了。邓小平想了想,让华国锋当傀儡,以后麻烦还是有的。那就是自己就成了“挟天子以令诸侯”的曹操了。华国锋的权力毕竟不是自己给的,而是毛主席给的。这跟汉献帝与曹操的关系一模一样。还不如干脆卸磨杀驴,把这驴肉分着吃了算了。那就让胡耀邦干吧,毕竟胡耀邦听话多了。

邓小平的想法与陈云的想法似乎心有灵谋,还没说出来呢,俩人立刻一点即通。这样,按照邓小平的安排,驴肉分三块:头部(指挥党的枪杆子--军委主席)由邓小平吃;肚子(党中央主席)由胡耀邦吃;四条大腿(国务院总理)由赵紫阳吃。剩下的,就一马勺烩了,让老一辈无产阶级革命家们喝汤,按照刘邦把项羽老爹活蒸了后刘邦也要喝一碗老爹的肉汤的说法,那叫“分一杯羹。”

这样,中南海内外白天互相串联,互通情报;晚上灯火通明,暗潮汹涌。胡耀邦出头干掉华国锋的组织工作、宣传工作按部就班地进行着。首先,要清君侧。

邓小平此时便安抚华国锋,说我是支持你的,但汪东兴不行,纪登奎不行,吴德不行,这些人要下去。华国锋此时知道清君侧后自己也许会大权旁落,回家练毛笔字去,但考虑到跟邓小平对抗,唯有启用汪东兴宫廷政变方式。

吴德陈锡联等人如同热锅上的蚂蚁,惶惶然去找汪东兴,说这样下去,不仅我们都完了,华国锋也会被干掉的!汪东兴不以为然,告诉吴德他们说,华主席关键时刻有魄力力挽狂澜,你们就等着好消息吧。毛主席不会看走眼的,这个世界上没有人甘愿把到手的权力让出来的。再说了,华主席要是个窝囊废,他也逮捕不了四人帮啊。叶剑英自己亲自说的,逮捕四人帮这事,叶帅他不敢干,邓小平也不敢干。

汪东兴此时以为,邓小平还在北京,还在北京卫戍区中央保卫局的监控范围内。只要华主席一声令下,汪东兴吴德联手就把他给干掉了。那么多“篡党夺权”“搞阴谋诡计”的罪证,还不好找?哪条不违反党章不违反国法?至于后事的说法,那怎么说都成。历史是强者说了算。

这个道理华国锋是清楚的,但他自己要反复考虑,在北京干掉邓小平,此时已晚,无法向全国人民交待。而且,北京以外的各大军区司令员都不是自己的人,都听邓小平的指挥。这样,杀掉邓小平后内战打起来的可能性非常之大。为了个人的权力,让北京市民遭受战争创伤,这么做不符合华国锋的人品。华国锋便决定以牺牲自己的权力甚至生命来换取国家不打内战。当邓小平让华国锋手下的干将们包括汪东兴吴德下台时,华国锋没有阻拦。汪东兴预测的事情没有发生。他们感到华国锋出卖了他们而换取自己给邓小平当傀儡的筹码。连陈永贵都看不起华国锋了,骂他丢卒保帅太缺德。

然而,事情的根本是邓小平对提拔他的叶剑英恩将仇报。这是叶剑英本人也万没想到的。没有了叶剑英的军权护驾,华国锋只好交权,因为邓小平知道华国锋不会为了个人权力而打内战的。打内战结局必然是鱼死网破。打完越南以后,华国锋再想干掉邓小平已经太晚了。即使干掉了,鱼死网破,内战打完后,华国锋汪东兴吴德只能是遭到清算。

此时的叶剑英只能考虑自己的后果了。他不能跟华国锋一起灭亡,便自保,配合邓小平搞掉华国锋的台柱子。

事情安排好了,华国锋的台柱子汪东兴吴德纪登奎都下去了。然后告诉华国锋,让他交权。叶剑英在电话里告诉华国锋,只有和平交权才是上策。看到叶剑英出卖了自己,华国锋只好认栽了。胡耀邦让赵紫阳陪同宴请恩人加好友加同事华国锋,在酒桌上跟他说,让他合作。因为开中央全会还是要华国锋主持的,否则就是政变性质了。干掉英明领袖华主席,那时的全国人民是不答应的。北京天安门广场闹事是可能的。

华国锋参加了胡耀邦的宴请,他想告诉胡耀邦:你别高兴的太早。

三杯酒下肚,谁也不想喝了,胡耀邦便提议猜拳。

哥仨好啊,
全来了啊。

吃驴肉啊,
感情厚啊!

华国锋出的是布,胡耀邦出的剪子,华国锋只好举杯一饮而尽。趁着酒兴,他说:

“你两个,加上邓小平,等于我一个。”

胡耀邦听出了华国锋的意思,看来不需要直接告诉他这顿酒席的含义了,华国锋全明白了。但只好打岔说:“我和紫阳加起来,还比不上你呢。”他倒没敢说邓小平同志也比不上华国锋。毕竟言多语失,酒后的话不是闹着玩的。谁知道以后赵紫阳怎么跟邓小平说呢。

华国锋告诉二位:“希望你们二位总结我的教训,千万莫要重蹈我的覆辙。”

胡耀邦哪里想的到后来自己的下场会更惨?他还继续开导曾经共事4次的老朋友:

“华国锋同志,上下升贬,这是政治斗争中的家常便饭。一个政治家,要准备两手,—是整人,二是挨整。你这小小变动,根本算不了什么。我、紫阳,特别是小平同志,大起大落多少次啊!”

听到这话,赵紫阳想起了一只躺在草木林中受了伤的警犬。跟受了伤、流着血的人在—起,仍然是很危险的。此时华国锋毕竟还是党中央主席,军委主席,国务院总理。赵紫阳对胡耀邦说:“不要提那么多了,这些还要经中央全会讨论,究竟中央全会能不能批准,还不一定呢。”

华国锋说, “你还想给我点安慰。”

胡耀邦告诉华国锋:“紫阳说得并不错。我们希望我们还能在一起合作共事。咱们三个人中,你年龄最小,希望还是很大的。来,为将来干杯!”

华国锋连连摇头:“共事?哈哈,你曾经跟我共事,抓到了我的软肋,配合了小平,我哪里还敢再跟你共事?别让我当你的副主席跟你共事,我不会干的。你说什么‘将来’哈哈,我们应该为你荣升党中央主席干杯!我配合你们,和平交接所有的职务,我不会闹事的。”

“好,这可是你亲口说的,紫阳作证!干杯!”

“我说话从来算数,决不会当面是人,背后是鬼!放心吧!”

胡耀邦说:“国锋同志的话,我是深信不疑的!”

听了胡耀邦的话,华国锋暗忖:我不恨邓小平,他毕竟不是我的朋友。在我背后捅一刀的,竟然是我几十年认同的“可信赖的朋友”以“胸怀坦荡”自称的胡耀邦同志。华国锋自言自语道:恩将仇报者,必遭报复。只要死不了,我就好好活着。多练练太极俯卧掌,就能看到胡耀邦的结局。


(四)胡耀邦是怎么玩邓小平的?

按照邓小平隔代指定接班人的“摸石头过河”摸出来的经验来回忆,我们可以知道邓小平让胡耀邦下台的前因后果了。而这些历史,他们当事人是无法启口说出来的。现在就让我们顺瓜摸藤,让时光倒流。

胡耀邦上台后发现:邓小平把责、权、利分开了。有责任的没有权力,有权力的不负责任,有责任的没有利益。事情干好了的,功劳归打牌的邓小平;干砸了的,责任由干事的人顶缸。胡耀邦还发现,你干了半天没功劳只有过错不算,说不定哪天邓小平就让你下台,然后屎盆子一股脑都扣在你头上。

这个小算盘胡耀邦算清楚了,聪明的胡耀邦自然有他的对应之策。

当时,邓小平给了胡耀邦一点人事权。胡耀邦就利用这点权力搞起了第三梯队。等到自己的接班人胡启立当上了政治局常委,胡耀邦就不怎么往邓小平那里请示汇报了。胡耀邦计算着,如果邓小平以“反对资产阶级自由化不利”的借口让自己下台,那也得有个因应之策。胡耀邦走了两步棋:

第一步,以反对腐败的提法应对反自由化。跟邓小平等老一辈无产阶级革命家们一样,胡耀邦也亲眼目睹过腐败的国民党惨败经历。而邓小平的改革必然导致执政者腐败。胡耀邦及时地可以说是超前意识地提出了“新矛盾论”那就是今后的社会主要矛盾是腐败与反腐败的矛盾。胡耀邦这一手十分毒辣,如果邓小平把胡耀邦干掉,不论确切原因如何,人民会认为胡耀邦主张反腐败而遭到罢黜,胡耀邦必然落个清官名声。

第二步,自己提出退休。邓小平本人万没想到在他眼里年纪轻轻的胡耀邦会提出退休!这分明是对老一辈无产阶级革命家们的咒骂。

从经验中获得知识摸石头过河的邓小平立刻认识到:胡耀邦之所以敢提出退休,是因为我给了他选择第三梯队的权力。胡耀邦都退休了,我邓小平能不腿休吗?我邓小平一旦退休,就什么权力都没了。而胡耀邦退休后,接班的胡启立就是他的儿皇帝,他胡耀邦就是多年媳妇熬成婆的第二个邓小平了。

想到这里,邓小平不寒而栗,甚至悔恨交加。当初不该把培养第三梯队的权力交给他。要自己隔代指定接班人。

邓小平把此话告诉陈云后,陈云点头称是:如果不是胡启立,而是咱们俩选择的人接胡耀邦的班,胡耀邦还敢提出退休吗?

陈云的话跟邓小平想到一块去了。从那一刻,邓小平决定要隔代指定接班人。所以,赵紫阳当上总书记后,发现连一点人事权都没有,连更换中央宣传部长都办不到。这一点,赵紫阳也在后来的谈话中多次说过,任何人事权,都是邓小平和陈云商量好了就算数了。

胡耀邦用以上两步棋玩弄了邓小平,达到了自己的目的,胡耀邦死后的名声远远超过邓小平的想象,逼使邓小平不得不出席胡耀邦的追悼会,还引发六四事件,将邓小平暴力镇压北京市民学生的历史牢牢写在耻辱柱上。邓小平的一生,败给过毛泽东,但终于把毛泽东的妻子逼死、侄子坐牢,算是出了口恶气。要不是陈云极力反对,说“如果你们非要杀掉江青,那要在上面公开写上陈云不同意”否则,邓小平就把江青杀掉了。除了毛泽东外,邓小平没有败给任何人。但他败给了胡耀邦。从历史名声角度看,是胡耀邦把邓小平搞惨了。

但从双赢角度来看,胡耀邦还不如不出卖老朋友华国锋,即使自己当不上儿皇帝,也不会被活活气死。非但如此,胡耀邦晚年想起华国锋酒桌上的话语,不可能后背不冒凉气。至于良心是否得到拷问,那就不得而知了。

邓小平死前搞明白了:当初让华国锋当傀儡才是英明决策。华国锋的人品在共产党的历史上是个出类拔萃的异类。胡耀邦并不是像邓小平猜测的那样听话,甘当儿皇帝。这不仅仅是因为他的人品,而且还有他的个性。尽管胡耀邦的人品在共产党员中算是很光明磊落的了,但远不是华国锋那样光明正大,下台前早就设计好因应之策,算是运筹帷幄了。只是胡耀邦对权势远比不上华国锋那样捡得起放得下。

邓小平清楚:华国锋下台后没有什么朋友敢接近,当年的追随者们都跑得无影无踪了,只有自己跟老伴在大院子里度过余生。但他能活过胡耀邦赵紫阳,表明他的内心对权势对荣辱都看得开。用非法手段逮捕四人帮表明江青太过分了,责任不在华国锋。胡耀邦失势不算下台,那是邓小平把权收回。而华国锋才是被真正逼下了台。可胡耀邦被活活气死了,表明他的心胸没有华国锋宽广、豁达。

从这件事上邓小平摸石头过河又摸到了一块大石头:等到自己见马克思之前,要隔代指定接班人。


(五)薄一波是怎么玩胡耀邦的?

胡耀邦得到了华国锋给他的人事权后便极力让薄一波从叛徒集团案中解放出来。薄一波出来后对胡耀邦自然感恩戴德,全力支持胡耀邦。直到赵紫阳搞什么“党政分家”时,薄一波还是站在胡耀邦一边的,因为他认为邓小平不会干掉胡耀邦的。赵紫阳搞经济可以,但玩权术远不是胡耀邦的对手。

邓力群等人一直对胡耀邦赵紫阳不满。胡耀邦搞的第三梯队让邓力群痛苦不堪。邓力群便从小报上找到了讽刺胡耀邦第三梯队的顺口溜,把它交给了邓小平。邓小平把这个顺口溜也转给了薄一波等人,只是没说是谁交给邓小平的。顺口溜是这么写的:

打牌一夜两夜不睡,
跳舞三步四步都会,
搞女人五个六个不累,
喝酒七两八两不醉,
工作九年十年不会。
要问此公是谁,
党的第三梯队。

薄一波看后立刻想到不管这是谁送给邓小平的,但胡耀邦要是下台,得势的必然是赵紫阳。薄一波回忆起了自己的一生,说什么论本事也不输给华国锋胡耀邦赵紫阳他们啊。

人都是有自信的,尤其是爬到人上人的人。

薄一波立刻想到胡耀邦可能跟邓小平有比较大的分歧了,否则邓小平不会把这个东东交给我的。让我取而代之?这个可能性应该是有的。唯一的麻烦就是邓小平还有大家都会认为我薄一波跟胡耀邦是一拨的,我要跟他划清界限。但现在攻击胡耀邦还太早,先得给赵紫阳搞个小鞋穿。于是,薄一波也在小报上找到了赵紫阳与胡耀邦“党政分家”政府那摊第三梯队的顺口溜。他便把这个顺口溜交给了邓小平。顺口溜是这么描述政府部门如何选择第三梯队的:

东厂当官东厂乱,
调到西厂照样干。

西厂当官西厂愁,
调到南厂去当头。

南厂当官南厂亏,
调到北厂当指挥。

北厂当官北厂糟,
升到局里管指标。


那时每个局都有管指标管计划的副局长。而副局长就是这么升上来的。

薄一波发现邓小平也对赵紫阳的工作时有批评,便认为此时是干掉胡耀邦的时机了,便到处活动,便把所有社会不稳的因素统统算在胡耀邦的帐上。

胡耀邦此时才彻底明白什么叫“恩将仇报”什么叫“前车后辄,”眼前华国锋的眼神在不停地晃动,脑子里闪烁“报应”二字。

薄一波成了干掉胡耀邦的马前卒,但胡耀邦下台后,薄一波什么也没捞到。他说什么也搞不懂:邓小平对叶剑英恩将仇报而得了军权成了太上皇;胡耀邦对华国锋恩将仇报得了党主席;这都成了传统了,可凭什么到我这儿就不灵了?我对胡耀邦恩将仇报为何就什么都得不到了?请问:天理何在?法理何在?良心何在?

薄一波死前都搞不明白:

天理,那是唯心主义者才掌握的东西,与彻底的唯物主义共产党没什么关系;
法理,那是被强者随时玩弄随时更换的妓女,初一、十五不一样;
良心,那个东东对政治家来说,只有被利用的使用价值、出卖价值,没有保存价值。

薄一波感叹,还是曹雪芹看得准:乱哄哄你方唱罢我登场,到头来都是给别人做嫁衣裳。

并非邓小平看不上薄一波,而是邓小平终于认识到“恩将仇报”成为传统后如果不拔刀斩断这个传统,自己前边死,后边就遭鞭尸。隔代指定的接班人也只好效仿了。



上面是华国锋的书法《人格是金》