分类归档: Javascript

[转]JavaScript类的继承

利用共享prototype实现继承
继承是面向对象开发的又一个重要概念,它可以将现实生活的概念对应到程序逻辑中。例如水果是一个类,具有一些公共的性质;而苹果也是一类,但它们属于水果,所以苹果应该继承于水果。
在JavaScript中没有专门的机制来实现类的继承,但可以通过拷贝一个类的prototype到另外一个类来实现继承。一种简单的实现如下:
fucntion class1(){
      //构造函数
}

function class2(){
      //构造函数
}
class2.prototype=class1.prototype;
class2.prototype.moreProperty1="xxx";
class2.prototype.moreMethod1=function(){
      //方法实现代码
}
var obj=new class2();
这样,首先是class2具有了和class1一样的prototype,不考虑构造函数,两个类是等价的。随后,又通过prototype给class2赋予了两个额外的方法。所以class2是在class1的基础上增加了属性和方法,这就实现了类的继承。
JavaScript提供了instanceof操作符来判断一个对象是否是某个类的实例,对于上面创建的obj对象,下面两条语句都是成立的:
obj instanceof class1
obj instanceof class2
表面上看,上面的实现完全可行,JavaScript也能够正确的理解这种继承关系,obj同时是class1和class2的实例。事是上不对, JavaScript的这种理解实际上是基于一种很简单的策略。看下面的代码,先使用prototype让class2继承于class1,再在 class2中重复定义method方法:
<script language="JavaScript" type="text/javascript">
<!–
//定义class1
function class1(){
      //构造函数
}
//定义class1的成员
class1.prototype={
      m1:function(){
            alert(1);
      }
}
//定义class2
function class2(){
      //构造函数
}
//让class2继承于class1
class2.prototype=class1.prototype;
//给class2重复定义方法method
class2.prototype.method=function(){
      alert(2);
}
//创建两个类的实例
var obj1=new class1();
var obj2=new class2();
//分别调用两个对象的method方法
obj1.method();
obj2.method();
//–>
</script>
从代码执行结果看,弹出了两次对话框“2”。由此可见,当对class2进行prototype的改变时,class1的prototype也随之改变, 即使对class2的prototype增减一些成员,class1的成员也随之改变。所以class1和class2仅仅是构造函数不同的两个类,它们 保持着相同的成员定义。从这里,相信读者已经发现了其中的奥妙:class1和class2的prototype是完全相同的,是对同一个对象的引用。其 实从这条赋值语句就可以看出来:
//让class2继承于class1
class2.prototype=class1.prototype;
在JavaScript中,除了基本的数据类型(数字、字符串、布尔等),所有的赋值以及函数参数都是引用传递,而不是值传递。所以上面的语句仅仅是让 class2的prototype对象引用class1的prototype,造成了类成员定义始终保持一致的效果。从这里也看到了instanceof 操作符的执行机制,它就是判断一个对象是否是一个prototype的实例,因为这里的obj1和obj2都是对应于同一个prototype,所以它们 instanceof的结果都是相同的。
因此,使用prototype引用拷贝实现继承不是一种正确的办法。但在要求不严格的情况下,却也是一种合理的方法,惟一的约束是不允许类成员的覆盖定义。下面一节,将利用反射机制和prototype来实现正确的类继承。
利用反射机制和prototype实现继承
前面一节介绍的共享prototype来实现类的继承,不是一种很好的方法,毕竟两个类是共享的一个 prototype,任何对成员的重定义都会互相影响,不是严格意义的继承。但在这个思想的基础上,可以利用反射机制来实现类的继承,思路如下:利用 for(…in…)语句枚举出所有基类prototype的成员,并将其赋值给子类的prototype对象。例如:
<script language="JavaScript" type="text/javascript">
<!–
function class1(){
      //构造函数
}
class1.prototype={
      method:function(){
           alert(1);
      },
      method2:function(){
           alert("method2");
      }
}
function class2(){
      //构造函数
}
//让class2继承于class1
for(var p in class1.prototype){
       class2.prototype[p]=class1.prototype[p];
}

