漫漫技术人生路

C#

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  234 随笔 :: 0 文章 :: 30 评论 :: 8 引用

公告

2006年10月3日 #

5  自定义类和对象

5.1  工厂方法

    在ECMAScript中创建工厂方法,返回一个特定类型的对象,以此实现代码的简洁适用。

function createFruit() {
  
var tempFruit = new Object;
  tempFruit.name 
= "apple";
  tempFruit.number 
= 5;
  tempFruit.showName 
= function() {
    alert(
this.name);
  }
;
return tempFruit;
}


var Fruit1 = creatFruit();
var Fruit2 = creatFruit();

    在createFruit()中可以加入形参来传入参数的值。随着ECMAScript不断被规范化,这种创建对象的方法已不再流行,一部分原因是语法上的,一部分原因是功能上的,如每个对象的实例都拥有属于自己的showName方法,给内存管理带来一定的开销。

5.2  构造函数

    选择一个类名,第一个字母大写,该类名即是构造函数的名称。创建一个构造函数和工厂方法比较类似,不同的是需要使用关键字new来创建对象的引用。使用构造函数的方式来创建对象和使用工厂方法有着相同的弊端。

function Fruit(name, number) {
  
this.name = name;
  
this.number = number;
  
this.showName = function() {
    alert(
this.name);
  }
;
}


var Fruit1 = new Fruit("apple"5);
var Fruit2 = new Fruit("pear"3);

5.3  使用 Prototype

    使用prototype属性可以用来创建新的对象,首先需要一个空的构造函数建立类的名称,然后所有的属性和方法都直接分配到prototype属性中。

function Fruit() {
}

Fruit.prototype.name 
= "apple";
Fruit.prototype.number 
= 5;
Fruit.prototype.showName 
= function() {
  alert(
this.name);
}
;

var fruit1 = new Fruit();
var fruit2 = new Fruit();

    但是,这样同样存在一些缺点。首先,构造函数中没有参数,给初始化带来一些麻烦,其次,当一个属性指向的是一个对象而非方法时,该对象会被所有的实例所共享,任何一点改动都会影响到其他对象引用的使用。

5.4  混合使用工厂方法和Prototype

这个概念很简单:使用构造函数定义所有除方法外的属性,使用 prototype 定义对象的方法。这样每个方法只会被创建一次,每个对象都能拥有自己对象实例的属性。

function Fruit(name, number) {
  
this.name = name;
  
this.number = number;
  
this.owner = new Array("Jerry""Terry");
}

Fruit.prototype.showName 
= function() {
  alert(
this.name);
}
;

var Fruit1 = new Fruit("apple"5);
var Fruit2 = new Fruit("pear"3);

5.5  动态 prototype

    简单来说,这种方法就是使用了一个标识符来判断 prototype 是否已经被指向某个方法,从而保证这些方法只会被创建并指向一次。

5.6  混合工厂方法

    这种方法和经典的工厂方法及构造函数方法在对象方法内存管理上存在同样的问题,一般不建议使用该方法,除了某些特殊情况(XML in JavaScript中有这样的例子)。

6  修改对象

    使用prototype对象可以对对象进行修改。除了用户自定义的对象外,ECMAScript原始对象也有prototype属性。直接使用 prototype可以给对象创建新的方法。

Number.prototype.toHexString = function() {
  
return this.toString(16);
}
;
var iNum = 10;
alert(iNum.toHexString());  
//输出A

    另外,使用prototype可以轻松修改已有的方法,让方法名指向新的方法。需要注意的是,指向新的方法后,原有的方法不再被任何对象使用,将会被垃圾回收器销毁,使得原有方法不再存在。比较安全的解决办法是,建立一个新的引用来保存原有的方法,然后再将原方法覆盖。

    比较特殊的是,ECMAScript中创建对象,在对象引用被创建后,可以给对象加入新的方法,并且可以立即在对象的引用中使用。这是ECMAScript的一个特性,但不推荐这样使用,以免带来不必要的麻烦,例如阅读理解、文档资料等。
