分类目录归档:程序开发

JavaScript面向对象基础

传统的Web开发模式中,JavaScript一直被当作过程化的语言使用,比如表单验证之类。其实,JavaScript提供了完善的机制来实现面向对象的开发思想,这在Ajax技术上,表现得淋漓尽致。

总结下JavaScript面向对象的基础:(结合了《Web 2.0 开发技术详解》这本书,希望对大家有帮助)

 

1 定义类

用定义函数的方式(即用“function”关键字定义):

function class01 () { //……. }

此处class01是一个函数,也是一个类。作为函数,可以理解为类的构造函数(初始化)。在JavaScript中,函数和类就是一个概念。

 

BTW:函数声明的方法:

(1)function fun1(…)  {…}

(2)var fun1 = function(…) {…};     // 注意无名函数在调用之前,必需先定义

(3)var fun1 = function fun2(…) {…};

(4)var fun1 = new Function(p1, p2, …, pN, body);

 

2 定义对象

1)使用new操作符。– 适用内部类(如Date类,Array类,String类,RegExp,Math,Error,Object)和用户自定义类。

如:

function class01() { // 类成员的定义 }

var obj = new class01();  // 即所有函数都可以用new来操作。

 

e.g:

var obj = new Object();

obj.name = "ljlwill";               // 定义name属性

obj.hello = function(…) {….}    // 定义obj的hello方法

 

有个小技巧,以下大家发现没有

var namespace = new Object();

var namespace.class1 = function() {…..}

var obj1 = new namespace.class1();

>> 对象名起到一个命名空间的作用。先用new出一个namespace对象,把namespace对象的方法定义为类的形式,再new出一个新的obj1

 

2)使用{}大括号创建无类型对象

传统的面向对象语言中,每个对象都会对应到一个类,而JavaScript中的对象,其实就是属性(方法)的一个集合,无严格意义的类的概念。故可用{}的语法创建一个对象。

如:

var user = { name: "ljwill",        // 定义了name属性,初始化为“ljlwill

hobby: ['basketball', 'football', 'ping-pong'],  // 定义了爱好,类型为数组

// 定义hello方法

hello: function() { alert("Hello, " + this.name); },

…… }

可看到形式,其中属性名和其定义之间用:(冒号)隔开,以,(逗号)结尾。很是紧凑,清晰,减少代码体积。

 

3 对象的属性和方法的引用

1) 对象名.属性名(方法名) — 即用.符号引用

2) 对象名["属性(方法)名"]  — 即用[]符号引用

如:

var arr = new Array(); // 为数组添加一个元素a

arr["push"]("a"); arr.push("a");

// 计算数组长度

var len = arr["length"];

var len = arr.length;

用方括号[]的引用对象属性(方法),类似于数组,体现了一个JavaScript对象就是一组属性(方法)的集合这个性质。适用于不确定具体要引用哪个属性(方法)的场合。 在document的集合对象中,也有类似[]的用法,如果引用某一个“myForm"的表单对象:document.forms["myForm"],当然,也可以写成document.forms.myForm。(注意,这里的forms对象是一个内部对象)

 

4 对象的属性和方法的操作

如何为一个对象添加,修改或删除其属性和方法呢?

在一些语言中,要修改一个已生成的对象的属性或方法,必须在对应的类修改,重新实例化,再重新编译。JavaScript却可以灵活的动态添加,修改或删除其属性和方法。

如:先创建一个空对象user:var user = new Object();

1)添加属性。

user.name = "jinlie";   // 为其添加name属性

user.age   = 25;       // 为其添加age属性

user["name"] = "jinle"   // 使用[]

user["age"]   =  "25"

// 可否发现[]方式有个而外特点, 可以使用非标识符字符串作为属性名称,如数字开头或出现,user["007 name"] = "jinlie",如果user.007 name就错误了。

// 这一属性可以很容易实现一个简单的哈希表。在此不详述。 输出试试:alert(user.name);

 

2)添加方法

user.getName = function() { 

     // 可添加参数 return this.name;

} // alert(user.getName());

 

3)修改属性

即用新的属性替换旧的属性

user.name = "hello"; //  测试:alert(user.getName());

 

4)删除属性和方法

将其置为undefined即可。

user.name = undefined;

user.getName = undefined;

 

到此,我们可以感受JavaScript的强大的灵活性。

 

5 prototype原型对象

在JavaScript中,每个函数(function)其实也是一个对象,他们对应的类是“Function”,而每个函数对象都具有一个子对象prototype。所以,prototype表示了该函数的原型,也即是表示一个类的成员的集合。 prototype是一个对象,也可以对其方法进行动态修改:

如:

function class01 { // ….. }

// 对类的prototype对象增加方法

hello class01.prototype.method = function() { alert("Hello Word!"); }

var obj = new class01();

obj.hello();   // 调用hello()方法

 

看个有趣的例子:

// 为所有的Function函数对象添加method1方法

Function.prototype.method1 = function() {

     alert("This is a function.");

}

function func1() { alert("this is func1"); }

func1.method1();                        // 执行了alert("This is a function")

func1.method1.method1();      // 执行了alert("This is a function")

func1.method1.method1()是个递归定义,因为method1本身也是一个函数,便具有了函数对象的属性和方法,所以它调用了method1这个函数对象的method1方法。结论:所有对Function类型的方法扩充都具有这样的递归性质。

 

6 类成员的访问机制

1)公有成员,私有成员

在JavaScript中,并没有特殊的机制(比如用像一些语言中有关键字private,public等)来访问其成员,但可以通过变量的作用域性质来实现这些功能。– 在一函数内部定义的变量称为局部变量,它不可以被函数外边的程序所使用,却可以被其内部定义的嵌套函数所使用。

有思路了吧?如:

function class1() {

var name = 'ljlwill';      // 私有属性

 

// 私有方法

function privateMethod(…) {

   ….

}

 

// 公有方法

this.pubMethod = function(…) {

    ….

}

}

 

// 建以对象obj

var obj = new class1();

obj.pubMethod();

注意,prototype定义的类成员却不可以访问其构造体中定义的局部变量(私有成员)

 

2)静态成员

它可以通过“class.staticMemberName(类名.静态成员名)”访问。

实现:

function class1() {

     ….

}

calss1.staticName = "ljlwil";

class1.staticMethod = function(…) { …. }

 

如果想给所有的对象添加静态成员(比如静态方法):

Function.prototype.staticMethod = function(…) { … }

 

7 类的继承

继承是OOP中很重要的IDEA。很抱歉,JavaScript没有专门的机制(extends)来实现,但还是可以使用以下方式实现继续

1)拷贝一个类的prototype到另外一个类来实现继承。

如:

function class1() {

   …

}

function class2() {

  ..

}

class2.prototype = class1.prototype;    // 这样class2有了其公有类成员的属性或方法

 

下边的语句是成立的:

var obj = new  class2();

obj instanceof class1;     // intanceof 操作符判断一个对象是否是某一类的实例。

obj instanceof class2;

注意:在JavaScript中,除了基本数据类型(数字,字符串,布尔等),所有的赋值或函数传参都是引用传递,所以class2的prototype引用了class2的prototype,如果改变class2的prototype,class2的prototype也随之改变。所以,注意要不允许类成员的重新定义,用prototype实现继承,还是蛮好用的。

 

2)使用发射机制实现正确继承

发射机制指的是程序在运行时能够获取自身的信息。

如:

// 为Object类添加静态方法extend

Object.extend = function(destination, source) {

for(p in source) {

destination[p] = source[p];

}

return destination;

}

 

// 通过Object类为所有对象添加extend方法

Object.prototype.extend = function(object) {

return Object.extend.apply(this, [this, object]);

}

 

// 定义class1

function class1() {…)

 

class1.prototype = {

method1: function(…) {…},

method2: function(…) {….}

}

 

// 定义class2

function class2() {….}

 

// 继承

class2.prototype = (new class1()).extend({         // 不要把new class1()换成class1,这样和prototype实现继承一样的错误哦!

// 重新定义method1

method1: function() {

    ….

}

}

);

 

// 定义对象

var obj1 = new class1();

var obj2 = new class2();

// 试试吧

obj1.method1();

obj1.method2();

obj2.method1();

obj2.method2();

 

8 抽象类

JavaScript的抽象类也是没任何机制支持的。但却还是可以实现。

传统的面向对象语言中,抽象类的概念是,具有虚函数的类。而虚函数指的是,只做了一个声明而未实现的方法。在JavaScript中,虚函数可以看作该类中没有定义的方法,但已经通过this指针使用了(在其他语言中,虚函数必须先声明)。

如:

Object.extend = function(destination, source) {

for(p in source) {

destination[p] = source[p];

}

return destination;

}

 

// 定义一个抽象基类base,无构造函数
function base() {…}

base.prototype = {

initialize: function() {

this.oninit();     // 调用了一个虚方法

}

 

// 如果要在基类中添加虚方法的一个定义,也可以的:

// oninit: function() {}   // 虚方法是一个空方法,由派生类实现

}

 

function class1() {….}

class1.prototype = (new base()).extend({

// 实现抽象类中的oninit虚方法

oninit: function() {

// 实现

}

});

 

好,今天就谈到这,JavaScript一个非常灵活的设计语言。学好基础,看看那些类的设计模式,和一些出名的框架,不禁要;)一下!

用php处理百万级以上的数据提高查询速度的方法

网上转帖(原文地址不明):

1.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
 
2.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
 
3.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num is null
可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
select id from t where num=0

4.应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num=10 or num=20
可以这样查询:
select id from t where num=10
union all
select id from t where num=20

5.下面的查询也将导致全表扫描:
select id from t where name like '%abc%'
若要提高效率,可以考虑全文检索。

6.in 和 not in 也要慎用,否则会导致全表扫描,如:
select id from t where num in(1,2,3)
对于连续的数值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3

7.如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然 而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:
select id from t where num=@num
可以改为强制查询使用索引:
select id from t with(index(索引名)) where num=@num

8.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where num/2=100
应改为:
select id from t where num=100*2

9.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where substring(name,1,3)='abc'–name以abc开头的id
select id from t where datediff(day,createdate,'2005-11-30')=0–'2005-11-30'生成的id
应改为:
select id from t where name like 'abc%'
select id from t where createdate>='2005-11-30' and createdate<'2005-12-1'

10.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
 
11.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。

12.不要写一些没有意义的查询,如需要生成一个空表结构:
select col1,col2 into #t from t where 1=0
这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
create table #t(…)

13.很多时候用 exists 代替 in 是一个好的选择:
select num from a where num in(select num from b)
用下面的语句替换:
select num from a where exists(select 1 from b where num=a.num)

14.并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。

15.索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有 必要。

16.应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。

17.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
 
18.尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。

19.任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。

20.尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。

21.避免频繁创建和删除临时表,以减少系统表资源的消耗。

22.临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。
 
23.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。

24.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。

25.尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。

26.使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。
 
27.与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时 间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。

28.在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。

29.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。

30.尽量避免大事务操作,提高系统并发能力。

深入理解PHP之require/include顺序

在大型的Web项目中, include_path是一个模块化设计的根本中的根本(当然,现在也有很多基于autoload的设计, 这个不影响本文的探讨), 但是正是因为include_path, 经常会让我们遇到一些因为没有找到正确的文件而导致的看似”诡异”的问题.

也就有了如下的疑问:

include_path是怎么起作用的?

如果有多个include_path顺序是怎么样的?

什么情况下include_path不起作用?

今天, 我就全面的介绍下这个问题, 先从一个例子开始吧.

如下的目录结构:

  1.   root
  2.     ├ 1.php
  3.     ├ 3.php
  4.     └ subdir
  5.          ├ 2.php
  6.           └ 3.php
在1.php中:
  1. <?php
  2. ini_set("include_path", ".:path_to_subdir");
  3. require("2.php");
  4. ?>
而在2.php中:
  1. <?php
  2. require("3.php");
  3. ?>
而在root目录下的3.php打印出”root”, 在subdir目录下的3.php打印出”subdir”;

现在, 我的问题来了:

1. 当在root目录下运行1.php, 会得到什么输出?

2. 在subdir下运行上一级目录的1.php, 有会得到什么输出?

3. 当取消include_path中的当前目录path(也就是include_path=”path_to_subdir”), 上面俩个问题又会是什么输出?

PHP中的include_path

PHP在遇到require(_once)/include(_once)的指令的时候, 首先会做如下的判断:

  1. 要包含的文件路径是绝对路径么?
  2.  如果是, 则直接包含, 并结束.
  3.  如果不是, 进入另外的逻辑(经过多次调用, 宏展开后进入_php_stream_fopen_with_path)寻找此文件
接下来, 在_php_stream_fopen_with_path中, 会做如下判断:
  1. 要包含的文件路径是相对路径么(形如./file, ../dir/file, 以下用"目录相对路径代替")?
  2.  如果是, 则跳过include_path的作用逻辑, 直接解析相对路径(随后单独介绍)
会根据include_path,和当前执行文件的path组成一个待选的目录列表, 比如对于文章前面的例子来说, 会形成一个如下的待选列表
  1. ".:path_to_subdir:current_script_dir
然后, 依次从待选列表头部开始, 根据DEFAULT_DIR_SEPARATOR(本文的环境是”:”)取出待选列表中的一个路径, 然后把要包含的文件名附加在这个路径后面, 进行尝试. 如果成功包含, 则返回, 否则继续下一个待选路径.

到现在为止, 我们已经可以回答我开头提出的3个问题了.

1. 因为在root目录下执行, 所以在1.php中包含2.php的时候, include_path的第二个待选路径起了作用(path_to_subdir), 找到了path_to_subdir/2.php, 而在2.php包含3.php的时候, 当前工作目录是root下, 所以在包含3.php的时候, include_path的第一个待选路径”.”(当前工作目录)下就找到的匹配的文件, 所以得到的输出是”root”.