//覆盖定义class1中的method方法
class2.prototype.method=function(){
      alert(2);
}
//创建两个类的实例
var obj1=new class1();
var obj2=new class2();
//分别调用obj1和obj2的method方法
obj1.method();
obj2.method();
//分别调用obj1和obj2的method2方法
obj1.method2();
obj2.method2();
//–>
</script>
从运行结果可见,obj2中重复定义的method已经覆盖了继承的method方法,同时method2方法未受影响。而且obj1中的method方 法仍然保持了原有的定义。这样,就实现了正确意义的类的继承。为了方便开发,可以为每个类添加一个共有的方法,用以实现类的继承:
//为类添加静态方法inherit表示继承于某类
Function.prototype.inherit=function(baseClass){
     for(var p in baseClass.prototype){
            this.prototype[p]=baseClass.prototype[p];
     }
}
这里使用所有函数对象(类)的共同类Function来添加继承方法,这样所有的类都会有一个inherit方法,用以实现继承,读者可以仔细理解这种用法。于是,上面代码中的:
//让class2继承于class1
for(var p in class1.prototype){
       class2.prototype[p]=class1.prototype[p];
}
可以改写为:
//让class2继承于class1
class2.inherit(class1)
这样代码逻辑变的更加清楚,也更容易理解。通过这种方法实现的继承,有一个缺点,就是在class2中添加类成员定义时,不能给prototype直接赋值,而只能对其属性进行赋值,例如不能写为:
class2.prototype={
      //成员定义
}
而只能写为:
class2.prototype.propertyName=someValue;
class2.prototype.methodName=function(){
      //语句
}
由此可见,这样实现继承仍然要以牺牲一定的代码可读性为代价,在下一节将介绍prototype-1.3.1框架(注:prototype-1.3.1框 架是一个JavaScript类库,扩展了基本对象功能,并提供了实用工具详见附录。)中实现的类的继承机制,不仅基类可以用对象直接赋值给 property,而且在派生类中也可以同样实现,使代码逻辑更加清晰,也更能体现面向对象的语言特点。

prototype-1.3.1框架中的类继承实现机制
在prototype-1.3.1框架中,首先为每个对象都定义了一个extend方法:
//为Object类添加静态方法:extend
Object.extend = function(destination, source) {
for(property in source) {
     destination[property] = source[property];
}
return destination;
}
//通过Object类为每个对象添加方法extend
Object.prototype.extend = function(object) {
return Object.extend.apply(this, [this, object]);
}
Object.extend方法很容易理解,它是Object类的一个静态方法,用于将参数中source的所有属性都赋值到destination对象 中,并返回destination的引用。下面解释一下Object.prototype.extend的实现,因为Object是所有对象的基类,所以 这里是为所有的对象都添加一个extend方法,函数体中的语句如下:
Object.extend.apply(this,[this,object]);
这一句是将Object类的静态方法作为对象的方法运行,第一个参数this是指向对象实例自身;第二个参数是一个数组,包括两个元素:对象本身和传进来 的对象参数object。函数功能是将参数对象object的所有属性和方法赋值给调用该方法的对象自身,并返回自身的引用。有了这个方法,下面看类继承 的实现:
<script language="JavaScript" type="text/javascript">
<!–
//定义extend方法
Object.extend = function(destination, source) {
for (property in source) {
     destination[property] = source[property];
}
return destination;
}
Object.prototype.extend = function(object) {
return Object.extend.apply(this, [this, object]);
}
//定义class1
function class1(){
      //构造函数
}
//定义类class1的成员
class1.prototype={
      method:function(){
           alert("class1");
      },
      method2:function(){
           alert("method2");
      }

}
//定义class2
function class2(){
      //构造函数
}
//让class2继承于class1并定义新成员
class2.prototype=(new class1()).extend({
      method:function(){
           alert("class2");
      }
});

//创建两个实例
var obj1=new class1();
var obj2=new class2();
//试验obj1和obj2的方法
obj1.method();
obj2.method();
obj1.method2();
obj2.method2();
//–>
</script>
从运行结果可以看出,继承被正确的实现了,而且派生类的额外成员也可以以列表的形式加以定义,提高了代码的可读性。下面解释继承的实现:
//让class2继承于class1并定义新成员
class2.prototype=(new class1()).extend({
      method:function(){
           alert("class2");
      }
});
上段代码也可以写为:
//让class2继承于class1并定义新成员
class2.prototype=class1.prototype.extend({
      method:function(){
           alert("class2");
      }
});
但因为extend方法会改变调用该方法对象本身,所以上述调用会改变class1的prototype的值,犯了和以前一样的错误。在 prototype-1.3.1框架中,巧妙的利用new class1()来创建一个实例对象,并将实例对象的成员赋值给class2的prototype。其本质相当于创建了class1的prototype 的一个拷贝,在这个拷贝上进行操作自然不会影响原有类中prototype的定义了。

Read: 883

INTRODUCTION TO JavaScript Functions

作者:F. Permadi
译者:Sheneyan(子乌)
英文原文: INTRODUCTION TO JavaScript Functions
中文译文(包括示例):javascript的函数
子乌注:一篇相当不错的function入门文章,个人感觉相当经典。

引用地址:http://www.ny2050.cn/article.asp?id=82

词语翻译列表:

function:函数(Function未翻译)
declare:定义
assign:指派,分配
functionbody:函数体(就是函数的内容)
object:对象
property:属性
unnamed:匿名(在这里没翻译成未命名)
object oriented programming:面相对相编程
class:类(比如后面的class data type我翻译成类数据类型)
pointer:指针
reassign:重新分配
nest:嵌套
feature:功能,特性
local/global:局部/全局
blueprint:蓝图(?)
user defined:用户自定义
instance:实例
prototype:原型(除了标题都不翻译)
internal:内部
constructor:构造器
duplication:

函数:定义

有以下这些方法可以定义一个函数。所有这些都是有效的,但是它们在后台如何实现的则有一些差别。

常用的写法

一般大家都用这个写法来定义一个函数:

functionName([parameters]){functionBody};
Example D1:

CODE:
function add(a, b)
{
return a+b;
}
alert(add(1,2)); // 结果 3

当我们这么定义函数的时候,函数内容会被编译(但不会立即执行,除非我们去调用它)。而且,也许你不知道,当这个函数创建的时候有一个同名的对象也被创建。就我们的例子来说,我们现在有一个对象叫做“add”(要更深入了解,看底下函数:对象节。)

匿名函数

我们也可以通过指派一个变量名给匿名函数的方式来定义它。

Example D2

var add=function(a, b)
{
return a+b;
}
alert(add(1,2)); // 结果 3

这个代码和前一个例子做了同样的事情。也许语法看起来比较奇怪,但它应该更能让你感觉到函数是一个对象,而且我们只是为这个对指派了一个名称。可以把它看做和 var myVar=[1,2,3]一样的语句。以这种方式声明的函数内容也一样会被编译。

当我们指派一个这样的函数的时候,我们并不一定要求必须是匿名函数。在这里,我作了和ExampleD2一样的事情,但我加了函数名“theAdd”,而且我可以通过调用函数名或者是那个变量来引用函数。

Example D2A

var add=function theAdd(a, b)
{
return a+b;
}
alert(add(1,2)); // 结果 3
alert(theAdd(1,2)); // 结果也是 3

使用这种方式来定义函数在面向对象编程中是很有用的,因为我们能像底下这样使一个函数成为一个对象的属性。

var myObject=new Object();
myObject.add=function(a,b){return a+b};
// myObject 现在有一个叫做“add”的属性(或方法)
// 而且我能够象下面这样使用它
myObject.add(1, 2);

我们也能够通过使用运算符new来定义一个函数。这是一个最少见的定义函数的方式并且并不推荐使用这种方式除非有特殊的理由(可能的理由见下)。语法如下:

varName=new Function([param1Name, param2Name,…paramNName], functionBody);
Example D3:

var add=new Function("a", "b", "return a+b;");
alert(add(3,4)); // 结果 7

我在这里有两个参数叫做a和b,而函数体返回a和b的和。请注意new Function(…)使用了大写F,而不是小写f。 这就告诉javascript,我们将要创建一个类型是Function的对象。 还要注意到,参数名和函数体都是作为字符串而被传递。我们可以随心所欲的增加参数,javascript知道函数体会是右括号前的最后一个字符串(如果没 有参数,你能够只写函数体)。你没必要将所有东西都写在一行里(使用或者使用字符串连接符+来分隔长代码)。标记告诉JavaScript在下一行查 找字符串的其余部分。例子如下:

Example D4