posted @ 2006-10-03 18:14 javaca88 阅读(120) 评论(0) 编辑

JavaScript 中的继承

    

    Prototype

    在《JavaScript中的对象(下)》一文中,我们了解到任何prototype的属性和方法都会被传递到该类的所有实例中,利用这一特性,使用prototype也能实现继承。
 
function ClassA()  {
}


ClassA.prototype.id
= 1998;
ClassA.prototype.sayId
=function(){
   alert(
this.id);
}
;

functionClassB(){
}


ClassB.prototype
=newClassA();
ClassB.prototype.name
="";
ClassB.prototype.sayName
=function(){
   alert(
this.name);
}

    需要注意的是,这种实现继承的方法不能将参数传入到ClassA的构造器中,是一个缺陷。ClassB的所有属性和方法必需在将ClassB的prototype对象指向ClassA的实例之后进行附值。这样做是因为,prototype指向一个新的对象,在此之前prototype的属性和方法都被覆盖销毁。

    对代码进行测试:

var  obj1 = new ClassA();
var  obj2 = new ClassB();
obj1.id
= 1998;
obj2.id
= 2000;
obj2.name
="悉尼奥运会";
obj1.sayId();  
//输出"1998"
obj2.sayId();  //输出"1998"
obj2.sayName();  //输出"悉尼奥运会"

alert(obj2
instanceofClassA);  //输出"true"
alert(obj2 instanceofClassB);  //输出"true"

    在上述代码中可以看出,使用prototype实现继承,instanceof操作符出现了另外的用途,在用构造起定义类实现继承时,instanceof不会出现这种效果。但是使用prototype不能支持多重继承。
  
    在《JavaScript中的对象(下)》和上文的论述中可以了解到,使用构造器定义类实现继承和使用prototype实现继承均存在各自的缺陷,要避免出现这些情况,只有将两者混合使用。

    混合方法

    《JavaScript中的对象(下)》一文中曾经论述,创建一个类的最佳方法,是使用构造器的方法去定义属性,使用prototype定义方法。在继承中同样如此。

function ClassA(id) {
  
this .id = id;
}


ClassA.prototype.sayId
= function() {
   alert(
this.id);
}
;

function ClassB(id, name) {
   ClassA.call(
this, id);
  
this.name =name;
}


ClassB.prototype
=  new ClassA();
ClassB.prototype.sayName
=function(){
   alert(
this.name);
}
posted @ 2006-10-03 18:12 javaca88 阅读(100) 评论(0) 编辑

this是JavaScript中功能最强大的关键字之一。不幸的是,如果你不知道它具体怎么工作,你将很难正确使用它。

    下面我来阐述如何在
事件处理中来使用this,之后我会附加一些this相关的例子。

   
Owner

  
    接下来文章中我们将要讨论的问题是:在函数doSomething()中this所指的是什么?
 
function doSomething() {
  
this.style.color = '#cc0000';
}

    在JavaScript中,this通常指向的是我们正在执行的函数本身(译者注:用owner代表this所指向的内容),或者是,指向该函数所属的对象。当我们在页面中定义了函数doSomething()的时候,它的owner是页面,或者是JavaScript中的window对象(或global对象)。对于一个onclick属性,它为它所属的HTML元素所拥有,this应该指向该HTML元素。

    这种“所有权”就是JavaScript中面向对象的一种方式。在Objects as associative arrays中可以查看一些更多的信息。

this1.gif


    如果我们在没有任何更多准备情况下执行doSomething(),this关键字会指向window,并且该函数试图改变window的style.color。因为window并没有style对象,这个函数将非常不幸的运行失败,并产生JavaScript错误。

    Copying
  
    因此如果我们想充分利用this,我们不得不注意使用this的函数应该被正确的HTML元素所拥有。换句话说,我们应该复制这个函数到我们的onclick属性。Traditional event registration会关心它。

