或许你会奇怪,面向对象的程序设计为什么从数组开始讲起?这是因为……其间的种种关系吧……嘿嘿,这里先卖个关子,先来看看我们熟悉的数组在JavaScript里面是什么样子的。
1. 创建数组
在JavaScript中有很多创建数组的方法。比如使用Array函数。不过这不是现在我们要讲述的。现在我们使用简单的方括号“[]”的办法来创建数组。
- var objAyyar = [];
- var objAyyar = [2];
- var objAyyar = ["a", "b", "c"];
- var objAyyar = [new Date(), 123, "abc"];
var objAyyar = []; // 1
var objAyyar = [2]; // 2
var objAyyar = ["a", "b", "c"]; // 3
var objAyyar = [new Date(), 123, "abc"]; // 4
这里有四个创建数组的语句。下面来一一解释一下:
第一句,创建一个空的数组;
第二句,创建一个数组,数组元素只有一个2;
第三句,创建一个数组,数组的元素分别初始化为"a", "b", "c";
第四句,创建一个数组,其中第一个元素为一个Date类型的对象,第二个元素是数字123,第三个元素是字符串"abc"。
回顾一下,在Java或者C++语言中,数组是具有相同的数据类型的元素的集合。比如使用Java语言的下面语句
- int[] array = new int[10];
int[] array = new int[10];
将创建一个能放入10个int类型的元素的数组。数组和其他类型的集合的一个很大的区别是,数组里面只能存放相同数据类型的元素(使用泛型的集合除外)。但是,像上面的第四句,JavaScript的数组怎么能存放不同类型的元素呢?这是因为,JavaScript是弱类型的语言,没有很大的数据类型的差别,所以数组的元素可以放入不同的类型。
2. 操作数组
数组是元素的有序集合。数组中的元素是有序的,这就可以通过下标访问到数组中的每个元素。而且,JavaScript的数组相当的灵活。当你习惯了Java或者C++的数组之后,或许并不习惯JavaScript的数组。在一定程度上,这种数组可以称为一种动态数组。看这样一段代码:
- var arr = [1, 2, 3, 4, 5];
- alert(arr.length);
- alert(arr[3]);
- arr[9] = 10;
- alert(arr[7]);
- alert(arr.length);
var arr = [1, 2, 3, 4, 5];
alert(arr.length); // 数组长度为5
alert(arr[3]); // arr[3] = 4
arr[9] = 10; // 改变了数组的长度为10
alert(arr[7]);
alert(arr.length);
首先创建一个数组arr,可以看到它的长度是5,arr[3]是4。这些都是很常见的。那么第三句,arr[9] = 10;就有点意思了——在Java中,这句操作将导致数组越界的异常,在C++中,这种操作是极其危险的。但是在JavaScript中,这样的操作是正常的——你可以动态的改变数组的大小!虽然你在创建数组时并没有这么大的长度,但是,你可以在创建之后指定它!这时的arr.length已经自动的变成10了。那么,arr[7]又会是什么呢?经过运行代码我们会看到,arr[7]是undefined。也就是说,虽然arr[9]有了值,但是其中从arr[5]到arr[8]这几个元素都是未定义的,也就是undefined。如果你问JavaScript怎么不给个初始值?唉,饶了它吧!JavaScript并不知道你想要它初始化成什么值啊!万一错了呢?干脆还是别了吧……
- var arr = [1, 2, 3, 4, 5];
- alert(arr.length);
- delete arr[3];
- alert(arr.length);
- alert(arr[3]);
- arr.length = 4;
- alert(arr[4]);
- arr.length = 10;
- alert(arr[6]);
var arr = [1, 2, 3, 4, 5];
alert(arr.length); // 数组长度为5
delete arr[3]; // 删掉第4个元素
alert(arr.length); // 长度不变
alert(arr[3]); // arr[3] = undefined
arr.length = 4; // 缩短长度
alert(arr[4]);
arr.length = 10; // 增加长度
alert(arr[6]);
上面的代码也很有意思:使用delete操作符可以删除任意一个数组元素,但是长度并不改变。
Java的数组也有一个length属性,用来显示数组的长度。JavaScript的数组也有这个属性。但是,和Java不同的是,后者的length属性并不是只读的!你可以任意的设置数组的length属性的值,无论是扩大还是缩小!只是如上面的代码所示,改变了length之后,越界的元素或者以前没有定义的元素都将成为undefined。也就是说,当length大于原始长度时,从原长度到length - 1的元素都将成为undefined;当length小于原始长度时,从length到原长度 - 1的元素也都会清除设置为undefined。
3. 非数字的下标?
如果动态的length属性还不够灵活的话,那么,JavaScript的数组还有另外的能力。
你见到过用字符串做数组下标的吗?Java行吗?C++行吗?JavaScript就行!看看下面的语句:
- var arr = [1, 2, 3];
- alert(arr[1] == arr["1"]);
- arr["js"] = 4;
- alert(arr["js"]);
var arr = [1, 2, 3];
alert(arr[1] == arr["1"]);
arr["js"] = 4;
alert(arr["js"]);
上面的语句看到,arr[1]和arr["1"]实际是一样的效果!这是怎么回事呢?我们用下面的语句验证一下:
- alert(1 == "1");
- alert(1 === "1");
alert(1 == "1"); // true
alert(1 === "1"); // false
由于JavaScript是弱类型语言,所以在使用变量的时候,JavaScript会尽可能的将它转换成所需要的类型。比如数组下面需要数字,那么提供一个字符串,将会试图把字符串转换成数字。这里的"1"就成功的转换成了数字1,于是这个语句就成立了。这就是使用 == 操作符返回true的原因。而 === 操作符不允许这样的类型转换,所以会返回false。
那么,这个arr["js"]怎么也能成立呢?这就不是上面的问题了。也就是说,JavaScript实际是允许将字符串作为数字下标的。这在JavaScript中是完全合法的。
1. 对象
对象是面向对象程序设计的基础概念之一,只需看看这个名字就已经知道了。在我们熟悉的面向对象语言中,比如Java或者C++,都有着类似的对象定义方法。比如,我们想定义一个类,名字叫Person,有两个属性:name和age,另外有一个方法,将显示出这个Person对象的名字和年龄,那么我们可以用下面的代码实现:
- public class Person {
- private String name;
- private int age;
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public int getAge() {
- return age;
- }
-
- public void setAge(int age) {
- this.age = age;
- }
-
- public void introduction() {
- System.out.println("My name is " + this.name + ", my age is " + this.age);
- }
-
- public static void main(String[] args) {
- Person p = new Person();
- p.setName("Tom");
- p.setAge(20);
- p.introduction();
- }
- }
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void introduction() {
System.out.println("My name is " + this.name + ", my age is " + this.age);
}
public static void main(String[] args) {
Person p = new Person();
p.setName("Tom");
p.setAge(20);
p.introduction();
}
}
C++的实现也是类似的,这里不再赘述。
我们先来看一下这个类的定义:首先声明属性,然后定义属性的getter和setter方法,用来外界访问私有变量,最后定义了它自己的方法。这是一个比较常见的定义方式,以至于以后的很多语言,比如C#,都采用这种定义。
那么,什么是对象呢?对象不过是具有特定属性和方法的集合。虽然这并不是一个严格的定义,但是将属性和它的名字(不妨我们把它的方法也看作是它的属性,这并没有什么不同)放在一起,形成一个集合,这就是对象。也就是说,简单来看,对象就是这种具有“键-值”对的形式。
2. JavaScript的对象
“键-值”对的形式……这个样子看上去是不是有些面熟?Bingo!对了!这不就是数组的形式吗?嗯,恭喜你想到这一点!的确,在JavaScript中,对象的定义就是像数组的定义。下面,我们在JavaScript中对这个Person进行一下定义:
- var Person = {
- "name": "Tom",
- "age": 20,
- "introduction": function() {
- alert("My name is " + this.name + ", my age is " + this.age);
- }
- };
- Person.introduction();
var Person = {
"name": "Tom",
"age": 20,
"introduction": function() {
alert("My name is " + this.name + ", my age is " + this.age);
}
};
Person.introduction();
来看一下这段代码。看上去很像数组的定义,只不过数组一般使用数字类型作为下标,而这里我们使用的是字符串。回想一下,其实在JavaScript中,字符串也是可以作为数组下标的,不是吗?好了,这里我们声明了一个对象Person,它有一个name,还有一个age,而且还有一个方法显示出这两个属性。
在JavaScript中,对象就是“键-值”对的形式,具体来说是"string-as-key": object-as-value的形式。也就是说,这个键必须是string类型的,而值可以是任何类型的。那么,方法呢?其实,JavaScript中的function也是一个类型,这个在后面会有描述的,这里仅仅先知道就可以了。这种数学上成为二元组的样式很常见,数组就是这样的,只不过数组的键必须是int。同样,JavaScript的对象也是一个特殊的二元组,只不过键是string类型的。这是不是就像是一种散列?或者说是哈希表?就是这个样子!
如果说你觉得每个属性名都要加一个引号觉得很别扭,那么你大可不加!像下面的语句,JavaScript完全认为你的正确的:
- var Person = {
- name: "Tom",
- age: 20,
- introduction: function() {
- alert("My name is " + this.name + ", my age is " + this.age);
- }
- }
- Person.introduction();
var Person = {
name: "Tom",
age: 20,
introduction: function() {
alert("My name is " + this.name + ", my age is " + this.age);
}
}
Person.introduction();
我比较习惯于这种写法,看上去和Java等语言差不多。
3. 属性的使用
JavaScript中属性的使用或许比较特别。看下面试图使用Person的name属性的四个语句,看上去都差不多,实际上也确实如此:
- alert(Person.name);
-
- alert(Person["name"]);
- alert(Person[name]);
alert(Person.name); // Tom
// alert(Person."name");
alert(Person["name"]); // Tom
alert(Person[name]); // undefined
除去注释掉的一句,其他的三个语句都能够通过解释(由于JavaScript是解释型语言,不是编译型的,因此这里不说是编译),但是只有1、3句能够取出name属性!第一句和Java没有什么区别,后面的两个显得比较特别,第三句看上去像什么?对了!数组元素的访问!这进一步验证了JavaScript中的数组和对象“本是同根生”。那么,第四句呢?当然是返回undefined!因为数组下标必须是数字或者字符串嘛!
熟悉Java的话或许会对introduction函数有些疑问。Java程序员不会时时刻刻把this加上,除非哪天心血来潮,才会加上这个this。但是,如果你想在JavaScript中偷懒,去掉this,结果只会是报错。这是怎么回事呢?简单来说,在这里的this关键字是必不可少的!这是JavaScript与其他语言的不同。具体为什么,会在以后的文章中说明,现在只是知道就好了——不要在这里偷懒哦~~
4. 更多属性的操作
现在对JavaScript对象属性的认识应该在这样一点上:JavaScript的对象就是一个二元组,或者说就是一个散列或哈希表。如果能明白这一点,就不会对下面的操作有所奇怪了:
- var Person = {};
- Person.name = "Tom";
- Person["age"] = 20;
- Person.introduction = function () {
- alert("My name is " + this.name + ", my age is " + this.age);
- };
- Person.introduction();
- for(var field in Person) {
- alert("field name: " + field + "; value: " + Person[field]);
- }
- delete Person.name;
- Person.introduction();
- alert(name in Person);
var Person = {}; // 创建一个空对象
Person.name = "Tom"; // 添加一个属性name,并赋值为Tom
Person["age"] = 20; // 用另外的办法新增属性
Person.introduction = function () {
alert("My name is " + this.name + ", my age is " + this.age);
};
Person.introduction();
for(var field in Person) { // 使用foreach循环列出对象中所有属性
alert("field name: " + field + "; value: " + Person[field]);
}
delete Person.name; // 删除name属性
Person.introduction();
alert(name in Person); // 使用in操作符判断属性是否存在
5. 对象的constructor属性
在JavaScript中,每个对象都有一个constructor属性。这个constructor属性用来记录对象初始化时的构造函数名字。例如:
- var date = new Date();
- alert(date.constructor);
- alert(date.constructor == "Date");
- alert(date.constructor == Date);
var date = new Date();
alert(date.constructor);
alert(date.constructor == "Date"); // false
alert(date.constructor == Date); // true
嗯,这个Date是JavaScript的内置对象。这里的代码看上去很平常。不过,如果你要使用自己写的对象,比如前面的Person,就会发现它的constructor对象怎么是Object?这里有两个问题:第一,我们并没有给Person constructor属性,它怎么会有的?第二,这个constructor属性怎么是object,而不是我们的Person呢?
对于第一个问题,很明显,是JavaScript给我们加上的。事实上,每个JavaScript对象都会有这样一个属性。那么,它的值又怎么是Object呢?这个问题,在我们说道new这个运算符的时候会给大家说明的。这里请大家注意,本文中的对象其实是指的单独的使用new之后得到的对象。也就是说,那个constructor属性是在new运算符的时候获得的。这就涉及到构造函数了——不过这不是本文的重点,以后再说吧~~ :-)
在很多语言中,函数(Java里面成为方法)和对象时截然不同的两种东西。函数被定义为对象的动作,或者是全局的(像在C++中的main函数一样)。但是在JavaScript中,函数和对象的界限却显得不那么明显。
1. 函数的定义
JavaScript中有很多种定义函数的方法:
- function hello() { alert("Hello!"); }
- var hello1 = function() { alert("Hello!"); };
- var hello2 = new Function("", "alert('Hello!');");
- hello();
- hello1();
- hello2();
function hello() { alert("Hello!"); }
var hello1 = function() { alert("Hello!"); };
var hello2 = new Function("", "alert('Hello!');");
hello();
hello1();
hello2();
上面给出了三种JavaScript的函数定义语句。第一句是常见的定义,看上去和Java等语言没有太大的不同。这句是定义了一个具名函数,按照上面的例子,这里的函数定义名字为hello。第二句是将一个匿名函数定义好后赋值给一个变量,于是通过这个变量就可以引用这个匿名函数。这两句看上去效果差不多,但是它们是不一样的:第一句定义的是一个具名函数,第二句定义的是一个匿名函数——尽管你可以通过这个变量引用到这个匿名函数,但实际上它还是匿名的。它们的区别可以由下面的看出:
- hello();
- hello1();
- function hello() { alert("Hello!"); }
- var hello1 = function() { alert("Hello!"); };
hello();
hello1(); // error
function hello() { alert("Hello!"); }
var hello1 = function() { alert("Hello!"); };
具名函数的作用范围是全局的:你可以在定义之前使用这个函数。但是匿名函数的定义是后向的,像C/C++一样,必须在定义之后才能使用。这就是为什么hello可以使用,但是hello1就会有错误。然后试想一下这是为什么呢?JavaScript的解释过程和HTML一样是从上到下的。所以,这里的匿名函数就相当于是一个变量的定义,因此在JavaScript解释器解释执行时并不知道这个变量的定义,因此发生错误。但是,对于函数的定义则是扫描全局。
第三个语句就很有意思了。它创建了一个Function类的对象。这个构造函数(姑且这么叫吧)具有两个参数,第一个是函数的参数,第二个是函数体。具体来说,下面的两个函数定义是等价的:
- function sayHelloTo(name) {
- alert("Hello, " + name);
- }
- var sayHelloTo1 = new Function("name", "alert('Hello, ' + name)");
function sayHelloTo(name) {
alert("Hello, " + name);
}
var sayHelloTo1 = new Function("name", "alert('Hello, ' + name)");
这种使用Function进行定义的方式并不常见,但是这个语句显示的特性却很有趣:它意味着,你可以使用这种构造函数在运行时动态的构造函数!这是一般的语言没有的特性。
2. 函数的参数
JavaScript的函数也是相当的灵活,不仅是它的定义方式多种多样,甚至它的参数都有“奇怪”的行为。由于JavaScript是弱类型的语言,因此,它不能对你的函数参数类型做检测,甚至不能保证你传入的参数个数是否和函数定义一致。这就需要有一些特殊的检测。
- function sum2(a, b) {
- alert(a + b);
- }
- sum2(1);
- sum2(1, 2);
- sum2(1, 3, 5);
function sum2(a, b) {
alert(a + b);
}
sum2(1); // NaN
sum2(1, 2); // 3
sum2(1, 3, 5); // 4
看这个例子,仅仅接受两个参数的函数,在调用时可以有任意个参数!但是,它仅取用符合条件的个数,在这里也就是前两个参数。所以,当你传入一个参数时,JavaScript试图将两个数字加起来,结果第二个参数不存在,因此返回值是NaN。第三种情况,实参个数多于形参个数,此时JavaScript只取前两个参数相加。
尽管很不正式,但是可以说,JavaScript的函数参数是不定参数,也就是说,你可以传入任意的参数值。使用JavaScript函数内置的arguments就可以遍历所有传入的参数。比如下面的代码:
- function sum() {
- var total = 0;
- for(var i = 0; i < arguments.length; i++) {
- total += arguments[i];
- }
- alert(total);
- }
- sum(1, 2);
- sum(1, 2, 3);
function sum() {
var total = 0;
for(var i = 0; i < arguments.length; i++) {
total += arguments[i];
}
alert(total);
}
sum(1, 2);
sum(1, 2, 3);
arguments的行为很像数组,但它并不是数组。可以使用typeof操作符看一下,也可以调用它的constructor属性。
这里有一点需要说明,arguments有个callee属性,可以调用arguments自身所在的函数。也就是说,可以通过这个属性递归调用函数自身。所以,即使是匿名函数,也可以实现递归调用。如:
- function sum(n) {
- if(n <= 1) {
- return 1;
- }
- return n + arguments.callee(n - 1);
- }
- alert(sum(100));
function sum(n) {
if(n <= 1) {
return 1;
}
return n + arguments.callee(n - 1); // 递归调用自身
}
alert(sum(100));
我觉得大家都会知道这个著名问题的答案的。
3. 函数也是对象
回想一下上面的第三个语句,它已经强烈暗示了,函数其实也是对象!那么,作为一个对象,函数应该具有对象的一切特性:添加属性、删除属性、作为返回值等等。是的!JavaScript的函数就是这么样的!
- function hello() {
- alert("Hello!");
- }
- hello.name = "Tom";
- alert(hello["name"]);
- delete hello.name;
- alert(hello.name);
-
- var hello1 = function() { alert("hello1"); };
- hello1();
-
- function show(x) { alert(x); }
- var arr = [show];
- arr[0](5);
-
- function callFunc(func) {
- func();
- }
- callFunc(function() {
- alert("Inner Function.");
- });
-
- function show() {
- return function(n) {
- alert("number is " + n);
- };
- }
- show()(10);
function hello() {
alert("Hello!");
}
hello.name = "Tom"; // 添加属性
alert(hello["name"]);
delete hello.name; // 删除属性
alert(hello.name);
// 赋值给变量
var hello1 = function() { alert("hello1"); };
hello1();
// 作为数组元素
function show(x) { alert(x); }
var arr = [show];
arr[0](5);
// 作为函数的参数
function callFunc(func) {
func();
}
callFunc(function() {
alert("Inner Function.");
});
// 作为函数的返回值
function show() {
return function(n) {
alert("number is " + n);
};
}
show()(10);
瞧!凡是对象可以做到的,函数统统都能做到!JavaScript中的函数就是对象!
现在我们已经从数组,逐渐开始到对象和函数。这些都是基本概念,后面,我们将对JavaScript的面向对象特性做进一步的介绍。