var add=new Function("a", "b",
"alert" + // 注意 "+"
"(‘adding ‘+a+’ and ‘ +b); // 和 ""的不同用法
return a+b;");
alert(add(3,4)); // 结果 7

采用这种方式定义函数会导致函数并没被编译,而且它有可能会比用其它方式定义的函数要慢。至于为什么,看一下这个代码:

Example D5

function createMyFunction(myOperator)
{
return new Function("a", "b", "return a" + myOperator + "b;");
}

var add=createMyFunction("+"); // 创建函数 "add"
var subtract=createMyFunction("-"); // 创建函数 "subtract"
var multiply=createMyFunction("*"); // 创建函数 "multiply"
// test the functions
alert("加的结果="+add(10,2)); // 结果是 12
alert("减的结果="+subtract(10,2)); // 结果是 8
alert("乘的结果="+multiply(10,2)); // 结果是 20
alert(add);

这个有趣的例子创建了三个不同的function,通过实时传递不同的参数来创建一个新Function。因为编译器没法知道最终代码会是什么样 子的,所以new Function(…)的内容不会被编译。那这有什么好处呢?嗯,举个例子,如果你需要用户能够创建他们自己的函数的时候这个功能也许很有用,比如在 游戏里。我们也许需要允许用户添加“行为”给一个“player”。但是,再说一次,一般情况下,我们应该避免使用这种形式,除非有一个特殊的目的。

函数:对象

函数是javascript中的一种特殊形式的对象。它是第一个[b〕类数据类型(class data type)。这意味着我们能够给它增加属性。这里有一些需要注意的有趣观点:

对象的创建

就像刚才提及的,当我们定义一个函数时,javascript实际上在后台为你创建了一个对象。这个对象的名称就是函数名本身。这个对象的类型是function。在下面的例子,我们也许不会意识到这一点,但我们实际上已经创建了一个对象:它叫做Ball。

Example 1

function Ball() // 也许看起来有点奇怪,但是这个声明
{ // 创建了一个叫做Ball的对象
i=1;
}
alert(typeof Ball); // 结果 "function"

我们甚至能将这个对象的内容打印出来而且它会输出这个函数的实际代码,Example2: 点击 alert(Ball);来看看Ball的内容。

属性的添加

我们能够添加给Object添加属性,包括对象function。因为定义一个函数的实质是创建一个对象。我们能够“暗地里”给函数添加属性。比如,我们这里定义了函数Ball,并添加属性callsign。

function Ball() // 也许看起来有点奇怪,但是这个声明
{ // 创建了一个叫做Ball的对象,而且你能够
} // 引用它或者象下面那样给它增加属性
Ball.callsign="The Ball"; // 给Ball增加属性
alert(Ball.callsign); // 输出 "The Ball"

指针

因为function是一个对象,我们能够为一个function分配一个指针。如下例,变量ptr指向了对象myFunction。

function myFunction(message)
{
alert(message);
}
var ptr=myFunction; // ptr指向了myFunction
ptr("hello"); // 这句会执行myFunction:输出"hello"

我们能够运行这个函数,就好像这个函数名已经被指针名代替了一样。所以在上面,这行ptr("hello"); 和myFunction("hello");的意义是一样的。

指向函数的指针在面向对象编程中相当有用。例如:当我们有多个对象指向同一个函数的时候(如下):

Example 4A

function sayName(name)
{
alert(name);
}
var object1=new Object(); // 创建三个对象
var object2=new Object();
var object3=new Object();
object1.sayMyName=sayName; // 将这个函数指派给所有对象
object2.sayMyName=sayName;
object3.sayMyName=sayName;

object1.sayMyName("object1"); // 输出 "object1"
object2.sayMyName("object2"); // 输出 "object2"
object3.sayMyName("object3"); // 输出 "object3"

http://www.blueidea.com/articleimg/2006/07/3823/01.gif

因为只有指针被保存(而不是函数本身),当我们改变函数对象自身的时候,所有指向那个函数的指针都会发生变化。我们能够在底下看到:

Example 5:

function myFunction()
{
alert(myFunction.message);
}
myFunction.message="old";
var ptr1=myFunction; // ptr1 指向 myFunction
var ptr2=myFunction; // ptr2 也指向 myFunction

ptr1(); // 输出 "old"
ptr2(); // 输出 "old"

myFunction.message="new";

ptr1(); // 输出 "new"
ptr2(); // 输出 "new"

指针的指向

我们能够在一个函数创建之后重新分配它,但是我们需要指向函数对象本身,而不是指向它的指针。在下例中,我将改变myfunction()的内容。

Example 6:

function myFunction()
{
alert("Old");
}
myFunction(); // 输出 "Old"
myFunction=function()
{
alert("New");
};
myFunction(); // 输出 "New"

旧函数哪里去了??被抛弃了。

如果我们需要保留它,我们可以在改变它之前给它分配一个指针。

http://www.blueidea.com/articleimg/2006/07/3823/02.gif

Example 6A:

function myFunction()
{
alert("Old");
}
var savedFuncion=myFunction;
myFunction=function()
{
alert("New");
};
myFunction(); // 输出 "New"
savedFuncion(); // 输出 "Old"

http://www.blueidea.com/articleimg/2006/07/3823/03.gif

不过要小心,象下面这样的例子并不会有作用,因为是创建了另一个叫做myFunctionPtr的函数而不是修改它。

Example 6B:

function myFunction()
{
alert("Old");
}
var savedFunc=myFunction;
savedFunc=function()
{
alert("New");
};
myFunction(); // 输出 "Old"
savedFunc(); // 输出 "New"

内嵌函数

我们还能够在一个函数中嵌套一个函数。下例,我有一个叫做getHalfOf的函数,而在它里面,我有另一个叫做calculate的函数。

Example 7

function getHalfOf(num1, num2, num3)
{
function calculate(number)
{
return number/2;
}

var result="";
result+=calculate(num1)+" ";
result+=calculate(num2)+" ";
result+=calculate(num3);
}
var resultString=getHalfOf(10,20,30);
alert(resultString); // 输出 "5 10 15"

你只能在内部调用嵌套的函数。就是说,你不能这么调用:getHalfOf.calculate(10),因为calculate只有当外部函数 (getHalfOf())在运行的时候才会存在。这和我们前面的讨论一致(函数会被编译,但只有当你去调用它的时候才会执行)。

调用哪个函数?

你也许正在想命名冲突的问题。比如,下面哪一个叫做calculate的函数会被调用?

Example 8

function calculate(number)
{
return number/3;
}

function getHalfOf(num1, num2, num3)
{
function calculate(number)
{
return number/2;
}

var result="";
result+=calculate(num1)+" ";
result+=calculate(num2)+" ";
result+=calculate(num3);
}
var resultString=getHalfOf(10,20,30);
alert(resultString); // 输出 "5 10 15"

在这个例子中,编译器会首先搜索局部内存地址,所以它会使用内嵌的calculate函数。如果我们删除了这个内嵌(局部)的calculate函数,这个代码会使用全局的calculate函数。

函数:数据类型及构造函数

让 我们来看看函数的另一个特殊功能--这让它和其它对象类型截然不同。一个函数能够用来作为一个数据类型的蓝图。这个特性通常被用在面向对象编程中来模拟用 户自定义数据类型(user defined data type)。使用用户自定义数据类型创建的对象通常被成为用户自定义对象(user defined object)。

数据类型

在定义了一个函数之后,我们也同时创建了一个新的数据类型。这个数据类型能够用来创建一个新对象。下例,我创建了一个叫做Ball的新数据类型。

Example DT1

function Ball()
{
}
var ball0=new Ball(); // ball0 现在指向一个新对象

alert(ball0); // 输出 "Object",因为 ball0 现在是一个对象

这样看来,ball0=new Ball()作了什么?new关键字创建了一个类型是Object的新对象(叫做ball0)。然后它会执行Ball(),并将这个引用传给ball0 (用于调用对象)。下面,你会看到这条消息:“creating new Ball”,如果Ball()实际上被运行的话。

Example DT2

function Ball(message)
{
alert(message);
}
var ball0=new Ball("creating new Ball"); // 创建对象并输出消息
ball0.name="ball-0"; // ball0现在有一个属性:name
alert(ball0.name); // 输出 "ball-0"

我们可以把上面这段代码的第6行看做是底下的代码6-8行的一个简写:

function Ball(message)
{
alert(message);
}
var ball0=new Object();
ball0.construct=Ball;
ball0.construct("creating new ball"); // 执行 ball0.Ball("creating..");
ball0.name="ball-0";
alert(ball0.name);

这行代码ball0.construct=Ball和Example 4中的ptr=myFunction语法一致。

如果你还是不明白这行的含义那就回过头再复习一下Example 4。注意:你也许考虑直接运行ball0.Ball("…"),但是它不会起作用的,因为ball0并没有一个叫做Ball("…")的属性,并且它也不知道你究竟想作些什么。

添加属性

当 我们象上面那样使用关键字new创建一个对象的时候,一个新的Object被创建了。我们可以在创建之后给这个对象添加属性(就好像我在上面那样添加属性 name。而接下来的问题就是如果我们创建了这个对象的另外一个实例,我们得象下面那样再次给这个新对象添加这个属性。)

Example DT3 (creates 3 ball objects)

function Ball()
{
}
var ball0=new Ball(); // ball0 现在指向了类型Ball的一个新实例
ball0.name="ball-0"; // ball0 现在有一个属性"name"

var ball1=new Ball();
ball1.name="ball-1";

var ball2=new Ball();

alert(ball0.name); // 输出 "ball-0"
alert(ball1.name); // 输出 "ball-1"
alert(ball2.name); // 哦,我忘记给ball2添加“name”了!

我忘记给ball2添加属性name了,如果在正式的程序中这也许会引发问题。有什么好办法可以自动增加属性呢?嗯,有一个:使用this关键 字。this这个词在function中有特别的意义。它指向了调用函数的那个对象。让我们看看下面的另一个示例,这时候我们在构造函数中添加上这些属 性:

Example DT4

function Ball(message, specifiedName)
{
alert(message);
this.name=specifiedName;
}
var ball0=new Ball("creating new Ball", "Soccer Ball");
alert(ball0.name); // prints "Soccer Ball"

请记住:是new关键字最终使得构造函数被执行。在这个例子中,它将会运行Ball("creating new Ball", "Soccer Ball");而关键字this将指向ball0。
因此,这行:this.name=specifiedName变成了ball0.name="Soccer Ball"。
它主要是说:给ball0添加属性name,属性值是Soccer Ball。
我 们现在只是添加了一个name属性给ball0,看起来和上一个例子中所做的很象,但却是一个更好更具扩展性的方法。现在,我们可以随心所欲的创建许多带 有属性的ball而无需我们手动添加它们。而且,人们也希望创建的Ball对象能够清晰的看懂它的构造函数并且能够轻松找出Ball的所有属性。让我们添 加更多属性到Ball里。

Example DT5

function Ball(color, specifiedName, owner, weight)
{
this.name=specifiedName;
this.color=color;
this.owner=owner;
this.weight=weigth;
}
var ball0=new Ball("black/white", "Soccer Ball", "John", 20);
var ball1=new Ball("gray", "Bowling Ball", "John", 30);
var ball2=new Ball("yellow", "Golf Ball", "John", 55);
var balloon=new Ball("red", "Balloon", "Pete", 10);

alert(ball0.name); // 输出 "Soccer Ball"
alert(balloon.name); // 输出 "Balloon"
alert(ball2.weight); // 输出 "55"

嘿!使用面向对象术语,你能够说Ball是一个拥有如下属性的对象类型:name, color, owner, weight。

将对象赋给属性

我们并没被限制只能添加形如字符串或者数字之类的简单数据类型作为属性。我们也能够将对象赋给属性。下面,supervisor是Employee的一个属性.

Example DT6

function Employee(name, salary, mySupervisor)
{
this.name=name;
this.salary=salary;
this.supervisor=mySupervisor;
}
var boss=new Employee("John", 200);

var manager=new Employee("Joan", 50, boss);
var teamLeader=new Employee("Rose", 50, boss);

alert(manager.supervisor.name+" is the supervisor of "+manager.name);
alert(manager.name+"’s supervisor is "+manager.supervisor.name);

会输出什么呢?

就像你在上面这个例子中看到的那样,manager和teamLeader都有一个supervisor属性,而这个属性是类型Employee的一个对象。

将函数作为属性

任何类型的对象都可以作为一个属性,回忆一下前面的Example 4(不是Example DT4),函数也是一个对象。所以你可以让一个函数作为一个对象的一个属性。下面,我将添加两个函数getSalary和addSalary。

Example DT7

function Employee(name, salary)
{
this.name=name;
this.salary=salary;

this.addSalary=addSalaryFunction;

this.getSalary=function()
{
return this.salary;
};
}
function addSalaryFunction(addition)
{
this.salary=this.salary+addition;
}

var boss=new Employee("John", 200000);
boss.addSalary(10000); // boss 长了 10K 工资……为什么老板工资可以长这么多:'(
alert(boss.getSalary()); // 输出 210K……为什么默认工资也那么高……:'(

addSalary和getSalary演示了几种将函数赋给属性的不同方法。如果你记得我们最开始的讨论;我讨论了三种声明函数的不同方式。所有那些在这里都是适用的,但是上面展示的两个最常用。

让我们看看有什么不同。下面,注意一下9-12行的代码。当这部分代码执行的时候,函数getSalary被声明。如前面数次提到的,一个函数声明的结果是一个对象被创建。所以这时候boss被创建(接下来的第19行),而boss里有一个getSalary属性。

function Employee(name, salary)
{
this.name=name;
this.salary=salary;

this.addSalary=addSalaryFunction;

this.getSalary=function()
{
return this.salary;
};
}
function addSalaryFunction(addition)
{
this.salary=this.salary+addition;
}

var boss=new Employee("John", 200000);
var boss2=new Employee("Joan", 200000);
var boss3=new Employee("Kim", 200000);

当你创建这个对象的更多实例时(boss2和boss3),每一个实例都有一份getSalary代码的单独拷贝;而与此相反,addSalary则指向了同一个地方(即addSalaryFunction)。

http://www.blueidea.com/articleimg/2006/07/3823/04.gif

看看下面的代码来理解一下上面所描述的内容。

Example DT8

function Employee(name, salary)
{
this.name=name;
this.salary=salary;

this.addSalary=addSalaryFunction;
this.getSalary=function()
{
return this.salary;
};
}
function addSalaryFunction(addition)
{
this.salary=this.salary+addition;
}

var boss1=new Employee("John", 200000);
var boss2=new Employee("Joan", 200000);

// 给getSalary函数对象添加属性
boss1.getSalary.owner="boss1";
boss2.getSalary.owner="boss2";
alert(boss1.getSalary.owner); // 输出 "boss1"
alert(boss2.getSalary.owner); // 输出 "boss2"
// 如果两个对象指向同一个函数对象,那么
// 上面两个输出都应该是“boss2”。

// 给addSalary函数对象添加属性
boss1.addSalary.owner="boss1";
boss1.addSalary.owner="boss2";
alert(boss1.addSalary.owner); // 输出 "boss2"
alert(boss2.addSalary.owner); // 输出 "boss2"
// 因为两个对象都指向同一个函数,(子乌注:原文写are not pointing to the same function,疑为笔误)
// 当修改其中一个的时候,会影响所有的实例(所以两个都输出“boss2”).

也许不是重要的事情,但这里有一些关于运行类似上面的getSalary的内嵌函数的结论: 1) 需要更多的存储空间来存储对象(因为每一个对象实例都会有它自己的getSalary代码拷贝);2) javascript需要更多时间来构造这个对象。