element.onclick = doSomething;

    这个函数被完整复制到onclick属性(现在成为了函数)。因此如果这个event handler被执行,this将指向HTML元素,并且该元素的颜色得以改变。

this2.gif


    这种方法使得我们能够复制这个函数到多个event handler。每次this都将指向正确的HTML元素:

this3.gif


    这样你就可以最大限度使用this。每当执行该函数,this所指向的HTML元素都正确响应事件,这些HTML元素拥有doSomething()的一个拷贝。

    Referring

    然而,如果你使用inline event registration(内联事件注册)

<element onclick="doSomething()">

    你将不能拷贝该函数!反而这种差异是非常关键的。onclick属性并不包含实际的函数,仅仅是一个函数调用。

doSomething();

    因此,它将声明“转到doSomething()并且执行它”。当我们到达doSomething(),this关键字又重新指向了全局的window对象,函数返回错误信息。

this4.gif


    The difference

    如果你想使用this来指向HTML元素响应的事件,你必须确保this关键字被写在onclick属性里。只有在这种情况下它才指向event handler所注册的HTML元素。
 
element.onclick = doSomething;
alert(element.onclick)

    你将得到

function doSomething() {
  
this.style.color = '#cc0000';
}

    正如你所见,this关键字被展现在onclick函数中,因此它指向HTML元素。

    但是如果执行

<element onclick="doSomething()">
alert(element.onclick)

你将得到

function onclick() {
  doSomething()
}

    这仅仅是到doSomething()函数的一个引用。this关键字并没有出现在onclick函数中,因此它不指向HTML元素。

    例子--拷贝

    下面的例子中,this被写入onclick函数里:

element.onclick = doSomething
element.addEventListener('click', doSomething, 
false)
element.onclick 
= function() {this.style.color = '#cc0000';}
<element onclick="this.sytle.color = '#cc0000';">

    例子--引用

    下述情况中,this指向window:

element.onclick = function() {doSomething()}
element.attachEvent('onclick', doSomething)
<element onclick="doSomething()">

    注意attachEvent()的出现。Microsoft event registration model最主要的弊端是attachEvent()创建了一个指向函数的引用,而不是复制它。因此有时不可能知道哪个HTML正在处理该事件。

    组合使用

    当使用内联事件注册时,你可以将this发送到函数以至于可以正常使用:

<element onclick="doSomething(this)">
function doSomething(obj) {
  
//this出现在event handler中并被发送到函数
  //obj指向HTML元素,因此可以这样:
  obj.style.color = '#cc0000';
}
posted @ 2006-10-03 18:11 javaca88 阅读(140) 评论(0) 编辑

Objects as associative arrays

On this page I explain how JavaScript objects are also associative arrays (hashes). Using these you can associate a key string with a value string, which can be very useful sometimes.

Suppose you have a mouseover / click image swap script. You want to keep track of the status of each image, whether it is normal, mouseovered or clicked. In addition you want to be able to reach this status by image name. So if you have an image named 'Home' you want to read out

theStatus.Home

and get one of the values 'normal', 'mouseover' or 'clicked', corresponding to the current status of the image.
To do this you need JavaScript objects.

Objects in JavaScript

JavaScript is an object oriented language. However, in practice objects defined by the programmer himself are rarely used, except in complex DOM API's. Of course such standard objects as window and document and their numerous offspring are very important, but they are defined by the browser, not by the programmer.

I myself have written JavaScript for more than three years without ever defining an object. The technique explained on this page is the first practical use of programmer-defined objects I've found.

