函数、方法、对象和类
函数是JavaScript语言中的“一等公民”,函数是JavaScript编程里非常重要的一个概念。当使用JavaScript定义一个函数后,实际上可以得到如下4项。
➢ 函数:就像Java的方法一样,这个函数可以被调用。
➢ 对象:定义一个函数时,系统也会创建一个对象,该对象是Function类的实例。
➢ 方法:定义一个函数时,该函数通常都会附加给某个对象,作为该对象的方法。
➢ 类:在定义函数的同时,也得到了一个与函数同名的类。
函数可作为函数被调用,这在前面已经见到过很多例子,此处不再赘述。
函数不仅可作为函数使用,函数本身也是一个对象,是Function类的实例
例子
<script type="text/javascript">
// 定义一个函数,并将它赋给hello变量
var hello = function(name)
{
return name + ",您好";
}
// 判断函数是否为Function的实例、是否为Object的实例
alert("hello是否为Function对象:" + (hello instanceof Function)
+ "\nhello是否为Object对象:" + (hello instanceof Object));
alert(hello);
</script>
注意
如果直接输出函数本身,例如上面程序中的alert(hello);将会输出函数的源代码。
JavaScript的函数不仅是一个函数,更是一个类,在我们定义一个JavaScript函数的同时,也得到了一个与该函数同名的类,该函数也是该类唯一的构造器。
因此,当我们定义一个函数后,有如下两种方式来调用函数。
➢ 直接调用函数:直接调用函数总是返回该函数体内最后一条return语句的返回值;如果该函数体内不包含return语句,则直接调用函数没有任何返回值。
➢ 使用new关键字调用函数:通过这种方式调用总有一个返回值,返回值就是一个JavaScript对象。
例子
<script type="text/javascript">
// 定义一个函数
var test = function(name)
{
return "你好," + name ;
}
// 直接调用函数
var rval = test('leegang');
// 将函数作为类的构造器
var obj = new test('leegang');
alert(rval + "\n" + obj);
</script>
下面程序定义了一个Person函数,也就是定义了一个Person类,该Person函数也会作为Person类唯一的构造器。定义Person函数时希望为该函数定义一个方法,程序如下。
例子
<script type="text/javascript">
// 定义了一个函数,该函数也是一个类
function Person(name , age)
{
// 将参数name的值赋给name属性
this.name = name;
// 将参数age的值赋给age属性
this.age = age;
// 为函数分配info方法,使用匿名函数来定义方法
this.info = function()
{
document.writeln("我的名字是:" + this.name + "<br />");
document.writeln("我的年纪是:" + this.age + "<br />");
};
}
// 创建p对象
var p = new Person('yeeku' , 29);
// 执行info方法
p.info();
</script>
上面程序中使用了this关键字,被this关键字修饰的变量不再是局部变量,它是该函数的实例属性。
正如从上面代码中看到的,JavaScript定义的函数可以“附加”到某个对象上,作为该对象的方法。实际上,如果没有明确指定将函数“附加”到哪个对象上,该函数将“附加”到window对象上,作为window对象的方法。
例子
<script type="text/javascript">
// 直接定义一个函数,并未指定该函数属于哪个对象。
// 该对象默认属于window对象
var hello = function(name)
{
document.write(name + ", 您好<br />");
}
// 以window作为调用者,调用hello函数
window.hello("孙悟空");
// 定义一个对象
var p = {
// 定义一个函数,该函数属于p对象。
walk: function()
{
for(var i = 0 ; i < 2 ; i++)
{
document.write("慢慢地走...");
}
}
}
p.walk();
</script>
函数的实例属性和类属性
由于JavaScript函数不仅仅是一个函数,而且是一个类,该函数还是此类唯一的构造器,只要在调用函数时使用new关键字,就可返回一个Object,这个Object不是函数的返回值,而是函数本身产生的对象。因此在JavaScript中定义的变量不仅有局部变量,还有实例属性和类属性两种。根据函数中声明变量的方式,函数中的变量有3种。
➢ 局部变量:在函数中以普通方式声明的变量,包括以var或不加任何前缀声明的变量。
➢ 实例属性:在函数中以this前缀修饰的变量。
➢ 类属性:在函数中以函数名前缀修饰的变量。
前面已经对局部变量作了介绍,局部变量是只能在函数里访问的变量。实例属性和类属性则是面向对象的概念:实例属性是属于单个对象的,因此必须通过对象来访问;类属性是属于整个类(也就是函数)本身的,因此必须通过类(也就是函数)来访问。
同一个类(也就是函数)只占用一块内存,因此每个类属性将只占用一块内存;同一个类(也就是函数)每创建一个对象,系统将会为该对象的实例属性分配一块内存。
例子
<script type="text/javascript">
// 定义函数Person
function Person(national, age)
{
// this修饰的变量为实例属性
this.age = age;
// Person修饰的变量为类属性
Person.national =national;
// 以var定义的变量为局部变量
var bb = 0;
}
// 创建Person的第一个对象p1。国籍为中国,年纪为29
var p1 = new Person('中国' , 29);
document.writeln("创建第一个Person对象<br />");
// 输出第一个对象p1的年纪和国籍
document.writeln("p1的age属性为" + p1.age + "<br />");
document.writeln("p1的national属性为" + p1.national + "<br />");
document.writeln("通过Person访问静态national属性为"
+ Person.national + "<br />");
// 输出bb属性
document.writeln("p1的bb属性为" + p1.bb + "<br /><hr />");
// 创建Person的第二个对象p2
var p2 = new Person('美国' , 32);
document.writeln("创建两个Person对象之后<br />");
// 再次输出p1的年纪和国籍
document.writeln("p1的age属性为" + p1.age + "<br />");
document.writeln("p1的national属性为" + p1.national + "<br />");
// 输出p2的年纪和国籍
document.writeln("p2的age属性为" + p2.age + "<br />");
document.writeln("p2的national属性为" + p2.national + "<br />");
// 通过类名访问类属性
document.writeln("通过Person访问静态national属性为"
+ Person.national + "<br />");
</script>
Person函数的age属性为实例属性,因而每个实例的age属性都可以完全不同,程序应通过Person对象来访问age属性;national属性为类属性,该属性完全属于Person类,因此必须通过Person类来访问national属性,Person对象并没有national属性,所以通过Person对象访问该属性将返回undefined;而bb则是Person的局部变量,在Person函数以外无法访问该变量。
动态增加对象属性
值得指出的是,JavaScript与Java不一样,它是一种动态语言,它允许随时为对象增加属性和方法,当我们直接为对象的某个属性赋值时,即可视为给对象增加属性。
例子
<script type="text/javascript">
function Student(grade , subject)
{
// 定义一个grade实例属性,
// 将grade形参的值赋值给该实例属性。
this.grade = grade;
// 定义一个subject静态属性,
// 将subject形参的值赋值给该静态属性。
Student.subject = subject;
}
s1 = new Student(5, 'Java');
with(document)
{
writeln('s1的grade属性:' + s1.grade + "<br />");
writeln('s1的subject属性:' + s1.subject + "<br />");
writeln('Student的subject属性:' + Student.subject + "<br />");
}
// 为s1对象的subject属性赋值,即为它增加一个subject属性
s1.subject = 'Ruby';
with(document)
{
writeln('<hr />为s1的subject属性赋值后<br />');
writeln('s1的subject属性:' + s1.subject + "<br />");
writeln('Student的subject属性:' + Student.subject + "<br />");
}
</script>
上面程序为s1的subject属性赋值,赋值后该subject属性值为'Ruby',但这并不是修改Student的subject属性,这行代码仅仅是为s1对象动态增加了一个subject属性。
调用函数的3种方式
定义一个函数之后,JavaScript提供了3种调用函数的方式。
直接调用函数
直接调用函数是最常见、最普通的方式。这种方式直接以函数附加的对象作为调用者,在函数后括号内传入参数来调用函数。这种方式是前面最常见的调用方式。当程序使用window对象来调用方法时,可以省略方法前面的window调用者。
以call()方法调用函数
直接调用函数的方式简单、易用,但这种调用方式不够灵活。有些时候调用函数时需要动态地传入一个函数引用,此时为了动态地调用函数,就需要使用call方法来调用函数了。 假如我们需要定义一个形如each(array, fn)的函数,这个函数可以自动迭代处理array数组元素,而fn函数则负责对数组元素进行处理——此时需要在each函数中调用fn函数,但目前fn函数并未确定,因此无法采用直接调用的方式来调用fn函数,需要通过call()方法来调用函数。
例子
<script type="text/javascript">
// 定义一个each函数
var each = function(array , fn)
{
for(var index in array)
{
// 以window为调用者来调用fn函数,
// index、array[index]是传给fn函数的参数
fn.call(null , index , array[index]);
}
}
// 调用each函数,第一个参数是数组,第二个参数是函数
each([4, 20 , 3] , function(index , ele)
{
document.write("第" + index + "个元素是:" + ele + "<br />");
});
</script>
上面程序中粗体字代码示范了通过call()动态地调用函数,从调用语法来看,不难发现通过call()调用函数的语法格式为:
函数引用.call(调用者,参数1,参数2...)
由此可以得到直接调用函数与通过call()调用函数的关系如下:
调用者.函数(参数1,参数2...)=函数引用.call(调用者,参数1,参数2...)
以apply()方法调用函数
apply()方法与call()方法的功能基本相似,它们都可以动态地调用函数。apply()与call()的区别如下:
➢ 通过call()调用函数时,必须在括号中详细地列出每个参数。
➢ 通过apply()动态地调用函数时,可以在括号中以arguments来代表所有参数。
例子
<script type="text/javascript">
// 定义一个函数
var myfun = function(a , b)
{
alert("a的值是:" + a
+ "\nb的值是:" + b);
}
// 以call()方法动态地调用函数
myfun.call(window , 12 , 23);
var example = function(num1 , num2)
{
// 直接用arguments代表调用example函数时传入的所有参数
myfun.apply(this, arguments);
}
example(20 , 40);
// 为apply()动态调用传入数组
myfun.apply(window , [12 , 23]);
</script>
对比上面两行粗体字代码不难发现,当通过call()动态地调用方法时,需要为被调用方法逐个地传入参数;当通过apply()动态地调用方法时,能以arguments一次性地传入多个参数。
需要指出的是,arguments可代表调用当前函数时传入的所有参数,因此arguments相当于一个数组,所以上面程序使用数组代替了arguments。
注:本博客内容节选自李刚编著的疯狂HTML 5/CSS 3/JavaScript讲义 ,详细内容请参阅书籍。