让我们重新写这个示例来让它更有效率些。

Example DT9

function Employee(name, salary)
{
this.name=name;
this.salary=salary;

this.addSalary=addSalaryFunction;
this.getSalary=getSalaryFunction;
}
function getSalaryFunction()
{
return this.salary;
}

function addSalaryFunction(addition)
{
this.salary=this.salary+addition;
}

看这儿,两个函数都指向同一个地方,这将会节约空间和缩短构造时间(特别是当你有一大堆内嵌函数在一个构造函数的时候)。这里有另外一个函数的功能能够来提升这个设计,它叫做prototype,而我们将在下一节讨论它。

函数:原型

每一个构造函数都有一个属性叫做原型(prototype,下面都不再翻译,使用其原文)。这个属性非常有用:为一个特定类声明通用的变量或者函数。

prototype的定义

你不需要显式地声明一个prototype属性,因为在每一个构造函数中都有它的存在。你可以看看下面的例子:

Example PT1

function Test()
{
}
alert(Test.prototype); // 输出 "Object"

给prototype添加属性

就如你在上面所看到的,prototype是一个对象,因此,你能够给它添加属性。你添加给prototype的属性将会成为使用这个构造函数创建的对象的通用属性。

例如,我下面有一个数据类型Fish,我想让所有的鱼都有这些属性:livesIn="water"和price=20;为了实现这个,我可以给构造函数Fish的prototype添加那些属性。