Since the only other programming languages I know are Commodore 64 Basic (which is not object oriented, to put it mildly) and Perl (which doesn't need to be object oriented) and since I don't have any formal training in programming I cannot write a general introduction to objects and object oriented programming. Therefore a quick overview will have to suffice.

Methods and properties

In JavaScript you can define your own objects. In addition, you can assign methods and properties to each object, pre-written or self-defined.

  • Methods are 'things that do something', they can be recognized by their brackets (). When you call them, like object.method(), something happens.
  • Properties are 'things that are something'. They have a value, for instance a number, a string or a Boolean value. When you call them, like object.property, you get (or set) this value.

Normal JavaScript functions are also methods (hence the brackets). If you do

document.write('text')

you execute the pre-defined write() method of the document object. If you write your own functions you add methods to the window object, the parent of all other JavaScript objects.

Likewise, if you ask for the innerHeight of a page, you access a property of the window object and if you define a variable of your own you really add a new property to the window object.

So you already use methods and properties in everyday JavaScripting. Since most of these are preprogrammed functions and variables, you usually don't need to worry about the objects themselves, they're just a kind of 'black boxes' that contain useful stuff. The methods and properties (functions and variables) that you define yourself are usually added to the window object.

Defining an object and properties

But now we want to create an object of our own. This is simple:

var theStatus = new Object;

Now we have initialized our theStatus object and we can start adding properties (in this example we don't need methods). What we want is to create one property for each image on the page. We could do

theStatus.Home = 'normal';

Now we have added a new property Home to our object and set its value to the string 'normal'. (Remember that JavaScript is case sensitive, so the property home does not exist, only Home.)

All this is very useful, but using this notation we encounter problems later on. Suppose we want to create a property of theStatus for each image on the page. The property should have the same name as the image and its value should be 'normal'.

We cannot do:

var x = document.images;
for (var i=0;i<x.length;i++)
{
	var theName = x[i].name;
	theStatus.theName = 'normal';
}

We go through the entire images array of the page, take the name of each image and then try to create a new property with the same name. But the code above doesn't work. Each time you do

	theStatus.theName = 'normal';

JavaScript faithfully creates a new property named theName and sets its value to 'normal'. After executing this script you have only one property theName. This is not what we want, we want one property for each image.

Associative arrays

So we have to use one of JavaScript's minor mysteries. In JavaScript, objects are also associative arrays (or hashes). That is, the property

theStatus.Home

can also be read or written by calling

theStatus['Home']

Thus, you can access each property by entering the name of the property as a string into this array. Such an array associates each key with a value (in this case the key Home is associated with the value normal). In the Perl programming language it is also called a hash.

Unlike Perl, which requires you to create such an associative array explicitly, JavaScript automatically creates a associative array for each object.

You see this behaviour with common objects like a form. You can access a form by performing either of these DOM calls:

document.forms['theForm']
document.forms.theForm

(You can also use document.theForm but that's a special case, not regular behaviour of JavaScript objects/associative arrays).

So when we want to set the status of each image to 'normal' in our object, we do

var x = document.images;
for (var i=0;i<x.length;i++)
{
  var theName = x[i].name;
  theStatus[theName] = 'normal';
}

and it works. Now theName (a string) is put into the brackets [] where a string is expected. So you create a new key/value pair, which is the same as a new property with a value.

All this is JavaScript magic at its fullest. I don't completely understand what I'm doing either, but it works just fine. Basically you now have the power to let one name or string refer to another one.

for (var i in object)

for (var i in object) is equivalent to Perl foreach $key (keys %hash).

Just as you can go through each element of a normal array by

var x = [the array];
for (var i = 0;i<x.length;i++)
{
  do something with x[i]
}

you can also go through each element of an associative array. Suppose you want to go through the status values of all images. If the status of the image is 'mouseover' you want to call a function callFn() and pass the image name to it. You can of course tediously write out everything:

if (theStatus.Home == 'mouseover')
   callFn('Home'):
if (theStatus.Place == 'mouseover')
   callFn('Place'):
etc.

or

if (theStatus['Home'] == 'mouseover')
   callFn('Home'):
if (theStatus['Place'] == 'mouseover')
   callFn('Place'):
etc.

But this quickly leads to immense scripts. Besides, if you rename an image later on you also have to change a line of code and of course you forget, so you get errors etc.

Fortunately JavaScript has the for/in statement which is meant exactly for this situation. If you do

for (var i in theStatus)
{
  if (theStatus[i] == 'mouseover')
    callFn(i)
}

you go through all properties of the theStatus object (= all keys in the associative array theStatus). The variable i succesively becomes the name of each property of the object (key of the associative array) so you can do something with theStatus[i] and it is done to each property.
In this case, if an image status has the value 'mouseover' you call callFn() and pass it the key (the name of the image).

(Note that JavaScript doesn't guarantee any particular order for the properties. So you cannot expect the property that was defined first to appear first, it might come last.)

Test script

A tiny script for your testing pleasure. If you click this link this script is executed:

var theStatus = new Object();

function testIt() {
	theStatus.Home = 'mouseover';
	theStatus['Place'] = 'click';
	for (var i in theStatus)
	{
		alert('theStatus[\''+i+'\'] is ' + theStatus[i])
	}
}
posted @ 2006-10-03 18:09 javaca88 阅读(69) 评论(0) 编辑

在浏览器中使用JavaScript分为内联和外联两种方法,和CSS比较相似。

    内联

<html>
<head>
<title>内联</title>
<script language="JavaScript">
  
function message() {
    alert(
"Good Day !");
  }

</script>
</head>
<body></body>
</html>

    外联

<html>
<head>
<title>外联</title>
<script language="JavaScript" src="../path/external.js"></script>
</head>
<body></body>
</html>

    在external.js中,可以包含任何js代码,例如类的定义、方法函数等。相比之下,使用外联方法引用js文件可以做到更安全、代码更容易管理、节省资源开销。之所以能节省资源开销,是因为当两个页面同时使用一个js文件时,该js文件只会被下载一次,避免的相同代码重复下载增加页面的代码量。

    网页中的JavaScript源代码一般放置在<head></head>区域,当写在<body></body>中时,一旦页面加载就会执行body中的JavaScript代码,无法人工控制代码的执行。如果需要页面加载时同时执行JavaScript方法,则需要在head中定义该方法,否则系统会报错。

    JavaScript最初只有一种浏览器的支持,因此,需要一种方法将JavaScript代码隐藏在老的浏览器中,避免对JavaScript解析产生错误,这就是我们过去经常可以看到的,在代码前后加上<!--><//-->,在现在看来,这种方法已经没有必要了,一是因为使用外联方式引用JavaScript代码更加合适,二是当前主流浏览器对JavaScript的支持已经越来越多,当然也就能正确识别JavaScript代码。

    在浏览网页时,当某张图片无法显示时,可以通过文字来替换它,当网页不支持某段JavaScript代码时,我们也能通过一种替代的方式来提醒用户自己的浏览器不支持JavaScript或禁止了JavaScript,即使用<noscript></noscript>,放置在body区域中。

    随着XHTML(eXtensible HTML)标准的出现,<script>标签也出现了一些变化,过去我看到有的页面在<script>标签中使用languange属性,有的使用type属性,当时没怎么注意,其实后者正是XHTML出现后所定义的,可以为JavaScript设置mime类型为"text/javascript"。

    XHTML带来的另外一个变化就是使用CDATA,使用CDATA可以在表达式中使用一些特殊字符,增强代码的可读性,避免使用"&lt"、"&gt"来分别表示小于和大于。

<script type="text/javascript">
<![CDATA[
  
function comp(a, b) {
    
if (a > b)
      alert(
"a is bigger than b");
    
else
      alert(
"a is not bigger than b");
  }

]]
>
</script>

    代码中大于和引号均使用了我们常见的书写形式,避免了使用XML实体所带来的阅读上的困难。当然,这也存在旧的浏览器无法识别CDATA的问题,需要我们加入注释来隐藏CDATA标签。大家可以想到,最好的办法还是使用外联的方法使用JavaScript。

<script type="text/javascript">
//<![CDATA[
  function comp(a, b) {
    
if (a > b)
      alert(
"a is bigger than b");
    
else
      alert(
"a is not bigger than b");
  }

//]]>
</script>

    虽然部分浏览器还不完全支持XHTML,但我们应该尽量按照XHTML标准来书写代码,使得该标准能够获得更大更广泛的支持。
posted @ 2006-10-03 17:55 javaca88 阅读(60) 评论(0) 编辑

JavaScript 对象的反射及应用

    作者:Flyingis

    Java和.NET都有着比较完善的反射机制,用来处理未知的对象并获取它们的属性和方法。JavaScript虽然没有完善的反射体系,但在编程的时候还是可以通过代码设计来实现类似反射的基本功能。

    检测一个JavaScript对象是否支持某种特定的属性或方法:

if (typeof(obj.property) != "undefined"{}

    这样声明比直接使用"if (obj.property)"来描述要更准确,因为当obj.property的值为false、0、null的时候,虽然该属性存在,但返回的结果却恰恰相反。

    如果要求检测更详细一些,查看该属性的具体类型,可以用instanceof操作符:

if (obj instanceof PredefinedObj) {}

    但是,当对obj对象进行条件检测的时候,如果多种条件的对象类型存在继承关系,则需要注意代码的书写顺序,例如:

function() ExamineType(obj) {
  
if (obj instanceof Object) {
    alert(
"An Object");
  
else if (obj instanceof Array) {
    alert(
"An Array");
  }

  }

}

    上述代码执行的结果会认为原为Array类型的obj是一个Object,因为Array本身就是从Object继承而来,显然,将对Array的检测放在前面会得到更精确的结果。因此,使用instanceof来判断对象类型,需要注意当两个对象存在继承关系的时候,应该关注检测顺序的问题,进一步我们可以想到,JSON创建的对象不是Object就是Array,使用instanceof来检测JSON对象意义不大。

    利用JavaScript的反射,我们可以编写一个函数来检查对象是否有一个特定名称的函数,然后利用该函数进行计算,以此在JavaScript中实现接口的功能,为在Ajax中使用设计模式奠定基础。

//this.getWeight和this["getWeight"]意义相同
//
判断对象是否存在指定名称的函数
Object.prototype.hasFunc = function(func) {
  
return this && this[func] && this[func] instanceof Function;
}


function hasWeight(obj) {
  
return obj.hasFunc("getWeight");
}


//判断参数是否为数值类型
function isNum(param) {
  
return parseFloat(param) != NaN;
}


//计算两个对象的重量
function calWeight(obj1, obj2) {
  
var total = null;
  
if (hasWeight(obj1) && hasWeight(obj2)) {
    
var w1 = obj1.getWeight();
    
var w2 = obj2.getWeight();
    
if (isNum(w1) && isNum(w2)) {
      total 
= parseFloat(w1) + parseFloat(w2);
    }

  }

  
return total;
}

    calWeight先判断两个对象是否均存在getWeight()函数,然后检查getWeight()计算结果是否为数值类型,最后进行数值相加返回计算结果。需要注意的是,parseFloat(param)函数能够除去param中非数字部分,如果param=16pm,parseFloat(16pm)得到的结果是16。如果不使用parseFloat(param)函数对getWeight()计算结果进行检验,那么会带来安全性的问题,这种情况下可以将对象的getWeight()设计为返回字符串或其他类型,在调用它之前我们是不知道JavaScript函数的返回类型的,因为JavaScript函数没有预先定义的类型。

NaN就是指not a number,可以用来直接和变量比较。isNaN()是判断函数,上面例子可以写成:
"parseFloat(param).isNaN() == false"。


function isNum(param) {
return !isNaN(parseFloat(param));
}

顺便说一下,obj.property的值不但可以是null,false,0,也可以是undefined。这种情况下这个property也是存在的:
var o={};
o.a=undefined;
for(var i in o) alert(typeof(o[i])=="undefined")
posted @ 2006-10-03 17:52 javaca88 阅读(164) 评论(0) 编辑