2. 同1, 只不过当前的路径是subdir, 所以得到的输出是”subdir”.

3. 因为没有了当前路径为include_path, 所以在root目录下运行的时候2.php中包含3.php的时候, 是path_to_subdir起了作用, 所以无论在root还是subdir都将得到”subdir”的输出.
而如果在2.php中清空include_path,

  1. <?php
  2. ini_set("include_path", '');
  3. require("3.php");
  4. ?>
那么将会是current_script_dir起作用, 而这个时候current_script_dir是2.php的路径, 所以还是会得到”subdir”的输出.
 

目录相对路径

在使用目录相对路径的情况下, 相对路径的基点, 永远都是当前工作目录.

为了说明在目录相对路径下的情况,我们再看个列子, 还是上面的目录结构, 只不过1.php变成了:

  1. <?php
  2. ini_set("include_path", "/");
  3. require("./subdir/2.php");
  4. ?>

2.php变成了:

  1. <?php
  2. require("./3.php");
  3. ?>

如果在root目录下执行, 2.php中寻找3.php将会在当前目录的相对路径下寻找, 所以得到的输出是”root”, 而如果是在subdir下执行上一级目录的1.php(php -f ../1.php), 将会因为在subdir下找不到”./subdir/2.php”而异常退出.

后记

1. 因为使用include_path和相对路径的情况下, 性能会和寻找的次数有关, 最坏的情况下, 如果你有10个include_path, 那么最多可能会重试11次才能找到要包含的文件, 所以, 在能使用绝对路径的情况下最好使用绝对路径.

2. 因为目录相对路径的basedir, 永远都是当前工作路径, 如果要使用, 需要和实际部署路径相关, 所以实际使用的很少(当然, 也有借助chdir来完成的模块).

3. 在模块化的系统设计中, 一般应该在模块内, 通过获取模块的部署路径(dirname(__FILE__), php5.3以后更是提供了__DIR__常量)从而使用绝对路径.

转:http://www.uh80.com/?p=493

ASP连接MySQL数据库

用php一段时间,现得处理asp方面的内容,却有些生疏了。为此,记下这个基本的数据库(MySQL)连接,以便使用。

在WINNT里,MySQL的ODBC驱动是没有安装的,网上搜索mysql-connector-odbc-5.1.5-win32.msi(看所要求适合哪个版本),默认安装,重启。然后直接连接的代码为:(DNS连接就先不整了)

“Driver={MySQL ODBC 5.1 Driver};database=数据库名;server=服务器IP地址(本地可localhost);uid=登录用户名;password=密码”

Zend Studio实现自动换行

Zend Studio默认没有自动换行这一功能,我使用的是V7.0,在 http://ahtik.com/eclipse-update 有其组件实现自动换行。

实现:菜单”help” –> “Install New Software…”,然后点击”Add”按钮,接着填写上Name(随便命名),Location(即http://ahtik.com/eclipse-update ),接下按着提示操作,即可安装成功。

最后重启Zend Studio,在编辑工作区,点上右键,选上“Virtual Word Wrap”便实现文字过长超过编辑区时自动换行了。

Session的Contents.RemoveAll()和Abandon()区别

还是罗嗦下:

Contents.RemoveAll(): 删除Session.Contents集合中的所有变量
Abandon(): 撤消当前Session对象并结束当前用户会话

显然,执行这两个方法都会释放当前 用户会话的所有Session变量,不同的是Contents.RemoveAll()单纯地释放Session变量的值而不终止当前的会话,而Abandon()除了释放Session变量外还会终止会话引发Session_OnEnd事件

PHP中__construct(), __destory(), __get(), __set(), __call(), __toString(), __clone()函数简说

(1)__construct() 是PHP内置的构造函数, 是同PHP 解析引擎自动调用的, 当实例化一个对象的时候,这个对象的这个方法首先被调用。

例:class Test

       {

             function __construct()

            {

                  echo “This is __construct function!”;

             }

            function Test()

           {

                 echo “This is Test!”;

           }

     }

$objTest = new Test;      // 运行结果是“This is __construct function!”

(2)__destory()是PHP内置的析构函数,当删除一个对象或对象操作终止的时候,调用该方法,所以可进行释放资源之类的操作。

class Test

{

      function __destory()

      {

            echo “This is  __destory function!”;

       }

}

$objTest = new Test;       // 运行结果是“This is __destory function!”

(3)__get()当试图读取一个并不存在的属性的时候被调用,类似java中反射的各种操作。

class Test

{

      function __get($key)

     {

           echo $key, “doesn’t exist!”;

      }

}

$objTest = new Test;

$objTest->Name;    // 运行结果是“Name does’nt exist!”

(4)__set()当试图向一个并不存在的属性写入值的时候被调用。

class Test

{

      function __set($key, $val)

      {

              echo “Can’t assign\”” . $val . “\” to “. $key;

      }

}

$objTest = new Test;

$objTest->Name = “ljlwill”;    // 运行结果是“Can’t assign “ljlwill” to Name”

(5)__call()当试图调用一个对象并不存在的方法时,调用该方法。

class Test

{

     function __call($key, $args)

    {

         echo “The function \””. $key .”\” doesn’t exist. it’s args are “. print_r($args);

    }

}

$objTest = new Test;

$objTest->getName(“2004”, “ljlwill”); 

// 运行结果是 The function “getName” doesn’t exist. it’s args are: Array(

   [0]  => 2004;

   [1]  => ljlwill; 

)

(6)__toString() 当打印一个对象的时候被调用,类似于java的toString方法,当我们直接打印对象的时候回调用这个函数。

class Test

{

     function __toString()

     {

              return “This is Test!”;

     }

}

$objTest = new Test;

eho $objTest;         // 运行结果是“This is Test!”

(7)__clone() 当对象被克隆时,被调用。

class Test

{

        function __clone()

        {

                echo “I am cloned!” ;
        }

}

$objTest = new Test;

$objCloneTest = clone $objTest;    // 运行结果是“I am cloned!”

如何提高 ASP 应用程序性能之实践压力测试

前记:个人感觉这是一篇很不错的文章,值得认真拜读。思想严谨的同时也不要趋于死板,应融会贯通,掌握精髓。

ASP开发人员为 了在他们的设计项目中获得更好的性能和可扩展性而不断努力。幸运地是,有许多书籍和站点在这方面提供了很好的建议。但是这些建议的基础都是从ASP平台工 作的结构上所得出的结论,对实际获得的性能的提高没有量的测量。由于这些建议需要更加复杂的编码过程并降低了编码的可读性,开发人员就只能在看不到实际运 行效果的情况下,独自衡量为了提高他们ASP应用程序的性能是否值得付出这些代价。

本文分为两大部分,我将介绍一些性能测试结果, 帮助开发人员来确定某一特定举措是否不仅对将来的项目来说是值得的,并且能够对原来的项目进行更新。在第一部分我将回顾一些ASP开发的基础性问题。在第 二部分,将涉及一些最优化ADO函数,并将它们的结果与调用VB COM对象执行相同ADO函数的ASP页面进行比较。这些结果很让人开眼界,甚至有些时候是很令人吃惊的。

一、在本节中,我们将回答以下问题:

  • 将ASP生成的内容写入响应流中最有效的方法是什么?
  • 是否应该开启缓冲器?
  • 是否应该考虑向ASP代码中增加注释?
  • 是否应该为页面明确地设置默认语言?
  • 如果不需要,是否应该关闭Session 状态?
  • 是否应该把脚本逻辑放在子程序和函数区中?
  • 使用包含文件有什么影响?
  • 执行错误处理时会施加什么样的负载?
  • 设置一个上下文处理是否对性能有影响?

所有测试都是用 Microsoft 的 Web 应用程序压力工具(Web Application Stress Tool) 来进行的,这是一个免费的工具,可以在微软站点找到。我用 WAST 创建了一个简单的 test 脚本,反复调用下面所描述的 ASP 页面测试(每个超过70,000次)。反应的时间基于平均最后字节总时间(TTLB), 也就是从最初请求的时间到工具从服务器接收最后一位数据的时间。我们的测试服务器是一个Pentium 166,内存为196MB,客户机为Pentium 450,内存为256MB。你也许会想这些机器的性能并不算很高级,但是不要忘了,我们并不是要测试服务器的容量,我们只是要测试服务器每次处理一个页面 所用的时间。测试期间这些机器不做其它工作。WAST 测试脚本、测试报告以及所有的ASP测试页面都包含在ZIP文件中,你可以自己进行回顾和测试。

1、将ASP生成的内容写入响应流中最有效的方法是什么?

使用ASP的一个最主要原因是在服务器上生成动态内容。所以很明显,我们测试的起点是确定将动态内容发送到响应流中的最适合的方式。在多种选择中,有两个是最基本的:一是使用内联ASP标记,另一个是使用Response.Write 语句。 为测试这些选择,我们创建了一个简单的ASP页面,其中定义了一些变量,然后将它们的值插入表格中。虽然这个页面很简单也不是很实用,但它允许我们分离并测试一些单独的问题。

①使用ASP内联标记
第一个测试包括使用内联ASP标记< %= x % >,其中x是一个已赋值的变量。到目前为止,这个方法是最容易执行的,并且它使页面的HTML部分保持一种易于阅读和维护的格式。

  1. <%
  2. Option Explicit
  3. Dim FirstName, LastName, MiddleInitial, Address, City, State , PhoneNumber, FaxNumber, EMail, BirthDate
  4. FirstName = “John”
  5. %>
  6. <html>
  7. <head>
  8. <meta http-equiv=“Content-Type” content=“text/html; charset=gb2312” />
  9. <title>Response Test</title>
  10. </head>
  11. <body>
  12. <h1>Response Test</h1>
  13. <table width=“200” border=“0” cellpadding=“0” cellspacing=“0”>
  14. <tr>
  15. <td>First Name: </td>
  16. <td><%= FirstName %></td>
  17. </tr>
  18. </table>
  19. </body>
  20. </html>
<%
Option Explicit
Dim FirstName, LastName, MiddleInitial, Address, City, State , PhoneNumber, FaxNumber, EMail, BirthDate
FirstName = "John"
%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
<title>Response Test</title>
</head>

<body>
<h1>Response Test</h1>
<table width="200" border="0" cellpadding="0" cellspacing="0">
  <tr>
    <td>First Name: </td>
    <td><%= FirstName %></td>
  </tr>
 </table>
</body>
</html>

/app1/response1.asp的完整代码 (网易这个博客不可以post太多文字,所以代码有些不太紧要的有删除,不影响其思路)
以前的最佳(反应速度) = 8.28 msec/page

②在HTML的每一行使用Response.Write 语句
许多比较好的学习文档建议避免使用前面的那种方法。其主要理由是,在输出页面和处理页面施加反应时间的过程中,如果web 服务器不得不在发送纯HTML和处理脚本之间进行转换,就会发生一种被称为上下文转换的问题。大部分程序员一听到这里,他们的第一反应就是将原始的 HTML的每一行都包装在Response.Write函数中。

  1. Response.Write(“< html >”)
  2. Response.Write(“< head >”)
  3. Response.Write(” < title >Response Test< /title >”)
  4. Response.Write(“< /head >”)
  5. Response.Write(“< body >”)
  6. Response.Write(“< h1 >Response Test< /h1 >”)
  7. Response.Write(“< table >”)
  8. Response.Write(“< tr >< td >< b >First Name:< /b >< /td >< td >” & FirstName & “< /td >< /tr >”)
  9. Response.Write(“< tr >< td >< b >Middle Initial:< /b >< /td >< td >” & MiddleInitial & “< /td >< /tr >”)
...
Response.Write("< html >")
Response.Write("< head >")
Response.Write(" < title >Response Test< /title >")
Response.Write("< /head >")
Response.Write("< body >")
Response.Write("< h1 >Response Test< /h1 >")
Response.Write("< table >")
Response.Write("< tr >< td >< b >First Name:< /b >< /td >< td >" & FirstName & "< /td >< /tr >")
Response.Write("< tr >< td >< b >Middle Initial:< /b >< /td >< td >" & MiddleInitial & "< /td >< /tr >")
…

/app1/response2.asp的片段
以前的最佳(反应速度) = 8.28 msec/page
反应时间 = 8.08 msec/page
差= -0.20 msec (减少 2.4%)

我们可以看到,使用这种方法与使用内联标记的方法相比在性能上获得的收益非常小,这也许是因为页面给服务器装载了一大堆小的函数调用。这种方法最大的缺点是,由于现在HTML都嵌入脚本中,所以脚本代码变得更加冗长,更加难以阅读和维护。

③使用包装函数
当我们试图使用Response.Write 语句这种方法时,最令人灰心的发现可能就是Response.Write 函数不能在每行的结尾处放置一个CRLF 。因此,当你从浏览器中阅读源代码时,本来布置得非常好的HTML,现在成了没有结束的一行。我想,你的下一个发现可能会更令你恐怖:在Response 对象中没有其姊妹函数Writeln 。所以,一个很明显的反应就是为Response.Write 函数创建一个包装函数,以便给每一行都附加一个CRLF 。

  1. writeCR(“< tr >< td >< b >First Name:< /b >< /td >< td >” & FirstName & “< /td >< /tr >”)
  2. Sub writeCR(str)
  3. Response.Write(str & vbCRLF)
  4. End Sub
…
writeCR("< tr >< td >< b >First Name:< /b >< /td >< td >" & FirstName & "< /td >< /tr >")
…
Sub writeCR(str)
    Response.Write(str & vbCRLF)