Example PT2

function Fish(name, color)
{
this.name=name;
this.color=color;
}
Fish.prototype.livesIn="water";
Fish.prototype.price=20;

接下来让我们作几条鱼:

var fish1=new Fish("mackarel", "gray");
var fish2=new Fish("goldfish", "orange");
var fish3=new Fish("salmon", "white");

再来看看鱼都有哪些属性:

for (int i=1; i<=3; i++)
{
var fish=eval("fish"+i); // 我只是取得指向这条鱼的指针
alert(fish.name+","+fish.color+","+fish.livesIn+","+fish.price);
}

输出应该是:

"mackarel, gray, water, 20"
"goldfish, orange, water, 20"
"salmon, white water, 20"

你看到所有的鱼都有属性livesIn和price,我们甚至都没有为每一条不同的鱼特别声明这些属性。这时因为当一个对象被创建时,这个构造函 数将会把它的属性prototype赋给新对象的内部属性__proto__。这个__proto__被这个对象用来查找它的属性。

你也可以通过prototype来给所有对象添加共用的函数。这有一个好处:你不需要每次在构造一个对象的时候创建并初始化这个函数。为了解释这一点,让我们重新来看Example DT9并使用prototype来重写它:

用prototype给对象添加函数

Example PT3

function Employee(name, salary)
{
this.name=name;
this.salary=salary;
}
Employee.prototype.getSalary=function getSalaryFunction()
{
return this.salary;
}

Employee.prototype.addSalary=function addSalaryFunction(addition)
{
this.salary=this.salary+addition;
}

我们可以象通常那样创建对象:

var boss1=new Employee("Joan", 200000);
var boss2=new Employee("Kim", 100000);
var boss3=new Employee("Sam", 150000);

并验证它:

alert(boss1.getSalary()); // 输出 200000
alert(boss2.getSalary()); // 输出 100000
alert(boss3.getSalary()); // 输出 150000

这里有一个图示来说明prototype是如何工作的。这个对象的每一个实例(boss1, boss2, boss3)都有一个内部属性叫做__proto__,这个属性指向了它的构造器(Employee)的属性prototype。当你执行 getSalary或者addSalary的时候,这个对象会在它的__proto__找到并执行这个代码。注意这点:这里并没有代码的复制(和 Example DT8的图表作一下对比)。

http://www.blueidea.com/articleimg/2006/07/3823/05.gif

Read: 929

自动创建selectbox的多级联动下拉框

by GiK

这里面用到了我前面写的那个数据存取对象

<script type="text/javascript">
function $(o) {
return document.getElementById(o);
}

game = {
code : new String(),
lv    : new Number(),
set   : function(code){
    this.code = code;
    switch(code.substr(0,3)){
        case ‘001’:
            game.lv = 3;
            break;
        case ‘002’:
            game.lv = 2;;
            break;
    }
}
}
<?php
$code = array_flip(LJSConfig::$game_level);
?>
game.set(‘<?= array_shift($code) ?>’);
var server_code = "001002003001";
comboBox = {
    target   : $(‘catbox’),//where to add the selectbox
    sels : new Object(),
   
    init : function(){
    this.sels.length = new Number(0);
    for(var i=0; i<4; i++){
        this.sels[i] = document.createElement("select");
        this.sels[i].id = "sl" + i;//set element id
        if(i == 0){
        this.sels[i].onchange = function(){
          comboBox.selectChange(this.id.charAt(this.id.length – 1));
          comboBox.hide(game.lv);
        }
        } else {
        this.sels[i].onchange = function(){
          comboBox.selectChange(this.id.charAt(this.id.length – 1));
        }
        }
        this.sels.length++;
        this.target.appendChild(this.sels[i]);
    }
    this.createOptions("0",this.sels[0]);
    this.selectChange(0,1);
    },
   
    selectChange : function (level)
{
    level = new Number(level); //change type to number
    var selectObj = this.sels[level];
    if(level == 0){
      game.set(selectObj.value);
    }
    if( level >= game.lv )
    {return;}
   
    var nextSelectObj = this.sels[level+1];
    nextSelectObj.options.length = 0;//clear next select box
    nextSelectObj.style.display = "";
    var currentCode = selectObj.value;
    this.createOptions(currentCode,nextSelectObj);
   
    if( ( level + 1 ) < game.lv )
      comboBox.selectChange( level + 1 ,nextSelectObj);
},
   
    createOptions : function( code, sel){
    var datas = code == "0" ? cat.getlevel(1) : cat.getsub( code );
    for(var item in datas)
    {
      if(item != ‘len’)
      {
        var op = document.createElement("option");
        op.text = datas[item].name;
        op.value = item;
        if(server_code && item == server_code.substr(0,item.length)){
          op.selected = ‘true’;
        }
        sel.options.add( op );
      }
    }
    },
   
    hide : function(level){
    for(var i = this.sels.length; i > level; i–){
        if(this.sels[i])
         this.sels[i].style.display = "none";
    }
    }
}
comboBox.init();
</script>