End Sub

/app1/response4.asp的片段
以前的最佳(反应速度)= 8.08 msec/page
反应时间= 10.11 msec/page
差 = +2.03 msec (增加 25.1%)

当然,由于这种方法有效地使函数调用次数加倍,其对性能的影响也很明显,因此要不惜一切代价避免。具有讽刺意味的是CRLF也向反应流中为每行增加了2个字节,而这是浏览器不需要呈现到页面上的。格式化良好的HTML所做的一切就是让你的竞争者更容易阅读你的HTML源代码并理解你的设计。 (个人觉得在后台程序可以用这个啦,又不多人需要应用嘛)

④将连续的Response.Write 连接到一个单独语句中
不考虑我们前面用包装函数进行的测试,下一个合乎逻辑的步骤就是从单独的Response.Write 语句中提取出所有的字符串,将它们连接到一个单独语句中,这样就减少了函数调用的次数,极大地提高了页面的性能。

  1. Response.Write(“< html >”&_
  2. “< head >”&_
  3. “< title >Response Test< /title >”&_
  4. “< /head >”&_
  5. “< body >”&_
  6. “< h1 >Response Test< /h1 >”&_
  7. “< table >”&_
  8. “< tr >< td >< b >First Name:< /b >< /td >< td >” & FirstName & “< /td >< /tr >”&_
  9. “< tr >< td >< b >Birth Date:< /b >< /td >< td >” & BirthDate & “< /td >< /tr >”&_
  10. “< /table >”&_
  11. “< /body >”&_
  12. “< /html >”)
…
Response.Write("< html >"&_
"< head >"&_
"< title >Response Test< /title >"&_
"< /head >"&_
"< body >"&_
"< h1 >Response Test< /h1 >"&_
"< table >"&_
"< tr >< td >< b >First Name:< /b >< /td >< td >" & FirstName & "< /td >< /tr >"&_
…
"< tr >< td >< b >Birth Date:< /b >< /td >< td >" & BirthDate & "< /td >< /tr >"&_
"< /table >"&_
"< /body >"&_
"< /html >")

/app1/response3.asp的片段
以前的最佳(反应速度)= 8.08 msec/page
反应时间 = 7.05 msec/page
差 = -1.03 msec (减少12.7%)
目前,这是最优化的配置。