Read: 940

[转] JQUERY中简单使用AJAX

JQuery第14天里说到了Ajax:同时给了几个读取数据的例子,jquery没有Prototypre的严谨与规范,但利于普通开发的快速上手,也算是各有千秋吧,要是结合两者,我想更是事倍功倍,虽然说两者加起来快近80K,但这个年代带宽应该不是问题了,AJAXModule

I’m trying something a little different today. It took me a while to figure out why I couldn’t open a free account with YouTube for about 3 weeks but now that it’s all straightened out I thought I’d upload a video where I walk you through some of the basic ways you could use jquery to add AJAX functionality to your site.

The video is short because my understanding of YouTube is that I have to limit the file to 10 minutes. Not everything I’ve said in the tutorial is 100% correct. There are minor mistakes such as the part where I call cgi a “server side script” when it would be more accurate to say “server side language”.

Cut me some slack… woodya?英文全文

Demos » AJAX Plugin

Loaded from an HTML File

Code:

$("div#html").load("ajax-test.html");
Loaded from an XML File

Code:

$.get("ajax-test.xml",function(xml){
var text = $("title",xml).text();
$("div#xml").html("<h1>"+text+"</h1>");
});
Loaded from a Dynamic HTML File

Code:

$("div#dhtml").load("html.cgi",{name:"John"});
Loaded from a Dynamic XML File

Code:

$.post("XML.cgi",{
name: "John"
},function(xml){
var text = $("title",xml).text();
$("div#dxml").html("<h2>"+text+"</h2>");
});
Loaded from a Text File

Code:

$.get("ajax-test.txt",function(txt){
$("div#txt").html("<h1>"+txt+"</h1>");
});

Read: 191

【原创】一个简单数据存取的对象

最近刚好要做个有联动的分类选择,就写了下面这个东西,令人郁闷的javascript

cat = {
data : function (code,name,price,discount){
    this.code = code;
    this.name = name;
    this.price = price;
    this.discount = discount;
},
get : function(code){
    if (!code || code.length % 3 != 0 || code.length < 3)
    {
      return false;
    }
    if(cat.getlevel(code.length / 3)[code]){
      return cat.getlevel(code.length / 3)[code];
    } else {
      return false;
    }
},

add : function (code,name,price,discount){
    if(!cat["lv_" + code.length / 3 ]){
      cat["lv_" + code.length / 3 ]     = new Object();
      cat["lv_" + code.length / 3 ].len = 0;
    }
    cat["lv_" + code.length / 3 ][code] = new cat.data(code,name,price,discount );
    cat["lv_" + code.length / 3 ].len++;
},

getlevel : function (level){
    if(cat["lv_" + level]){
      return cat["lv_" + level]
    } else {
      return false;
    }
},

getsub : function( code ){
    code = new String(code);
    datas = new Object();
    level = code.length / 3 + 1;
    sub   = cat.getlevel(level);
    count = 0;
    for(var item in sub){
      if(sub[item].code){
        if(sub[item].code.substr(0,code.length) == code ){
          datas[sub[item].code] = sub[item];
          count++;
        }
      }
    }
    datas.len = count;
    if(count == 0){return false;}
    return datas;
}
}

附部份测试数据

cat.add(‘001′,’魔兽世界’);
cat.add(‘001001′,’一区’);
cat.add(‘001001001′,’伊瑟拉’);
cat.add(‘001001001001′,’联盟’,’0.2617′,’1.00′);
cat.add(‘001001001002′,’部落’,’0.6229′,’1.00′);
cat.add(‘001001002′,’卡德罗斯’);
cat.add(‘001001002001′,’联盟’,’0.3297′,’1.00′);
cat.add(‘001001002002′,’部落’,’0.7794′,’1.00′);
cat.add(‘001001003′,’卡扎克’);
cat.add(‘001001003001′,’联盟’,’0.9082′,’1.00′);
cat.add(‘001001003002′,’部落’,’0.2026′,’1.00′);
cat.add(‘001001004′,’回音山’);
cat.add(‘001001004001′,’联盟’,’0.2884′,’1.00′);
cat.add(‘001001004002′,’部落’,’0.8341′,’1.00′);
cat.add(‘001001005′,’国王之谷’);

还是感觉好麻烦 做联动的时候还是很郁闷啊····没有什么好办法

我太笨了

Read: 808