⑤将连续的Response.Write 连接到一个单独语句中,在每行结尾处增加一个CRLF
考虑到那些要求他们的源代码从浏览器中看要很纯粹的人,我用vbCRLF 常量在前面测试中每行的结尾处插入了一些回车,然后重新运行。

  1. Response.Write(“< html >” & vbCRLF & _
  2. “< head >” & vbCRLF & _
  3. ” < title >Response Test< /title >” & vbCRLF & _
  4. “< /head >” & vbCRLF & _
…
Response.Write("< html >" & vbCRLF & _
"< head >" & vbCRLF & _
" < title >Response Test< /title >" & vbCRLF & _
"< /head >" & vbCRLF & _
…

/app1/response5.asp的片段
前面的最佳(反应速度)= 7.05 msec/page
反应时间= 7.63 msec/page
差 = +0.58 msec (增加 8.5%)

运行的结果在性能上有一点降低,这也许是由于额外的串联和增加的字符量。

回顾和观测

从前面有关ASP输出的测试中可以得出一些规则:

  • 避免内联ASP的过多使用。
  • 总是将连续Response.Write 语句连接进一个单独语句内。
  • 永远不要在Response.Write 周围使用包装函数来附加CRLF。(汗,后台我很喜欢,前台肯定不能了)
  • 如果必须格式化HTML输出,直接在Response.Write 语句内附加CRLF。

2、是否应该开启缓冲器?

①通过脚本程序启动缓冲器
在ASP脚本的顶部包含Response.Buffer=True ,IIS就会将页面的内容缓存。

  1. <%
  2. Option Explicit
  3. Response.Buffer = true
  4. Dim FirstName
<%
Option Explicit
Response.Buffer = true
Dim FirstName
…

/app1/buffer__1.asp的片段
以前的最佳(反应时间)= 7.05 msec/page
反应时间 = 6.08 msec/page
差= -0.97 msec (降低13.7%)
性能得到了极大提高。但是等等,还能有更好的。

②通过服务器配置启动缓冲器
虽然在IIS 5.0中缓冲器是被默认启动的,但是在IIS 4.0中还必须手动来启动它。这时要找到站点的Properties 对话框,在那里,从Home Directory 标签中选择配置按钮。然后在”App options”下选择”enable buffering” 。对于这个测试,Response.Buffer 语句从脚本中被移走了。

以前的最佳= 7.05 msec/page
反应时间 = 5.57 msec/page
差= -1.48 msec (降低 21.0%)

目前,这是我们所得到的最快反应了,比我们以前最好情况下的反应时间还要降低21%。从现在开始,我们以后的测试都要把这个反应时间作为基准值。

回顾及观测

缓冲器是提高性能的好方法,所以把缓冲器设置成服务器的默认值很有必要。如果因为某些原因,页面不能正确地使缓冲器运行,只需要 Response.Buffer=False 命令即可。缓冲器的一个缺点是在整个页面处理完之前,用户从服务器看不到任何东西。因此,在复杂页面的处理期间,偶而调用一次 Response.Flush 来更新用户是个好主意。

现在在我们的规则中又增加了一条:
总是通过服务器设置开启缓冲器

3、是否应该考虑向ASP代码中增加注释?

大部分HTML开发人员都知道包含HTML注释不是个好主意,首先会增加传输数据的规模,其次它们只是向别的开发人员提供有关你页面组织的信息。但是ASP页面上的注释又如何呢?它们从来不离开服务器,但也确实要增加页面的规模,因此必须用ASP进行分解。
在这次的测试中,我们增加20条注释,每条有80个字符,总共有1600个字符。

  1. <%
  2. Option Explicit
  3. ‘———————-
  4. … 20 lines …
  5. ‘———————-
  6. Dim FirstName
<%
Option Explicit
'----------------------
… 20 lines …
'----------------------
Dim FirstName
…

/app2/comment_1.asp片段
基准= 5.57 msec/page
反应时间= 5.58 msec/page
差 = +0.01 msec (增加 0.1%)

测试的结果是惊人的。虽然注释几乎相当于文件本身的两倍,但是它们的存在并没有给反应时间带来很大的影响。所以说我们可以遵循以下规则:
只要使用适度,ASP注释对性能的影响很小或根本没有影响。

4、是否应该为页面明确地设置默认语言?

IIS处理VBScript是默认的设置,但是我看到,在大多数例子中还是用< %@LANGUAGE=VBSCRIPT% >声明将语言明确地设置为VBScript 。我们的下一个测试将检验这个声明的存在对性能有什么影响。

  1. <%@ LANGUAGE=VBSCRIPT%>
  2. <%
  3. Option Explicit
  4. Dim FirstName
<%@ LANGUAGE=VBSCRIPT%>
<%
Option Explicit
Dim FirstName
…

/app2/language1.asp片段。
基准值= 5.57 msec/page
反应时间= 5.64 msec/page
差= +0.07 msec (增加1.2%)

可以看到,包含了语言的声明对性能有一个轻微的影响。因此:

  • 设置服务器的默认语言配置以与站点上使用的语言相匹配。
  • 除非你使用非默认语言,不要设置语言声明。

5、如果不需要,是否应该关闭Session 状态?

避免使用IIS的Session上下文有许多理由,那些已经可以独立成为一篇文章。我们现在试图回答的问题是当页面不需要时,关闭Session上下文是否对性能提高有所帮助。从理论上讲应该是肯定的,因为这样一来就不需要用页面例示Session上下文了。
同缓冲器一样,Session状态也有两种配置方法:通过脚本和通过服务器设置。

①通过脚本关闭Session上下文
对于这个测试,要关闭页面中的Session上下文,我增加一个Session状态声明。

  1. <% @@ ENABLESESSIONSTATE = FALSE %>
  2. <%
  3. Option Explicit
  4. Dim FirstName
<% @@ ENABLESESSIONSTATE = FALSE %>
<%
Option Explicit
Dim FirstName
...

/app2/session_1.asp片段。
基准值= 5.57 msec/page
反应时间= 5.46 msec/page
差= -0.11 msec (降低2.0%)

只通过这样一个小小的努力就得到了不错的进步。现在看看第二部分。

②通过服务器配置关闭Session 上下文
要在服务器上关闭Session 上下文,请到站点的Properties 对话框。在Home Directory 标签上选择Configuration 按钮。然后在”App options”下取消”enable session state” 的选择。我们在没有ENABLESESSIONSTATE 声明的情况下运行测试。

基准值 = 5.57 msec/page
反应时间= 5.14 msec/page
差= -0.43 msec (降低7.7%)

这是性能的又一个显著提高。所以,我们的规则应是:

  • 在不需要的情况下,总是在页面或应用程序的水平上关闭Session状态。

6、使用Option Explicit 会使性能有实质改变吗?

在一个ASP页面的顶部设置Option Explicit 以要求所有的变量在使用之前都要在页面上进行声明。这有两个原因。首先应用程序可以更快地处理变量的存取。其次,这样可以防止我们无意中错用变量的名字。 在这个测试中我们移走Option Explicit 引用和变量的Dim 声明。

基准值 = 5.57 msec/page
反应时间= 6.12 msec/page
差 = +0.55 msec (9.8% 增加)

尽管有一些代码行从页面中去掉了,反应时间却依然增加了。所以尽管使用Option explicit 有时候费时间,但是在性能上却有很显著的效果。因此我们又可以增加一条规则:

  • 在VBScript中总是使用Option explicit

7、是否应该把脚本逻辑放在子程序和函数区?

用函数和子程序来组织和管理代码是一个很好的方法,特别是当一个代码区在页面中多次使用的情况。缺点是要在系统上增加一个做相同工作的额外函数调用。子 程序和函数的另一个问题是变量的范围。从理论上说,在一个函数区内指定变量更有效。现在我们看看这两个方面如何发生作用。

①将Response.Write语句移入子程序
这个测试只是将Response.Write 语句移入一个子程序区内。

  1. CALL writeTable()
  2. SUB writeTable()
  3. Response.Write(“< html >”&_
  4. “< head >”&_
  5. “< tr >< td >< b >EMail:< /b >< /td >< td >” & EMail & “< /td >< /tr >”&_
  6. “< tr >< td >< b >Birth Date:< /b >< /td >< td >” & BirthDate & “< /td >< /tr >”&_
  7. “< /table >”&_
  8. “< /body >”&_
  9. “< /html >”)
  10. END SUB
…
CALL writeTable()
SUB writeTable()
    Response.Write("< html >"&_
    "< head >"&_
    …
    "< tr >< td >< b >EMail:< /b >< /td >< td >" & EMail & "< /td >< /tr >"&_
    "< tr >< td >< b >Birth Date:< /b >< /td >< td >" & BirthDate & "< /td >< /tr >"&_
    "< /table >"&_
    "< /body >"&_
    "< /html >")
END SUB

/app2/function1.asp片段
基准值= 5.57 msec/page
反应时间= 6.02 msec/page
差 = +0.45 msec (8.1% 增加)

同预料中一样,子程序调用给页面带来了额外的负担。

②将所有脚本移入子程序中
在这个测试中,Response.write 语句与变量声明都移入一个子程序区中。

  1. <%
  2. OPTION EXPLICIT
  3. CALL writeTable()
  4. SUB writeTable()
  5. Dim FirstName
  6. Dim BirthDate
  7. FirstName = “John”
  8. BirthDate = “1/1/1950”
  9. Response.Write(“< html >”&_
  10. “< head >”&_
  11. ” < title >Response Test< /title >”&_
  12. “< /head >”&_
  13. “< body >”&_
  14. “< h1 >Response Test< /h1 >”&_
  15. “< /table >”&_
  16. “< /body >”&_
  17. “< /html >”)
  18. END SUB
<%
OPTION EXPLICIT
CALL writeTable()
SUB writeTable()
    Dim FirstName
    …
    Dim BirthDate
    FirstName = "John"
    …
    BirthDate = "1/1/1950"
    Response.Write("< html >"&_
    "< head >"&_
    " < title >Response Test< /title >"&_
    "< /head >"&_
    "< body >"&_
    "< h1 >Response Test< /h1 >"&_
    …
    "< /table >"&_
    "< /body >"&_
    "< /html >")
END SUB

/app2/function2.asp片段
基准值= 5.57 msec/page
反应时间= 5.22 msec/page
差 = -0.35 msec (6.3% 降低)

非常有趣!尽管将变量移到函数范围内增加了额外的函数调用,但实际上却提高了性能。我们又可以增加以下规则:

  • 在一个页面上,如果代码要使用一次以上,就将代码封入函数区。
  • 适当时候,将变量声明移到函数范围内。

7、使用包含文件有什么影响?
ASP编程的一个重要功能就是包含来自其它页面的代码。
通过这项功能,程序员可以在多个页面上共享函数,使代码更易于维护。缺点在于服务器必须从多个来源组装页面。以下是使用Include文件的两个测试。

①使用内联代码的Include 文件
在这个测试中,有一小段代码被移到一个Include 文件中:

  1. <%
  2. Option Explicit
  3. Dim FirstName
  4. Dim BirthDate
  5. FirstName = “John”
  6. BirthDate = “1/1/1950”
  7. %>
  8. <!–#include file=“inc1.asp”–>
<%
Option Explicit
Dim FirstName
…
Dim BirthDate
FirstName = "John"
…
BirthDate = "1/1/1950"
%>
<!--#include file="inc1.asp"-->

/app2/include_1.asp片段
基准值 = 5.57 msec/page
反应时间= 5.93 msec/page
差 = +0.36 msec (6.5% 增加)
这不奇怪。使用Include 文件形成了负载。

②在函数区使用Include 文件
在这里,代码都包装在一个Include 文件中的子程序里。Include 引用是在页面顶部进行的,在ASP脚本的适当位置调用子程序。

  1. <%
  2. Option Explicit
  3. Dim FirstName
  4. Dim BirthDate
  5. FirstName = “John”
  6. BirthDate = “1/1/1950”
  7. CALL writeTable()
  8. %>
  9. <!–#include file=“inc2.asp”–>
<%
Option Explicit
Dim FirstName
…
Dim BirthDate
FirstName = "John"
…
BirthDate = "1/1/1950"
CALL writeTable()
%>
<!--#include file="inc2.asp"-->

/app2/include_2.asp片段
基准值 = 5.57 msec/page
反应时间= 6.08 msec/page
差 =+0.51 msec (9.2% 增加)

这对性能造成的影响比functions调用还大。因此:

  • 只有当代码在页面之间共享时才使用Include 文件。

③执行错误处理时会形成多大的负载?
对于所有真正的应用程序来说,错误处理都是必要的。这个测试中,通过调用On Error Resume Next函数来调用错误句柄。

  1. <%
  2. Option Explicit
  3. On Error Resume Next
  4. Dim Firstname
<%
Option Explicit
On Error Resume Next
Dim Firstname
...

/app2/error_1.asp片段
基准值 = 5.57 msec/page
反应时间= 5.67 msec/page
差= 0.10 msec (1.8% 增�
你可以看到,错误句柄带来了代价。我们可以提出以下建议:

  • 只有在会发生超出测试或控制能力之外的情况时才使用错误句柄。

一个最基本的例子就是使用存取其它资源,如ADO或FileSystem 对象的COM对象。

④设置一个上下文处理是否对性能有影响?
当错误发生时,在页面上设置一个上下文处理允许脚本进行反转操作。这是通过在页面上使用处理声明来设置的。

  1. <%@Transaction = Required %>
  2. <%
  3. Option Explicit
  4. Dim FirstName
<%@Transaction = Required %>
<%
Option Explicit
Dim FirstName
…

/app2/transact1.asp片段
基准值 = 5.57 msec/page
反应时间= 13.39 msec/page
差 = +7.82 msec (140.4% 增加)

啊!这真实最具有戏剧性的结果。所以请留意以下规则:

  • 只有当两个或更多操作被作为一个单元执行时,才使用处理上下文。

结论

本文第一部分的重要之处在于许多小事情的累积。为了强调这个问题,我设置了最后一个测试,在其中进行了我们以前曾经测试过的看来无所谓但实际上有坏影响 的所有操作。我包含了许多Response.Write 声明、关闭了缓冲器、设置了默认语言、去掉了Option Explicit 引用并初始化了错误句柄。

  1. <%@ LANGUAGE=VBSCRIPT %>
  2. <%
  3. On Error Resume Next
  4. FirstName = “John”
  5. BirthDate = “1/1/1950”
  6. Response.Write(“<html >”)
  7. Response.Write(“<head >”)
  8. Response.Write(“<title >Response Test</title >”)
  9. Response.Write(“</head >”)
  10. Response.Write(“<body >”)
  11. Response.Write(“<h1>Response Test</h1>”)
  12. Response.Write(“<table>”)
  13. Response.Write(“<tr><td><b>First Name:</b></td><td>”& FirstName &“</td></tr>”)
  14. Response.Write(“<tr><td><b>Birth Date:</b></td><td>”& BirthDate &“</td></tr>”)
  15. Response.Write(“</table>”)
  16. Response.Write(“</body >”)
  17. Response.Write(“</html>”)
  18. %>
<%@ LANGUAGE=VBSCRIPT %>
<%
On Error Resume Next
FirstName = "John"
…
BirthDate = "1/1/1950"
Response.Write("<html >")
Response.Write("<head >")
Response.Write("<title >Response Test</title >")
Response.Write("</head >")
Response.Write("<body >")
Response.Write("<h1>Response Test</h1>")
Response.Write("<table>")
Response.Write("<tr><td><b>First Name:</b></td><td>"& FirstName &"</td></tr>")
…
Response.Write("<tr><td><b>Birth Date:</b></td><td>"& BirthDate &"</td></tr>")
Response.Write("</table>")
Response.Write("</body >")
Response.Write("</html>")
%>

/app2/final_1.asp片段
基准值 = 5.57 msec/page
反应时间 = 8.85 msec/page
差 = +3.28 msec (58.9% 增加)

听起来可能很明显,但是理解更重要,那就是我们放置在页面上的代码会对性能有影响。页面上的小变化有时会大大地增加反应时间。

规则概括

  • 避免内联ASP的过多使用。
  • 总是将连续Response.Write 语句连接进一个单独语句内。
  • 永远不要在Response.Write 周围使用包装函数以附加CRLF。
  • 如果必须格式化HTML输出,直接在Response.Write 语句内附加CRLF。
  • 总是通过服务器设置开启缓冲器。
  • 只要使用适度,ASP注释对性能的影响很小或根本没有影响。
  • 设置服务器的默认语言配置以与站点上使用的语言相匹配。
  • 除非你使用非默认语言,不要设置语言声明。
  • 在VBScript中总是使用Option explicit 。
  • 在不需要的情况下,总是在页面或应用程序的水平上关闭Session状态。
  • 只有当代码在页面之间共享时才使用Include 文件。
  • 在一个页面上,如果代码要使用一次以上,就将代码封入函数区。
  • 适当时候,将变量声明移到函数范围内。
  • 只有会发生超出测试或控制能力之外的情况时才使用错误句柄。
  • 只有当两个或更多操作被作为一个单元执行时,才使用上下文处理。

现在回顾一下,有许多问题可以作为普遍性的方针:

  • 避免冗余–不要设置那些默认状态下已经设置的属性。
  • 限制函数调用的次数。
  • 缩小代码的范围。

二、在本节中,我们将探索有关ADO和COM对象一些深入的问题。

在本文的第一部分中,我回顾了有关ASP开发的一些基本问题,介绍了一些性能测试的结果,以理解我们放置在页面中的代码可能对运行性能造成什么样的影 响。在这个系列的第二部分,我们将探讨经过论证的ASP最广泛的用途,即通过ActiveX数据对象(ADO)交互使用数据库内容。ADO是 Microsoft通用并简单的数据库界面。

ADO有很多的功能设置,因此准备这篇文章时最大的挑战便是限制测试问题的范围。考虑到 读取大数据集会为web 服务器施加很大的负载,我决定将研究的内容局限在为使用ADO记录集寻找最优化配置的方面。但是这个限制还是提出了一个挑战,因为ADO为执行同一个功能 提供了多种方式。比如说,记录集可以从Recordset 类中恢复,也可以从Connection和Command 类中恢复。另外,一旦你有了一个记录集,那么有很多个选择会戏剧性地影响性能。因此,同第一部分一样,我将尽可能地多涉及一些具体问题。

目的

我研究的目的是获取足够的信息以找到以下问题的答案:

  • 是否应该使用ADOVBS.inc包含文件?
  • 当使用一个记录集时,是否应该创建一个单独的Connection对象?
  • 恢复一个记录集最好的方法是什么?
  • 指针和锁的类型中,哪些是最有效的?
  • 是否应该使用断开的记录集?
  • 设置记录集(Recordset)属性的最好方法是什么?
  • 引用记录集中域值的最有效方法是什么?
  • 使用临时字符串可以较好地代替缓冲器吗?

测试是如何设立的?

为进行这项研究中的测试,我们共组装了21个ASP页面(包含在本文下载内容中)。每个页面都被配置成用3个不同的查询返回记录集运行,这些记录集中分别有0、25、250条记录。这可以帮助我们将装载记录集的问题和在记录集中循环上的性能问题隔离开。

为满足这些变化的条件,数据库连接字符串和测试SQL字符串都作为应用程序变量存储在Global.asa中。因为我们的测试数据库是在 Microsoft SQL Server 7.0上运行的,因此我们的连接字符串指定OLEDB作为连接供应者、Northwind 样本数据库(包含在SQL服务器中)作为当前数据库。SQL Select语句要求Northwind orders 表格中的7个特定域。

  1. <SCRIPT LANGUAGE=VBScript RUNAT=Server>
  2. Sub <A href=“http://www.zdxz.cn/default.asp?tag=Application” target=_blank>Application</A>_OnStart
  3. <A href=“http://www.zdxz.cn/default.asp?tag=Application” target=_blank>Application</A>(“Conn”) = “Provider=SQLOLEDB; “&_
  4. “Server=MyServer; “&_
  5. “uid=sa; “&_
  6. “pwd=;”&_
  7. “DATABASE=northwind”
  8. <A href=“http://www.zdxz.cn/default.asp?tag=Application” target=_blank>Application</A>(“SQL”) = “Select TOP 0 orderID, “&_
  9. ” CustomerID, “&_
  10. ” EmployeeID, “&_
  11. ” orderDate, “&_
  12. ” RequiredDate, “&_
  13. ” ShippedDate, “&_
  14. ” Freight “&_
  15. “FROM [Orders]”
  16. End Sub
  17. </SCRIPT>
  18. ‘alternate sql ?25 records
  19. <A href=“http://www.zdxz.cn/default.asp?tag=Application” target=_blank>Application</A>(“SQL”) = “Select TOP 25 orderID, “&_
  20. ” CustomerID, “&_
  21. ” EmployeeID, “&_
  22. ” orderDate, “&_
  23. ” RequiredDate, “&_
  24. ” ShippedDate, “&_
  25. ” Freight “&_
  26. “FROM [Orders] “
  27. ‘alternate sql ?250 records
  28. <A href=“http://www.zdxz.cn/default.asp?tag=Application” target=_blank>Application</A>(“SQL”) = “Select TOP 250 orderID, “&_
  29. ” CustomerID, “&_
  30. ” EmployeeID, “&_
  31. ” orderDate, “&_
  32. ” RequiredDate, “&_
  33. ” ShippedDate, “&_
  34. ” Freight “&_
  35. “FROM [Orders]”
<SCRIPT LANGUAGE=VBScript RUNAT=Server>
Sub Application_OnStart
Application("Conn") = "Provider=SQLOLEDB; "&_
"Server=MyServer; "&_
"uid=sa; "&_
"pwd=;"&_
"DATABASE=northwind"
Application("SQL") = "Select TOP 0 orderID, "&_
" CustomerID, "&_
" EmployeeID, "&_
" orderDate, "&_
" RequiredDate, "&_
" ShippedDate, "&_
" Freight "&_
"FROM [Orders]"
End Sub
</SCRIPT> 

'alternate sql ?25 records
Application("SQL") = "Select TOP 25 orderID, "&_
" CustomerID, "&_
" EmployeeID, "&_
" orderDate, "&_
" RequiredDate, "&_
" ShippedDate, "&_
" Freight "&_
"FROM [Orders] "
'alternate sql ?250 records
Application("SQL") = "Select TOP 250 orderID, "&_
" CustomerID, "&_
" EmployeeID, "&_
" orderDate, "&_
" RequiredDate, "&_
" ShippedDate, "&_
" Freight "&_
"FROM [Orders]"

我们的测试服务器是一个双450 MHz Pentium ,512MB的RAM,在其上运行着NT Server 4.0 SP5, MDAC 2.1 (数据访问组件)以及Microsoft Scripting Engine的5.0版本。SQL服务器在一个同样规格的单独机器上运行。同第一篇文章一样,我使用Microsoft的Web应用程序重点工具记录从最 初的页面请求到传输最后一个字节(TTLB )的时间,精确到服务器上的毫秒级。这个测试脚本运行20小时,调用每个页面1300次以上。显示的时间是session的平均TTLB。要记住的是,同 第一篇文章一样,我们只是试图涉及性能方面的问题,而非伸缩性和容量的问题。

还请注意,我们在服务器上开启了缓冲器。另外,我把所有的文件名都定为同样长度,因此文件名中就会有一个或多个下划线来衬垫。

开始

在第一个测试中,我们使用典型Microsoft ASP ADO 样本文件中的典型场景来恢复一个简单的记录集。在这个例子( ADO__01.asp )中,我们首先创建一个Connection对象,然后创建一个Recordset对象。当然,我在脚本中进行了一些修改,以反映在本系列的第一部分中涉 及到的一些好的做法。

  1. <%Option Explicit%>
  2. <!– #Include file=“ADOVBS.INC”–>
  3. <%
  4. Dim objConn
  5. Dim objRS
  6. Response.Write(“<HTML><HEAD>”&_
  7. “<TITLE>ADO Test</TITLE>”&_
  8. “</HEAD><BODY>”
  9. )
  10. Set objConn = Server.CreateObject(“ADODB.Connection”)
  11. objConn.Open <A href=“http://www.zdxz.cn/default.asp?tag=Application” target=_blank>Application</A>(“Conn”)
  12. Set objRS = Server.CreateObject(“ADODB.Recordset”)
  13. objRS.ActiveConnection = objConn
  14. objRS.CursorType = adOpenForwardOnly
  15. objRS.LockType = adLockReadOnly
  16. objRS.Open <A href=“http://www.zdxz.cn/default.asp?tag=Application” target=_blank>Application</A>(“SQL”)
  17. If objRS.EOF Then
  18. Response.Write(“No Records Found”)
  19. Else
  20. ‘write headings
  21. Response.Write(“<TABLE BORDER=1>”&_
  22. “<TR>”&_
  23. “<TH>OrderID</TH>”&_
  24. “<TH>CustomerID</TH>”&_
  25. “<TH>EmployeeID</TH>”&_
  26. “<TH>OrderDate</TH>”&_
  27. “<TH>RequiredDate</TH>”&_
  28. “<TH>ShippedDate</TH>”&_
  29. “<TH>Freight< /TH>”&_
  30. “</TR>”)
  31. ‘write data
  32. Do While Not objRS.EOF
  33. Response.Write(“< TR >”&_
  34. “< TD >” & objRS(“OrderID”) & “< /TD >”&_
  35. “< TD >” & objRS(“CustomerID”) & “< /TD >”&_
  36. “< TD >” & objRS(“EmployeeID”) & “< /TD >”&_
  37. “< TD >” & objRS(“OrderDate”) & “< /TD >”&_
  38. “< TD >” & objRS(“RequiredDate”) & “< /TD >”&_
  39. “< TD >” & objRS(“ShippedDate”) & “< /TD >”&_
  40. “< TD >” & objRS(“Freight”) & “< /TD >”&_
  41. “< /TR > “)
  42. objRS.MoveNext
  43. Loop
  44. Response.Write(“< /TABLE >”)
  45. End If
  46. objRS.Close
  47. objConn.Close
  48. Set objRS = Nothing
  49. Set objConn = Nothing
  50. Response.Write(“< /BODY >< /HTML >”)
  51. %>
<%Option Explicit%>
<!-- #Include file="ADOVBS.INC"-->
<%
Dim objConn
Dim objRS
Response.Write("<HTML><HEAD>"&_
"<TITLE>ADO Test</TITLE>"&_
"</HEAD><BODY>"
)
Set objConn = Server.CreateObject("ADODB.Connection")
objConn.Open Application("Conn")
Set objRS = Server.CreateObject("ADODB.Recordset")
objRS.ActiveConnection = objConn
objRS.CursorType = adOpenForwardOnly
objRS.LockType = adLockReadOnly
objRS.Open Application("SQL")
If objRS.EOF Then
Response.Write("No Records Found")
Else
'write headings
Response.Write("<TABLE BORDER=1>"&_
"<TR>"&_
"<TH>OrderID</TH>"&_
"<TH>CustomerID</TH>"&_
"<TH>EmployeeID</TH>"&_
"<TH>OrderDate</TH>"&_
"<TH>RequiredDate</TH>"&_
"<TH>ShippedDate</TH>"&_
"<TH>Freight< /TH>"&_
"</TR>") 

'write data
Do While Not objRS.EOF
Response.Write("< TR >"&_
"< TD >" & objRS("OrderID") & "< /TD >"&_
"< TD >" & objRS("CustomerID") & "< /TD >"&_
"< TD >" & objRS("EmployeeID") & "< /TD >"&_
"< TD >" & objRS("OrderDate") & "< /TD >"&_
"< TD >" & objRS("RequiredDate") & "< /TD >"&_
"< TD >" & objRS("ShippedDate") & "< /TD >"&_
"< TD >" & objRS("Freight") & "< /TD >"&_
"< /TR > ") 

objRS.MoveNext
Loop
Response.Write("< /TABLE >")
End If
objRS.Close
objConn.Close
Set objRS = Nothing
Set objConn = Nothing
Response.Write("< /BODY >< /HTML >")
%>

结果是这样的:

现在先来看看每一栏中的数字代表什么:
0 代表运行返回0个记录的查询时的TTLB,单位毫秒。在我们所有测试中,这个数字用来标志页面的负载或装载页面创建对象但不在数据中循环所用的时间。

25 装载并显示25条记录的TTLB(毫秒)。

tot time/25 TTLB除以25条记录(毫秒)。代表每条记录的总平均时间。

disp time/25 以毫秒计的TTLB减去“0”那栏的TTLB,并除以25条记录。代表在记录集中循环显示每条记录的时间。

250 装载并显示250条记录的TTLB(毫秒)。

tot time/250 TTLB除以250条记录(毫秒)。代表每条记录的总平均时间。

disp time/250 以毫秒计的TTLB减去“0”那栏的TTLB,并除以250条记录。代表在记录集中循环显示每条记录的时间。

我们将用下面测试的结果与这些值相比较。

是否应该使用ADOVBS.inc 包含文件?

这个问题我想快点解决。Microsoft 提供的ADOVBS.inc 文件包含270行代码,代表可以应用于ADO属性的大部分常量。我们的例子中只引用了这个文件中的2个常量。因此对于这个测试( ADO__02.asp ),我取消了包含文件的引用,并用属性列举中的实际数字代替了常量。

  1. objRS.CursorType = 0 ‘ adOpenForwardOnly
  2. objRS.LockType = 1 ‘ adLockReadOnly
objRS.CursorType = 0 ' adOpenForwardOnly
objRS.LockType = 1 ' adLockReadOnly

我们可以看到装载时间减少了23%。这与每条记录的显示时间有定义上的不同,因为这种改变对于在记录集中循环不应该有影 响。这个问题有几种解决办法。我建议使用ADOVBS.inc 文件作为参考,必要时使用注释来注明数字。要记住,就如同在第一部分所阐明的一样,注释是不需要惧怕的,因为只要使用适度,它们不会给性能带来大的影响。 另一种方法是只从文件中将你所需要的常量复制到页面中。

解决这个问题有一个很酷的方法,通过将ADO类库连接到你的应用程序,使所有的ADO常量都可用。将以下代码增加到你的Global.asa 文件,你就可以直接使用所有的常量。

  1. <!–METADATA TYPE=“typelib” FILE=“C:\Program Files\Common Files\SYSTEM\ADO\msado15.dll” NAME=“ADODB Type Library” –>
  2. <!–METADATA TYPE=“typelib” UUID=“00000205-0000-0010-8000-00AA006D2EA4” NAME=“ADODB Type Library” –>
<!--METADATA TYPE="typelib" FILE="C:\Program Files\Common Files\SYSTEM\ADO\msado15.dll" NAME="ADODB Type Library" -->
或
<!--METADATA TYPE="typelib" UUID="00000205-0000-0010-8000-00AA006D2EA4" NAME="ADODB Type Library" -->

所以,这里是我们的第一个规则:

  • 避免包含ADOVBS.inc文件,用其它方法来使用常量。

当使用一个记录集时,是否应该创建一个单独的Connection对象?

要想正确回答这个问题,需要在两个不同情况下检验测试结果:第一是每页执行一个数据库处理的情况,第二是每页执行多个数据库处理的情况。

在前面的例子中,我们已经创建了一个单独的Connection对象,并将它传递到记录集的ActiveConnection属性。但是也有可能仅仅把 连接字符串传递到这个属性中,从而可以避免一个额外的步骤,即在脚本(ADO__03.asp )中例示和配置一个单独的组件:

  1. objRS.ActiveConnection = <A href=“http://www.zdxz.cn/default.asp?tag=Application” target=_blank>Application</A>(“Conn”)
objRS.ActiveConnection = Application("Conn")

尽管我们仍然在记录集中创建了一个连接,但它是在非常优化的情况下创建的,所以刚一开始我们就看到启动时间比以前的测试减少了23%,同预料中一样,同每个记录的显示时间几乎没有什么差别。

因此,我们的第二个规则是:

  • 当使用一个单个记录集时,将连接字符串传递到ActiveConnection属性中。

下面要确定当在一个页面上创建多个记录集时,这个逻辑是否依然成立。为测试这个情况,我引入了FOR 循环,将前面的例子重复10次。在这个测试中,我们还将研究3种选择:

第一,我们在每个循环中创建并销毁Connection 对象( ADO__04.asp ):

  1. Dim i
  2. For i = 1 to 10
  3. Set objConn = Server.CreateObject(“ADODB.Connection”)
  4. objConn.Open <A href=“http://www.zdxz.cn/default.asp?tag=Application” target=_blank>Application</A>(“Conn”)
  5. Set objRS = Server.CreateObject(“ADODB.Recordset”)
  6. objRS.ActiveConnection = objConn
  7. objRS.CursorType = 0 ‘adOpenForwardOnly
  8. objRS.LockType = 1 ‘adLockReadOnly
  9. objRS.Open <A href=“http://www.zdxz.cn/default.asp?tag=Application” target=_blank>Application</A>(“SQL”)
  10. If objRS.EOF Then
  11. Response.Write(“No Records Found”)
  12. Else
  13. ‘write headings
  14. ‘write data
  15. End If
  16. objRS.Close
  17. Set objRS = Nothing
  18. objConn.Close
  19. Set objConn = Nothing
  20. Next
Dim i
For i = 1 to 10
Set objConn = Server.CreateObject("ADODB.Connection")
objConn.Open Application("Conn")
Set objRS = Server.CreateObject("ADODB.Recordset")
objRS.ActiveConnection = objConn
objRS.CursorType = 0 'adOpenForwardOnly
objRS.LockType = 1 'adLockReadOnly
objRS.Open Application("SQL")
If objRS.EOF Then
Response.Write("No Records Found")
Else
'write headings
...
'write data
...
End If
objRS.Close
Set objRS = Nothing
objConn.Close
Set objConn = Nothing
Next

第二,在循环外创建一个单独的Connection 对象,并与每个记录集共享它( ADO__05.asp ):

  1. Set objConn = Server.CreateObject(“ADODB.Connection”)
  2. objConn.Open <A href=“http://www.zdxz.cn/default.asp?tag=Application” target=_blank>Application</A>(“Conn”)
  3. Dim i
  4. For i = 1 to 10
  5. Set objRS = Server.CreateObject(“ADODB.Recordset”)
  6. objRS.ActiveConnection = objConn
  7. objRS.CursorType = 0 ‘adOpenForwardOnly
  8. objRS.LockType = 1 ‘adLockReadOnly
  9. objRS.Open <A href=“http://www.zdxz.cn/default.asp?tag=Application” target=_blank>Application</A>(“SQL”)
  10. If objRS.EOF Then
  11. Response.Write(“No Records Found”)
  12. Else
  13. ‘write headings
  14. ‘write data
  15. End If
  16. objRS.Close
  17. Set objRS = Nothing
  18. Next
  19. objConn.Close
  20. Set objConn = Nothing
Set objConn = Server.CreateObject("ADODB.Connection")
objConn.Open Application("Conn")
Dim i
For i = 1 to 10
Set objRS = Server.CreateObject("ADODB.Recordset")
objRS.ActiveConnection = objConn
objRS.CursorType = 0 'adOpenForwardOnly
objRS.LockType = 1 'adLockReadOnly
objRS.Open Application("SQL")
If objRS.EOF Then
Response.Write("No Records Found")
Else
'write headings
...
'write data
...
End If
objRS.Close
Set objRS = Nothing
Next
objConn.Close
Set objConn = Nothing

第三,在每个循环中将连接字符串传递到ActiveConnection 属性( ADO__06.asp ):

  1. Dim i
  2. For i = 1 to 10
  3. Set objRS = Server.CreateObject(“ADODB.Recordset”)
  4. objRS.ActiveConnection = <A href=“http://www.zdxz.cn/default.asp?tag=Application” target=_blank>Application</A>(“Conn”)
  5. objRS.CursorType = 0 ‘adOpenForwardOnly
  6. objRS.LockType = 1 ‘adLockReadOnly
  7. objRS.Open <A href=“http://www.zdxz.cn/default.asp?tag=Application” target=_blank>Application</A>(“SQL”)
  8. If objRS.EOF Then
  9. Response.Write(“No Records Found”)
  10. Else
  11. ‘write headings
  12. ‘write data
  13. End If
  14. objRS.Close
  15. Set objRS = Nothing
  16. Next
Dim i
For i = 1 to 10
Set objRS = Server.CreateObject("ADODB.Recordset")
objRS.ActiveConnection = Application("Conn")
objRS.CursorType = 0 'adOpenForwardOnly
objRS.LockType = 1 'adLockReadOnly
objRS.Open Application("SQL")
If objRS.EOF Then
Response.Write("No Records Found")
Else
'write headings
...
'write data
...
End If
objRS.Close
Set objRS = Nothing
Next

你可能已经猜到了,在每个循环中创建并销毁Connection 对象是一个低效率的方法。但是令人吃惊的是,仅仅在每个循环中传递连接字符串比共享单一连接对象的效率只低一点点。

尽管如此,我们的第3条规则是:

  • 在一个页面上使用多个记录集时,创建一个Connection 对象,在ActiveConnection 属性中重复使用它。

指针和锁的类型中,哪些是最有效的?

到目前为止,我们所有测试都只用了只向前(Forward On

ly) 的指针在记录集中循环。但是,ADO还为记录集提供了3种类型的指针:Static(静态), Dynamic(动态)和 Keyset(键盘)。每一种都提供了额外的功能,比如向前和向后移动以及当别人建立数据时可以看到修改情况的功能。不过,讨论这些指针类型的内涵不是本 文讨论的范围。我把这些留给你自己。下面是各种类型的比较分析。

与它们的同类Forward Only 相比,这些额外的指针都明显地造成了更大的负载( ADO__03.asp )。另外这些指针在循环期间也更慢。我想与你一起分享的一条忠告是要避免这种想法:“我不时地需要一下Dynamic 指针,所以干脆总是用它算了。”

从本质上说,同样的问题也适用于锁的类型。前面的测试中只使用了Read Only(只 读)类型的锁。但是,还有三种类型的锁:Lock Pessimistic、 Lock Optimistic和Lock Batch Optimistic。同指针的选择一样,这些锁也为处理记录集中的数据提供了额外的功能和控制。同样,我将学习每种锁设置的适当用途的内容留给你自己。

所以引导我们考虑规则4的逻辑很简单:

  • 使用最适合你的任务的最简单的指针和锁的类型。

获取一个记录集最好的方式是什么?

到目前为止,我们只是通过Recordset 对象来恢复记录集。但是ADO还提供了一些获取记录集的间接方法。下一个测试就将ADO__03.asp 中的值与直接从一个Connection对象中创建一个记录集对象( CONN_01.asp )来比较。

  1. Set objConn = Server.CreateObject(“ADODB.Connection”)
  2. objConn.Open <A href=“http://www.zdxz.cn/default.asp?tag=Application” target=_blank>Application</A>(“Conn”)
  3. Set objRS = objConn.Execute(<A href=“http://www.zdxz.cn/default.asp?tag=Application” target=_blank>Application</A>(“SQL”))
Set objConn = Server.CreateObject("ADODB.Connection")
objConn.Open Application("Conn")
Set objRS = objConn.Execute(Application("SQL"))

我们看到,负载有一个轻微的增加,显示每条记录的时间没有变化。
然后,我们看看从一个Command 对象中直接创建一个Recordset 对象( CMD__01.asp ):

  1. Set objCmd = Server.CreateObject(“ADODB.Command”)
  2. objCmd.ActiveConnection = <A href=“http://www.zdxz.cn/default.asp?tag=Application” target=_blank>Application</A>(“Conn”)
  3. objCmd.CommandText = <A href=“http://www.zdxz.cn/default.asp?tag=Application” target=_blank>Application</A>(“SQL”)
  4. Set objRS = objCmd.Execute
Set objCmd = Server.CreateObject("ADODB.Command")
objCmd.ActiveConnection = Application("Conn")
objCmd.CommandText = Application("SQL")
Set objRS = objCmd.Execute

我们再次看到负载有一个轻微的增加,每个记录的显示时间有一个名义上的区别。虽然最后这两种方法对性能的影响很小,却有一个大问题需要考虑。

通过Recordset类创建一个记录集对于控制如何处理记录集提供了最大的灵活性。虽然其它方法也没有提出一个压倒性的性能问题,但是你会被默认状态下返回何种指针类型和锁类型而困惑,这些对于你的特定需求来说不一定是最优的。

所以,除非因为某种特殊原因你需要其它方法的话,请遵循第5条规则:

  • 通过ADODB.Recordset 类例示记录集以获得最好的性能和最大的灵活性。

是否应该断开记录集?

ADO为断开一个记录集提供了一种选择,记录集要在一个向前查询中恢复所有数据、关闭连接、使用一个本地(或客户)指针在数据集中移动。这还提供了一个 早期释放连接的机会。这种情况对于处理远程数据服务是必要的,因为这种情况下数据必须从数据库断开。但是对于普通的用途,这样做有好处吗?
下面我们增加了CursorLocation 属性,打开记录集后关闭连接( CLIENT1.asp ):

  1. Set objRS = Server.CreateObject(“ADODB.Recordset”)
  2. objRS.CursorLocation = 3 ‘ adUseClient
  3. objRS.ActiveConnection = <A href=“http://www.zdxz.cn/default.asp?tag=Application” target=_blank>Application</A>(“Conn”)
  4. objRS.LockType = 1 ‘ adLockReadOnly
  5. objRS.Open <A href=“http://www.zdxz.cn/default.asp?tag=Application” target=_blank>Application</A>(“SQL”)
  6. objRS.ActiveConnection = Nothing
Set objRS = Server.CreateObject("ADODB.Recordset")
objRS.CursorLocation = 3 ' adUseClient
objRS.ActiveConnection = Application("Conn")
objRS.LockType = 1 ' adLockReadOnly
objRS.Open Application("SQL")
objRS.ActiveConnection = Nothing

从理论上说,这个技术应该导致性能更快。原因有两个:首先,在记录集中移动时,避免了通过连接的重复请求;其次通过较早 地取消连接减轻了资源需求。但是,在使用客户端指针时,效率低得很明显。可能是由于当使用客户指针位置时,不管你的设置是什么,CursorType 都被修改成Static。

规则6是这样的:

  • 除非是一个断开的环境中所要求的,避免使用断开的记录集。

什么是设置记录集属性的最好方法?

前面所有的测试都是通过单独的属性设置来直接设置记录集的属性的。但是Recordset.Open 函数可以为我们所需要的全部属性接收额外的参数。虽然对于每个属性来说,单独的代码行易于阅读和维护,它们还是要分别执行一个单独函数调用,必须通过 COM界面来集合( ADO__07.asp ):

  1. Set objRS = Server.CreateObject(“ADODB.Recordset”)
  2. objRS.Open <A href=“http://www.zdxz.cn/default.asp?tag=Application” target=_blank>Application</A>(“SQL”), <A href=“http://www.zdxz.cn/default.asp?tag=Application” target=_blank>Application</A>(“Conn”), 0, 1
  3. ‘ adForwardOnly, adLockReadOnly
Set objRS = Server.CreateObject("ADODB.Recordset")
objRS.Open Application("SQL"), Application("Conn"), 0, 1
' adForwardOnly, adLockReadOnly

这些方法在负载上带来得差别小得惊人,于是我们得到规则7:

  • 不要对单独设置记录集属性感到担心 。

引用记录集中域值的最有效方法是什么?

到目前为止,我都是用名字引用记录集中的域值的。这可能是一种效率很低的方法,因为每次调用都需要查找域。为了证明这一点,下面的测试就要通过记录集中域的集合的指针来引用域(ADO__08.asp):

  1. ‘write data
  2. Do While Not objRS.EOF
  3. Response.Write(“< TR >”&_
  4. “< TD >” & objRS(0) & “< /TD >”&_
  5. “< TD >” & objRS(1) & “< /TD >”&_
  6. “< TD >” & objRS(2) & “< /TD >”&_
  7. “< TD >” & objRS(3) & “< /TD >”&_
  8. “< TD >” & objRS(4) & “< /TD >”&_
  9. “< TD >” & objRS(5) & “< /TD >”&_
  10. “< TD >” & objRS(6) & “< /TD >”&_
  11. “< /TR > “)
  12. objRS.MoveNext
  13. Loop
'write data
Do While Not objRS.EOF
Response.Write("< TR >"&_
"< TD >" & objRS(0) & "< /TD >"&_
"< TD >" & objRS(1) & "< /TD >"&_
"< TD >" & objRS(2) & "< /TD >"&_
"< TD >" & objRS(3) & "< /TD >"&_
"< TD >" & objRS(4) & "< /TD >"&_
"< TD >" & objRS(5) & "< /TD >"&_
"< TD >" & objRS(6) & "< /TD >"&_
"< /TR > ")
objRS.MoveNext
Loop

正如我们所预料的,装载时间的变化很小(差异可能是由于代码上的轻微减少引起的)。但是这种技术在有效显示时间上却带来了明显的减少。

在下面的例子中,我们将给每个域指定一个单独的变量。这种方法避免了在表格循环内的所有查找( ADO__09.asp ):

  1. If objRS.EOF Then
  2. Response.Write(“No Records Found”)
  3. Else
  4. ‘write headings
  5. Dim fld0
  6. Dim fld1
  7. Dim fld2
  8. Dim fld3
  9. Dim fld4
  10. Dim fld5
  11. Dim fld6
  12. Set fld0 = objRS(0)
  13. Set fld1 = objRS(1)
  14. Set fld2 = objRS(2)
  15. Set fld3 = objRS(3)
  16. Set fld4 = objRS(4)
  17. Set fld5 = objRS(5)
  18. Set fld6 = objRS(6)
  19. ‘write data
  20. Do While Not objRS.EOF
  21. Response.Write(“< TR >”&_
  22. “< TD >” & fld0 & “< /TD >”&_
  23. “< TD >” & fld1 & “< /TD >”&_
  24. “< TD >” & fld2 & “< /TD >”&_
  25. “< TD >” & fld3 & “< /TD >”&_
  26. “< TD >” & fld4 & “< /TD >”&_
  27. “< TD >” & fld5 & “< /TD >”&_
  28. “< TD >” & fld6 & “< /TD >”&_
  29. “< /TR >”)
  30. objRS.MoveNext
  31. Loop
  32. Set fld0 = Nothing
  33. Set fld1 = Nothing
  34. Set fld2 = Nothing
  35. Set fld3 = Nothing
  36. Set fld4 = Nothing
  37. Set fld5 = Nothing
  38. Set fld6 = Nothing
  39. Response.Write(“< /TABLE >”)
  40. End If
If objRS.EOF Then
Response.Write("No Records Found")
Else
'write headings
...
Dim fld0
Dim fld1
Dim fld2
Dim fld3
Dim fld4
Dim fld5
Dim fld6
Set fld0 = objRS(0)
Set fld1 = objRS(1)
Set fld2 = objRS(2)
Set fld3 = objRS(3)
Set fld4 = objRS(4)
Set fld5 = objRS(5)
Set fld6 = objRS(6)
'write data 

Do While Not objRS.EOF
Response.Write("< TR >"&_
"< TD >" & fld0 & "< /TD >"&_
"< TD >" & fld1 & "< /TD >"&_
"< TD >" & fld2 & "< /TD >"&_
"< TD >" & fld3 & "< /TD >"&_
"< TD >" & fld4 & "< /TD >"&_
"< TD >" & fld5 & "< /TD >"&_
"< TD >" & fld6 & "< /TD >"&_
"< /TR >") 

objRS.MoveNext
Loop
Set fld0 = Nothing
Set fld1 = Nothing
Set fld2 = Nothing
Set fld3 = Nothing
Set fld4 = Nothing
Set fld5 = Nothing
Set fld6 = Nothing
Response.Write("< /TABLE >")
End If

到目前,这种方法形成的结果是最好的。每条记录的显示时间下降成了.45 毫秒。

现在,所有测试脚本的配置都要求对结果记录集有一些了解。比如说,我们一直在栏标题中给域名编码,单独地引用这些域的值。下面的例子提供了一个动态的解决方案,在域的集合中循环,不仅得到数据,也得到域的标题(ADO__10.asp ):

  1. If objRS.EOF Then
  2. Response.Write(“No Records Found”)
  3. Else
  4. ‘write headings
  5. Response.Write(“< TABLE BORDER=1 >< TR >”)
  6. For Each objFld in objRS.Fields
  7. Response.Write(“< TH >” & objFld.name & “< /TH >”)
  8. Next
  9. Response.Write(“< /TR >”)
  10. ‘write data
  11. Do While Not objRS.EOF
  12. Response.Write(“< TR >”)
  13. For Each objFld in objRS.Fields
  14. Response.Write(“< TD >” & objFld.value & “< /TD >”)
  15. Next
  16. Response.Write(“< /TR >”)
  17. objRS.MoveNext
  18. Loop
  19. Response.Write(“< /TABLE >”)
  20. End If
If objRS.EOF Then
Response.Write("No Records Found")
Else
'write headings
Response.Write("< TABLE BORDER=1 >< TR >")
For Each objFld in objRS.Fields
Response.Write("< TH >" & objFld.name & "< /TH >")
Next
Response.Write("< /TR >")
'write data
Do While Not objRS.EOF
Response.Write("< TR >")
For Each objFld in objRS.Fields
Response.Write("< TD >" & objFld.value & "< /TD >")
Next
Response.Write("< /TR >")
objRS.MoveNext
Loop
Response.Write("< /TABLE >")
End If

可以看到,我们在性能上有一个损失,但是这个方法还是比ADO__07.asp要快一些。

下面的测试是在最后两个测试之间进行一些折中。通过在一个动态分配数组中保存域的引用,既维持了动态的灵活性,也挽回了一些性能上的损失。

  1. If objRS.EOF Then
  2. Response.Write(“No Records Found”)
  3. Else
  4. Dim fldCount
  5. fldCount = objRS.Fields.Count
  6. Dim fld()
  7. ReDim fld(fldCount)
  8. Dim i
  9. For i = 0 to fldCount-1
  10. Set fld(i) = objRS(i)
  11. Next
  12. ‘write headings
  13. Response.Write(“< TABLE BORDER=1 >< TR >”)
  14. For i = 0 to fldCount-1
  15. Response.Write(“< TH >” & fld(i).name & “< /TH >”)
  16. Next
  17. Response.Write(“< /TR >”)
  18. ‘write data
  19. Do While Not objRS.EOF
  20. Response.Write(“< TR >”)
  21. For i = 0 to fldCount-1
  22. Response.Write(“< TD >” & fld(i) & “< /TD >”)
  23. Next
  24. Response.Write(“< /TR >”)
  25. objRS.MoveNext
  26. Loop
  27. For i = 0 to fldCount-1
  28. Set fld(i) = Nothing
  29. Next
  30. Response.Write(“< /TABLE >”)
  31. End If
If objRS.EOF Then
Response.Write("No Records Found")
Else
Dim fldCount
fldCount = objRS.Fields.Count
Dim fld()
ReDim fld(fldCount)
Dim i
For i = 0 to fldCount-1
Set fld(i) = objRS(i)
Next
'write headings
Response.Write("< TABLE BORDER=1 >< TR >")
For i = 0 to fldCount-1
Response.Write("< TH >" & fld(i).name & "< /TH >")
Next
Response.Write("< /TR >")
'write data
Do While Not objRS.EOF
Response.Write("< TR >")
For i = 0 to fldCount-1
Response.Write("< TD >" & fld(i) & "< /TD >")
Next
Response.Write("< /TR >")
objRS.MoveNext
Loop
For i = 0 to fldCount-1
Set fld(i) = Nothing
Next
Response.Write("< /TABLE >")
End If

虽然它并不比最好值快,但是比前面的几个例子要快了很多,并且有一个优势就是能够动态地表现任何记录集。

在下一个测试中,我们将对以前的方案做一个彻底的改变,使用记录集的GetRows指令创建一个循环用的数组,而不是在记录集本身进行循环。注意,调用 GetRows之后,立刻就将记录集设置为Nothing,这样就能更快地释放系统资源。另外还要注意数组的第一个维数代表域,第二个维数代表行 ( ADO__12.asp ):

  1. If objRS.EOF Then
  2. Response.Write(“No Records Found”)
  3. objRS.Close
  4. Set objRS = Nothing
  5. Else
  6. ‘write headings
  7. ‘set array
  8. Dim arrRS
  9. arrRS = objRS.GetRows
  10. ‘close recordset early
  11. objRS.Close
  12. Set objRS = Nothing
  13. ‘write data
  14. Dim numRows
  15. Dim numFlds
  16. Dim row
  17. Dim fld
  18. numFlds = Ubound(arrRS, 1)
  19. numRows = Ubound(arrRS, 2)
  20. For row= 0 to numRows
  21. Response.Write(“< TR >”)
  22. For fld = 0 to numFlds
  23. Response.Write(“< TD >” & arrRS(fld, row) & “< /TD >”)
  24. Next
  25. Response.Write(“< /TR >”)
  26. Next
  27. Response.Write(“< /TABLE >”)
  28. End If
If objRS.EOF Then
Response.Write("No Records Found")
objRS.Close
Set objRS = Nothing
Else
'write headings
...
'set array
Dim arrRS
arrRS = objRS.GetRows
'close recordset early
objRS.Close
Set objRS = Nothing
'write data
Dim numRows
Dim numFlds
Dim row
Dim fld
numFlds = Ubound(arrRS, 1)
numRows = Ubound(arrRS, 2)
For row= 0 to numRows
Response.Write("< TR >")
For fld = 0 to numFlds
Response.Write("< TD >" & arrRS(fld, row) & "< /TD >")
Next
Response.Write("< /TR >")
Next
Response.Write("< /TABLE >")
End If

通过使用GetRows 指令,就可以获取整个记录集并将其装载到数组中。当恢复特别大的记录集时,这种方法有可能会造成资源问题,但是数据的循环快多了,因为类似于MoveNext 的函数调用和EOF 的检测都可以取消了。

不过速度的提升确实是有代价的,因为记录集的元数据不再与数据在一起。围绕这个问题,我在调用GetRows之前用记录集来恢复标题名。另外还可以提前提取数据类型和其它信息。还要注意,在我们的测试中,性能上的优势只有在使用大一些的记录集时才能看到。

在这部分最后的测试中,我们更进一步,使用记录集的GetString 指令。这个方法将整个记录集提取到一个大的字符串中,允许你指定自己的分隔符( ADO__13.asp ):

  1. If objRS.EOF Then
  2. Response.Write(“No Records Found”)
  3. objRS.Close
  4. Set objRS = Nothing
  5. Else
  6. ‘write headings
  7. ‘set array
  8. Dim strTable
  9. strTable = objRS.GetString (2, , “< /TD >< TD >”“< /TD >< /TR >< TR >< TD >”)
  10. ‘close recordset early
  11. objRS.Close
  12. Set objRS = Nothing
  13. Response.Write(strTable & “< /TD >< /TR >< /TABLE >”)
  14. End If
If objRS.EOF Then
Response.Write("No Records Found")
objRS.Close
Set objRS = Nothing
Else
'write headings
...
'set array
Dim strTable
strTable = objRS.GetString (2, , "< /TD >< TD >", "< /TD >< /TR >< TR >< TD >")
'close recordset early
objRS.Close
Set objRS = Nothing
Response.Write(strTable & "< /TD >< /TR >< /TABLE >")
End If

虽然这种方法已经接近了最高水平,但是它只适合于最简单的设计,因为它根本就不能应用于数据的特殊情况。

观察

在我们开始这套测试之前,执行每条记录的时间一直在.83 毫秒左右震动。这套测试中的大多数方法都将这个数字减少了一半。虽然有些方法明显地提供了更快的速度,但是代价是灵活性的降低。

下面的规则是以重要程度为顺序的:

  • 当记录集中的值不需要用一种特殊方式来对待并且能够格式化为一种统一的格式时,使用GetString方法来提取数据。
  • 当你在设计上需要更大的灵活性,但是又不需要用记录集的元数据进行工作,使用GetRows 方法将数据提取到一个数组中。
  • 当你需要设计的灵活性和元数据时,在进入一个数据恢复的循环之前,将你的域约束在本地变量中。避免用名字引用域。

使用临时字符串可以较好地代替缓冲器吗?

这是针对我上一篇文章提交的一些注解所引发的一个小小的离题。要讨论的问题是围绕着缓冲器的使用及使用临时字符串作为替代来收集输出,这样就允许 Response.Write 只调用一次。为了测试,我从ADO_11.asp的代码开始,将结果附加到一个字符串中,而不是在每个循环都调用Response.Write,当整个操 作都结束后,在字符串上调用Response.Write ( STR__01.asp ):

  1. Dim strTable
  2. strTable = “”
  3. ‘write headings
  4. strTable = strTable & “< TABLE BORDER=1 >< TR >”
  5. For i = 0 to fldCount-1
  6. strTable = strTable & “< TH >” & fld(i).name & “< /TH >”
  7. Next
  8. strTable = strTable & “< /TR >”
  9. ‘write data
  10. Do While Not objRS.EOF
  11. strTable = strTable & “< TR >”
  12. For i = 0 to fldCount-1
  13. strTable = strTable & “< TD >” & fld(i) & “< /TD >”
  14. Next
  15. strTable = strTable & “< /TR >”
  16. objRS.MoveNext
  17. Loop
  18. For i = 0 to fldCount-1
  19. Set fld(i) = Nothing
  20. Next
  21. strTable = strTable & “< /TABLE >”
  22. Response.Write(strTable)
Dim strTable
strTable = ""
'write headings
strTable = strTable & "< TABLE BORDER=1 >< TR >"
For i = 0 to fldCount-1
strTable = strTable & "< TH >" & fld(i).name & "< /TH >"
Next
strTable = strTable & "< /TR >"
'write data
Do While Not objRS.EOF
strTable = strTable & "< TR >"
For i = 0 to fldCount-1
strTable = strTable & "< TD >" & fld(i) & "< /TD >"
Next
strTable = strTable & "< /TR >"
objRS.MoveNext
Loop
For i = 0 to fldCount-1
Set fld(i) = Nothing
Next
strTable = strTable & "< /TABLE >"
Response.Write(strTable)

看起来执行得不是很好。也许正象许多人建议的,我们应该用Space 指令为这个字符串指定一些空间,这样它就不需要在循环期间总是为自己重新分配空间( STR__02.asp ):

  1. Dim strTable
  2. strTable = Space(10000)
Dim strTable
strTable = Space(10000)

也许Space 指令并不象建议的那样工作。我们最后的规则是:不要用临时字符串来收集输出。

规则的总结

现在我们来重新总结一下这些规则:

  • 避免包含ADOVBS.inc文件,用其它方法来使用常量。
  • 当使用一个单个记录集时,将连接字符串传递到ActiveConnection属性中。
  • 在一个页面上使用多个记录集时,创建一个Connection 对象,在ActiveConnection 属性中重复使用它。
  • 使用最适合你的任务的最简单的指针和锁的类型。
  • 通过ADODB.Recordset 类例示记录集以获得最好的性能和最大的灵活性。
  • 除非是一个断开的环境中所要求的,避免使用断开的记录集。
  • 不要对单独设置记录集属性感到担心。
  • 当记录集中的值不需要用一种特殊方式来对待并且能够格式化为一种统一的格式时,使用GetString方法来提取数据。
  • 当你在设计上需要更大的灵活性,但是又不需要用记录集的元数据进行工作,使用GetRows方法将数据提取到一个数组中。
  • 当你需要设计的灵活性和元数据时,在进入一个数据恢复的循环之前,将你的域约束在本地变量中。避免用名字引用域。
  • 不要用临时字符串来收集输出。

结论

同样,从这些测试中我们所学到的最重要的一点是:小小的变化会在性能上造成很大的影响。如果我们把第一个测试与ADO__09.asp(在记录集中循环的最快结果)相比,就会发现反应时间只是原始值的很小一部分。

所以要记住,永远不要想当然。如果你不能肯定,那就运行一些有针对性的测试。

ASP的类(class)详解

ASP 本身是不存在类这一概念的,这里所说的类为 ASP 支持的脚本语言 VBScript 中Class。VBScript 中类的功能相对比较简单,不具有真正意义上类的继承、重载等特性,但它仍然是非常有用的。使得程序组成更加合理,降低了编程的复杂度。

1.1、什么是类?

类:对象的蓝图

类是对象的符号表示形式。与蓝图描述构成建筑的项一样,类以同样的方式描述组成对象的属性、方法和事件。就像一副蓝图可以用于建成多座建筑一样,一个类 也可以用于根据需要创建多个对象。就像蓝图定义使用建筑的人可以访问建筑的哪些部分一样,类也可以通过封装来控制用户对对象项的访问。

类和对象

“类”和“对象”这两个术语有时互换使用,但实际上,类描述对象的“结构”,而对象是类的可用“实例”。每个实例都是其类的一个精确而又不同的副本。由于对象是类的实例,所以创建对象的操作称为“实例化”。

如果使用蓝图类比,类是蓝图,对象就是基于该蓝图的建筑。通常情况下,更改一个对象中的数据不会更改任何其他对象中的数据。

封装

“封装”是包含和控制对一组关联项的访问的能力。类提供一个最通用的方式来封装项。在下面的示例中,BankAccount 类封装描述银行帐户的方法、字段和属性。

如果没有封装,您就要声明单独的过程和变量来存储和管理银行帐户信息,并且要一次处理多个银行帐户就会比较困难。通过封装,您可以将 BankAccount 类中的数据和过程作为一个单元来使用。您可以同时处理多个银行帐户而不会混淆,因为每个帐户都由该类的一个唯一实例来表示。

封装还使您可以控制如何使用数据和过程。可以使用访问修饰符(如 Private 或 Public )来防止外部过程执行类方法或读取和修改属性和字段中的数据。您应该将类的内部详细信息声明为 Private 以防止它们在类的外部使用;这种技术被称为“数据隐藏”,是对帐户余额等客户信息的保护方式。

封装的一个基本规则是只能通过 Property 过程或方法来修改或检索类数据。隐藏类的实现详细信息可以防止这些类被以不希望的方式使用,并使您在以后修改这类项时没有兼容性问题方面的风险。例 如,BankAccount 类的更高版本可以更改 AccountBalance 字段的数据类型,而不会破坏依赖于此字段拥有特定数据类型的其他应用程序。

1.2、如何创建一个简单的类?

ASP 中创建一个类由 Class 语句声明:

引用内容
Class 语句
声明一个类的名称,以及组成该类的变量、属性和方法的定义。
Class name
statements
End Class

参数
name
必选项。Class 的名称;遵照标准的变量命名约定。

statements
必选项。一个或多个语句,定义了 Class 的变量、属性和方法。

说明
在 Class 块中,成员通过相应的声明语句被声明为 Private 或 Public。被声明为 Private 的将只在 Class 块内是可见的。被声明为 Public 不仅在 Class 块的内部是可见的,对 Class 块之外的代码也是可见的。没有使用 Private 或 Public 明确声明的被默认为 Public。 在类的块内部被声明为 Public 的过程 (Sub 或 Function)将成为类的方法。Public 变量将成为类的属性,同使用 Property Get、Property Let 和 Property Set 显式声明的属性一样。 类的缺省属性和方法是在它们的声明部分用 Default 关键字指定的。关于如何使用该关键字,请参考单独的声明语句主题。

示例:一个简单的 ASP Class 代码

  1. Class TestClass
  2. Sub PrintHello()
  3. Response.Write “Hello World!”
  4. End Sub
  5. End Class
Class TestClass �
    Sub PrintHello() �
        Response.Write "Hello World!" �
    End Sub �
End Class

1.3、如何实例化一个类?

  1. Dim objClass ‘ 定义一个类引用对象变量
  2. Set objClass = New TestClass ‘ 创建类引用对象
  3. ‘ 对象名称后加点,再跟成员变量、属性或函数,表示调用该对象的成员变量、属性或函数
  4. ‘ 这里屏幕将显示 Hello World!
  5. objClass.PrintHello
  6. Set objClass = Nothing ‘ 释放对象
Dim objClass ' 定义一个类引用对象变量
Set objClass = New TestClass ' 创建类引用对象
' 对象名称后加点,再跟成员变量、属性或函数,表示调用该对象的成员变量、属性或函数
' 这里屏幕将显示 Hello World!
objClass.PrintHello
Set objClass = Nothing ' 释放对象

ASP 类里不能重载构造函数,所以我们不能使用类似于 Set obj = New TestClass(“param”) 的语句来创建类。
在 ASP 中使用 Set 语句来创建\销毁类对象,有关 Set 语句的更多信息请参看 VBScript 帮助文档。

1.4、ASP 类事件(构造函数、析构函数)

引用内容
Initialize 事件
在创建类的实例时发生此事件。
Private Sub Class_Initialize()
语句
End Sub

当类初始化时,statements部分由零个或多个将要运行的代码语句组成。

Terminate 事件
在所关联的类的实例终止时发生此事件。
Private Sub Class_Terminate()
语句
End Sub

当类初始化时,statements 部分由零个或多个将要运行的代码语句组成。

示例:

  1. Class TestClass
  2. ‘ 该函数为构造函数,在使用 Set 创建对象时自动执行
  3. Private Sub Class_Initialize ‘ 设置 Initialize 事件。
  4. ResPonse.Write(“TestClass started”)
  5. End Sub
  6. ‘ 该函数为析构函数,在使用 Set 释放对象时自动执行
  7. Private Sub Class_Terminate ‘ 设置 Terminate 事件。
  8. ResPonse.Write(“TestClass terminated”)
  9. End Sub
  10. End Class
  11. Dim objT
  12. Set objT = New TestClass ‘ 创建一个 TestClass 实例。
  13. Set objT = Nothing ‘ 删除实例。
Class TestClass
    ' 该函数为构造函数,在使用 Set 创建对象时自动执行
    Private Sub Class_Initialize ' 设置 Initialize 事件。
        ResPonse.Write("TestClass started")
    End Sub
    ' 该函数为析构函数,在使用 Set 释放对象时自动执行
    Private Sub Class_Terminate ' 设置 Terminate 事件。
        ResPonse.Write("TestClass terminated")
    End Sub
End Class

Dim objT
Set objT = New TestClass ' 创建一个 TestClass 实例。
Set objT = Nothing ' 删除实例。

执行以上程序屏幕将显示:TestClass startedTestClass terminated

1.5、ASP 类成员变量、成员函数

ASP 类中成员变量(函数)有“公有”、“私有”之分,分别使用 Public、Private 语句来定义。

引用内容
Dim 语句
声明变量并分配存储空间。
Dim varname[([subscripts])][, varname[([subscripts])]] . . .

Public 语句
定义公有变量并分配存储空间。在 Class 块中定义私有变量。
Public varname[([subscripts])][, varname[([subscripts])]] . . .

Private 语句
定义私有变量并分配存储空间。在 Class块中定义私有变量。
Private varname[([subscripts])][, varname[([subscripts])]] . . .

参数
varname
变量的名称;遵循标准变量命名约定。

subscripts
数组变量的维数,最多可以声明 60 维的数组。subscripts 参数使用下列语法:
upper [, upper] . . .
数组的下界总是 0。

注意 在过程中使用 Private 语句时,通常将 Private 语句放在过程的开始处。

用 Dim 声明的 Script 级变量可用于脚本中的所有过程,过程级变量只能用于过程中。
Public语句变量可用于全部脚本中的所有过程。
Private 语句变量只能在声明该变量的脚本中使用。
如没有显式指定使用 Public或 Private,则 Function、Sub 过程默认为公用,即它们对于脚本中的所有其他过程是可见的。
有关公有、私有的更多信息请参看 VBScript 帮助文档。

示例:公有成员变量、私有成员变量、公有成员函数、私有成员函数

  1. Class TestClass
  2. Dim PublicParam ‘ 用 Dim 申明公有成员变量
  3. Private PrivateParam ‘ 用 Private 申明私有成员变量
  4. ‘PublicParam = “公有” ‘ 不能在 Class 标记以内,类函数以外为成员变量赋值,否则报语法错误
  5. ‘Const intMax = 5 ‘ 不能在 Class 标记以内,类函数以外使用 Const,否则报语法错误
  6. ‘ 该函数为构造函数,在使用 Set 创建对象时自动执行
  7. Private Sub Class_Initialize ‘ 设置 Initialize 事件。
  8. ‘ 在这里可以为成员变量赋值
  9. PublicParam = “公有”
  10. PrivateParam = “私有”
  11. End Sub
  12. ‘ 该函数为析构函数,在释放对象时自动执行
  13. Private Sub Class_Terminate ‘ 设置 Terminate 事件。
  14. ResPonse.Write “释放对象”
  15. End Sub
  16. ‘公有成员函数(过程),这里可不写 Public 语句
  17. ‘成员函数(过程)不需要也不能申明
  18. Public Sub PrintParam(i)
  19. If i = 1 Then
  20. Call PrintPublicParam()
  21. ElseIf i = 2 Then
  22. Call PrintPrivateParam()
  23. End If
  24. End Sub
  25. ‘私有成员函数(过程),比公有成员函数(过程)多一个 Private 语句
  26. Private Sub PrintPublicParam()
  27. Response.Write PublicParam
  28. End Sub
  29. Private Sub PrintPrivateParam()
  30. Response.Write PrivateParam
  31. End Sub
  32. End Class
  33. Dim objT
  34. Set objT = New TestClass ‘ 创建实例后自动调用 Class_Initialize 函数,为 PublicParam、PrivateParam 赋值
  35. Response.Write objT.PublicParam ‘ 输出:公有
  36. ‘Response.Write objT.PrivateParam ‘ 将发生错误,PrivateParam 为私有变量
  37. ‘赋新值
  38. objT.PublicParam = “新的公有”
  39. ‘objT.PrivateParam = “新的私有” ‘ 错误,同上
  40. objT.PrintParam(1) ‘ 输出 PublicParam 的值:新的公有
  41. objT.PrintParam(2) ‘ 输出 PrivateParam 的值:私有
  42. ‘将发生错误,私有函数
  43. ‘objT.PrintPublicParam
  44. ‘objT.PrintPrivateParam
  45. Set objT= Nothing
Class TestClass
    Dim PublicParam ' 用 Dim 申明公有成员变量
    Private PrivateParam ' 用 Private 申明私有成员变量
    'PublicParam = "公有" ' 不能在 Class 标记以内,类函数以外为成员变量赋值,否则报语法错误
    'Const intMax = 5 ' 不能在 Class 标记以内,类函数以外使用 Const,否则报语法错误

    ' 该函数为构造函数,在使用 Set 创建对象时自动执行
    Private Sub Class_Initialize ' 设置 Initialize 事件。
        ' 在这里可以为成员变量赋值
        PublicParam = "公有"
        PrivateParam = "私有"
    End Sub

    ' 该函数为析构函数,在释放对象时自动执行
    Private Sub Class_Terminate ' 设置 Terminate 事件。
        ResPonse.Write "释放对象"
    End Sub
   �
    '公有成员函数(过程),这里可不写 Public 语句
    '成员函数(过程)不需要也不能申明
    Public Sub PrintParam(i)
        If i = 1 Then
            Call PrintPublicParam()
        ElseIf i = 2 Then
            Call PrintPrivateParam()
        End If
    End Sub

    '私有成员函数(过程),比公有成员函数(过程)多一个 Private 语句
    Private Sub PrintPublicParam()
        Response.Write PublicParam
    End Sub
   �
    Private Sub PrintPrivateParam()
        Response.Write PrivateParam
    End Sub

End Class

Dim objT
Set objT = New TestClass ' 创建实例后自动调用 Class_Initialize 函数,为 PublicParam、PrivateParam 赋值
Response.Write objT.PublicParam ' 输出:公有
'Response.Write objT.PrivateParam ' 将发生错误,PrivateParam 为私有变量

'赋新值
objT.PublicParam = "新的公有"
'objT.PrivateParam = "新的私有" ' 错误,同上

objT.PrintParam(1) ' 输出 PublicParam 的值:新的公有
objT.PrintParam(2) ' 输出 PrivateParam 的值:私有

'将发生错误,私有函数
'objT.PrintPublicParam
'objT.PrintPrivateParam
Set objT= Nothing

1.6、ASP 类属性

类对外公开一个属性(变量)时有两种做法:
1、使用 Public 声明变量,这样在外部就可以直接读写这个变量。
2、 使用 Private 声明变量,然后使用 Property Let / Property Get 过程对外公开这个属性(只写/只读),在 Property 过程中可加入数据合法性验证代码,任何读写这个属性的外部过程都必须先通过 Property 过程的合法性验证,这就是所谓的封装,使得类更加强健。
事实上即使我们使用 Public 声明属性,VB 在后台总是用第二种方法进行处理这个属性。
当这个变量是一个对象变量(Object)时,使用 Property Set 过程来引用。
有关  Property Let / Property Get / Property Set  语句的更多信息请参看 VBScript 帮助文档。

引用内容
Property Let 语句
在 Class 块中,声明名称、参数和代码等,它们构成了赋值(设置)的 Property 过程的主体。

[Public | Private] Property Let name (
[arglist,] value
)
[statement]
[Exit Property]
[statement]
End Property

参数
Public
表明 Property Let 过程可以被所有脚本中的其他所有过程访问。

Private
表明 Property Let 过程只能被定义之的 Class 块内的其他过程访问。

name
Property Let 过程的名称;遵守标准的变量命名规则,区别仅仅在于其名称可以与相同 Class 块中的 Property Get 或 Property Set 过程相同。

arglist
该 变量列表代表了在调用时被传递到 Property Let 过程的参数。多个参数之间用逗号隔开。Property Let 过程的每个参数的名字必须与 Property Get 过程中的相应参数相同。此外, Property Let 过程的参数比相应的 Property Get 过程总要多一个。该参数为被赋予属性的值。

value
该变量中包含要赋予属性的值。当过程被调用时,该参数将出现在调用表达式的右侧。

statement
任意的一组语句,将在 Property Let 过程的主体内执行。

注意每个 Property Let 语句必须为所定义的属性定义至少一个参数。该参数(在存在多个参数时的最后一个参数)包含了当 Property Let 语句被调用时要赋予属性的值。该参数在前面的语法中被称为value。

说明
如果未明确地使用 Public 或 Private进行指定,Property Let 过程被缺省设置为公有的,即它们对于脚本内的其他所有过程都是可见的。Property Let过程中的局部变量的值在不同的过程调用之间是不被保存的。

在其他任何过程(例如 Function 或 Property Get)的内部不能够定义 Property Let 过程。

Exit Property 语句将导致立即从 Property Let 过程中退出。程序将从调用 Property Let 过程的语句之后的点继续执行。Exit Property 语句可以出现在 Property Let 过程中的任何位置,次数不限。

与 Function 和 Property Get 过程类似,Property Let 过程是一个单独的过程,它可以接受参数,执行一系列的语句,还可以改变参数的值。不过,与Function 和 Property Get 过程不同的是,它们两者都返回一个值,而Property Let过程只能用于属性赋值表达式的左侧。

示例:一个简单的属性读写

  1. Class TestClass
  2. Private strName
  3. Private intAge
  4. Private Sub Class_Initialize
  5. strName = “张三” ‘ 设置默认姓名
  6. intAge = 18 ‘ 设置默认年龄
  7. End Sub
  8. ‘ 属性只写
  9. Property Let Name(p)
  10. strName = p
  11. End Property
  12. ‘ 属性只读
  13. Property Get Name()
  14. Name = strName
  15. End Property
  16. Property Let Age(i)
  17. ‘ 若年龄范围介于[0-100]则赋值,否则置为-1
  18. If i >= 0 And i <= 100 Then
  19. intAge = i
  20. Else
  21. intAge = -1
  22. End If
  23. End Property
  24. Property Get Age()
  25. Age = intAge
  26. End Property
  27. End Class
  28. Dim objT
  29. Set objT = New TestClass
  30. Response.Write objT.Name ‘ 输出:张三
  31. Response.Write objT.Age ‘ 输出:18
  32. objT.Name = “李四” ‘ 设置姓名
  33. objT.Age = 20 ‘ 设置年龄
  34. Response.Write objT.Name ‘ 输出:李四
  35. Response.Write objT.Age ‘ 输出:20
  36. objT.Name = “王五” ‘ 设置姓名
  37. objT.Age = 101 ‘ 设置年龄
  38. Response.Write objT.Name ‘ 输出:王五
  39. Response.Write objT.Age ‘ 输出:-1
  40. Set objT = Nothing
Class TestClass
    Private strName
    Private intAge
   �
    Private Sub Class_Initialize �
        strName = "张三" ' 设置默认姓名
        intAge = 18 ' 设置默认年龄
    End Sub  

    ' 属性只写
    Property Let Name(p)
        strName = p
    End Property
   �
    ' 属性只读
    Property Get Name()
        Name = strName
    End Property

    Property Let Age(i)
        ' 若年龄范围介于[0-100]则赋值,否则置为-1
        If i >= 0 And i <= 100 Then
            intAge = i
        Else
            intAge = -1
        End If
    End Property
   �
    Property Get Age()
        Age = intAge
    End Property
End Class

Dim objT
Set objT = New TestClass
Response.Write objT.Name ' 输出:张三
Response.Write objT.Age ' 输出:18

objT.Name = "李四" ' 设置姓名
objT.Age = 20 ' 设置年龄

Response.Write objT.Name ' 输出:李四
Response.Write objT.Age ' 输出:20

objT.Name = "王五" ' 设置姓名
objT.Age = 101 ' 设置年龄
Response.Write objT.Name ' 输出:王五
Response.Write objT.Age ' 输出:-1
Set objT = Nothing

1.7、ASP 多类组合使用实现复杂应用

VBS 语言是基于对象而非面向对象,决定了其类不支持继承、重载、派生等特性。所以在实现复杂应用时我们只能使用类组合来实现。

示例:一个简单的类组合

  1. Class cFish
  2. Sub Swim()
  3. Response.Write “海阔凭鱼跃”
  4. End Sub
  5. End Class
  6. Class cBird
  7. Sub Fly()
  8. Response.Write “天高任鸟飞”
  9. End Sub
  10. End Class
  11. Class Animal
  12. Dim Fish
  13. Dim Bird
  14. Private Sub Class_Initialize()
  15. Set Fish = New cFish
  16. Set Bird = New cBird
  17. End Sub
  18. Private Sub Class_Terminate()
  19. Set Fish = Nothing
  20. Set Bird = Nothing
  21. End Sub
  22. End Class
  23. Dim objAnimal
  24. Set objAnimal = New Animal
  25. objAnimal.Fish.Swim()             ‘ 输出:海阔凭鱼跃
  26. objAnimal.Bird.Fly()                  ‘ 输出:天高任鸟飞
  27. Set objAnimal = Nothing