在JavaScript中,所有的事物都是对象,如字符串、数值、数组、函数。对象是包含相关属性和方法的集合体。
JavaScript中,有Date、Array、String等这样的内置对象,也可以自定义对象。创建自定义对象的最简单的方式就是使用操作符new创建一个object的实例。然后通过“.”为其添加属性和方法,创建对象的语法如下所示:
var 对象名称 = new Object();
示例 1:
<!DOCTYPE html> <html> <head> <title>创建对象</title> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="this is my page"> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <!--<link rel="stylesheet" type="text/css" href="./styles.css">--> <script> var flower=new Object(); flower.name="长春花"; flower.genera="夹竹桃科 长春花属"; flower.area="非洲、亚热带、热带,以及中国大陆的华东、西南、中南等地"; flower.uses="观赏或用药等"; flower.showName=function(){ alert(this.name); }; flower.showName(); </script> </head> <body> </body> </html>
运行结果:
上述方式是基于Object对象的方式创建对象,在JavaScript中还有一种使用字面量赋值的方式在定义对象的时候为其添加属性和方法,这样创建的对象,其方法和属性可以直接使用对象引用。
字面量:通常等号右面的数据成为字面量
如:
var name = "小黑";
name:变量名
小黑:字面量
json格式:
var student={
属性名1:属性值1,
属性名2:属性值2,
属性名3:属性值3,
}
示例 2:
<!DOCTYPE html> <html> <head> <title>字面量赋值方式创建对象</title> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="this is my page"> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <!--<link rel="stylesheet" type="text/css" href="./styles.css">--> <script> var flower={ name:"长春花", genera:"夹竹桃科 长春花属", area:"非洲、亚热带、热带以及中国大陆的华东、西南、中南等地", uses:"观赏或用药等", showName:function(){ alert(this.name); } }; flower.showName(); </script> </head> <body> </body> </html>
运行结果:
无论是基于Object创建对象,还是使用字面量赋值的方式创建对象,都有一个非常明显的缺点,那就是使用同一个接口需要创建很多对象,这样会产生大量的重复代码,但是构造函数的出现解决了这一问题。
构造函数: 内部使用了this变量,对构造函数使用new操作符,就能生成实例,并且this变量会绑定在实例对象上,从而定义自定义对象类型的属性和方法。下面使用构造函数重写示例1。
示例 3:
<!DOCTYPE html> <html> <head> <title>构造函数</title> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="this is my page"> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <!--<link rel="stylesheet" type="text/css" href="./styles.css">--> <script type="text/javascript"> window.onload=function(){ var flower1=new Flower("长春花","夹竹桃科 长春花属性","非洲、亚热带、热带,;以及中国大陆的华东、西南"+ +"中南等地","观赏或用药等"); flower1.showName(); }; function Flower(name,genera,area,uses){ this.name=name; this.genera=genera; this.area=area; this.uses=uses; this.showName=function(){ alert(this.name); }; } </script> </head> <body> </body> </html>
运行结果:
使用构造函数可以创建多个对象,如创建对象flower2和flower3,代码如下所示
<!DOCTYPE html> <html> <head> <title>构造函数</title> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="this is my page"> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <!--<link rel="stylesheet" type="text/css" href="./styles.css">--> <script type="text/javascript"> window.onload=function(){ var flower1=new Flower("长春花","夹竹桃科 长春花属性","非洲、亚热带、热带,;以及中国大陆的华东、西南"+ +"中南等地","观赏或用药等"); flower1.showName(); var flower2=new Flower("牡丹","芍药科","中国","观赏、食用或药用"); flower2.showName(); var flower3=new Flower("曼陀罗花","茄科 曼陀罗属","印度、中国北部","观赏或药用"); flower3.showName(); }; function Flower(name,genera,area,uses){ this.name=name; this.genera=genera; this.area=area; this.uses=uses; this.showName=function(){ alert(this.name); }; }; </script> </head> <body> </body> </html>
使用构造函数创建新实例,必须使用new操作符,以这种方式调用构造函数实际上会经历以下四个步骤。
① 创建一个新对象。
② 将构造函数的作用域赋给新对象(this就指向了这个新对象)。
③ 执行构造函数中的代码。
④ 返回新对象。
此时我们在加入如下代码
运行结果:
即如图所示,flower1、flower2、flower3都有一个名为showName()的方法,那三个方法不是同一个Function的实例,创建三个完全同样任务的Function实例完全没必要。况且有this对象在,根本不用再执行代码前就把函数绑定到特定对象上面,可以通过把函数定义转移到构造函数外部来解决这个问题。
示例4:
<!DOCTYPE html> <html> <head> <title>test04.html</title> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="this is my page"> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <!--<link rel="stylesheet" type="text/css" href="./styles.css">--> </head> <body> <script type="text/javascript"> function Flower(name,genera,area,uses){ this.name=name; this.genera=genera; this.area=area; this.uses=uses; this.showName=showName; } function showName(){ alert(this.name); } var flower1=new Flower("长春花","夹竹桃科 长春花属性","非洲、亚热带、热带,;以及中国大陆的华东、西南"+ +"中南等地","观赏或用药等"); flower1.showName(); var flower2=new Flower("牡丹","芍药科","中国","观赏、食用或药用"); flower2.showName(); var flower3=new Flower("曼陀罗花","茄科 曼陀罗属","印度、中国北部","观赏或药用"); flower3.showName(); alert(flower1.showName==flower2.showName); </script> </body> </html>
运行结果:
这样一来,由于showName包含的是一个指向函数的指针,因此flower1、flower2、flower3对象就共享了在全局作用域中定义的用一个showName()函数。这样做解决了三个函数做同一件事的问题。那么新问题又来了,在全局作用域中定义的函数实际上只能被某个对象调用,这就让全局作用域有点名不副实,大材小用了。
showName()函数在全局作用域中,所以可以直接调用。
如在示例4中加入下列代码:
showName();
运行结果为空。
更让人无法接受的是,如果对象需要定义很多方法,那么就要定义很多个全局函数,于是这个自定义的引用类型就丝毫没有封装性可言了,但是,这些问题由于原型对象的出现就完全可以解决了。
原型对象
先来说一下原型对象。
由上图可知:交通工具时一个原型对象。火车是交通工具的一个实例,而绿皮火车又是火车的一个实例;但当没有交通工具时,火车就变成了一个原型对象,绿皮火车是火车的一个实例;而当没有火车,只有绿皮火车时,绿皮火车本身也是一个原型对象,绿皮火车再细分之后的属于绿皮火车的的实例。即实例和原型对象都是相对而言的。
在JavaScript中创建的每个函数都有一个prototype属性,这个属性是一个指针;prototype通过调用构造函数而创建的那个对象实例的原型对象,使用原型对象的好处就是可以让所有对象实例共享它所有的属性和方法,也就是说不必在构造函数中定义对象实例的信息,可以将这些信息直接添加到原型对象中。
实例5:
<!DOCTYPE html> <html> <head> <title>test05.html</title> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="this is my page"> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <!--<link rel="stylesheet" type="text/css" href="./styles.css">--> </head> <body> <script type="text/javascript"> function Student(){ Student.prototype.name="张三"; Student.prototype.age="18"; Student.prototype.address="中国"; Student.prototype.showInfo=function(){ alert("姓名:"+this.name+" 年龄:"+this.age+" 地址:"+this.address); }; } var student1=new Student(); student1.showInfo(); var student2=new Student(); student2.showInfo(); alert(student1.showInfo==student2.showInfo); </script> </body> </html>
将showInfo()方法和所有属性直接添加到了Student的prototype属性中,通过构造函数来创建新对象,新对象还会有相同的属性和方法,但与构造函数不同的是,新对象的这些属性和方法是由所有实例共享,也就是说student1和student2访问的都是同一组属性和同一个showName()函数,所以student1.showInfo()和studenet2.showInfo()返回的信息是一样的,最后一句代码alert(student1.showInfo==student2.showInfo)返回值为true;
在默认的情况下,所有的原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针,Student.prototype.constructor指向Student,而通过这个构造函数还可以继续为原型对象添加其他属性和方法。
创建了自定义的构造函数只有,其原型对象默认会取得constructor属性,其他方法则是从Object继承而来的。当调用构造函数创建一个新实例后,该实例的内部将包含一个指针指向构造函数的原型对象,在很多实现中,这个内部属性的名字是__proto__,而且通过脚本可以访问到、
图中展示了Student构造函数,Student的原型属性及Student现有的两个实例之间的关系。Student.prototype指向了原型对象,而Studentprototype.constructor又指回了Student,原型对象中除了包含constructor属性之外,还包括后来添加的其他属性。Student的每个实例student1和student2都包含一个内部属性,该属性仅仅指向了Student.prototype,也就是说他们与构造函数没有直接的关系。虽然两个实例都不包含属性和方法,但却可以调用student1.showInfo();
虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值,如果在实例中添加一个属性,而该属性与实例原型中的一个属性同名,那就在实例中创建该属性,该属性将会屏蔽原型中的那个属性。
当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性,也就是说,添加这个属性只会阻止我们访问原型中的这个值,但不会修改那个属性,即使将这个属性设置为null,也只会在实例中设置这个属性,而不会恢复其指向原型的链接。
继承
继承是面向对象语言中一个最为人津津乐道的概念,许多面向对象语言都支持两种继承方式:接口继承和实现继承。接口继承制继承方法签名,而实现继承则继承实际的方法,由于函数没有签名,在ECMScript中无法实现接口继承,ECMAScript中只支持实现继承,而且其继承主要是依靠原型链来实现的。
原型链
对象继承
示例5:
<!DOCTYPE html> <html> <head> <title>test06.html</title> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="this is my page"> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <!--<link rel="stylesheet" type="text/css" href="./styles.css">--> </head> <body> <script type="text/javascript"> function Humans(){ this.clothing=["trousers","dress","jacket"]; } function Man(){ } // 继承了Humans Man.prototype=new Humans(); var man1= new Man(); man1.clothing.push("coat"); alert(man1.clothing); var man2=new Man(); alert(man2.clothing); </script> </body> </html>
运行结果:
从弹出的信息可以看到,Man的所有实例都会共享这一个clothing属性,从man1.clothing的修改能够通过man2.clothing反映出来。
原型链的第二个问题是在创建子类型的实例时,不能向父类型的构造函数中传递参数,其实是没有办法在不影响所有对象实例的情况下,给父类型的构造函数传递参数。
基于两个原因,实际开发中很少会单独使用原型链。因此,开发人员在解决原型中包含引用类型值所带来的问题时,使用一种叫做借用构造函数(constructor stealing)的技术。
借用构造函数
就是在子类型构造函数的内部调用父类型构造函数,,即在子类型构造函数的内部通过apply()或call()方法调用父类型的构造函数,也可以在将来新创建的对象上执行构造函数。
示例6:
<!DOCTYPE html> <html> <head> <title>test07.html</title> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="this is my page"> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <!--<link rel="stylesheet" type="text/css" href="./styles.css">--> </head> <body> <script type="text/javascript"> function Humans(){ this.clothing=["trousers","dress","jacket"]; } function Man(){ Humans.call(this); // 继承了Humans } // 继承了Humans Man.prototype=new Humans(); var man1= new Man(); man1.clothing.push("coat"); alert(man1.clothing); var man2=new Man(); alert(man2.clothing); </script> </body> </html>
运行结果:
示例中Humans.call(this)表示“借调”了父类型的构造函数,通过使用call()方法(也可以使用apply()方法),实际上是在新创建的Man实例的环境下调用了Humans构造函数,这样在新的Man对象上执行Humans()函数中定义的所有对象初始化代码。结果就是,Man每个实例都会具有自己的clothing属性的副本,都会被Humans()重新赋值。
示例7:
<!DOCTYPE html> <html> <head> <title>test08.html</title> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="this is my page"> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <!--<link rel="stylesheet" type="text/css" href="./styles.css">--> </head> <body> <script type="text/javascript"> function Humans(name){ this.name=name; } function Man(){ Humans.call(this,"Mary"); //继承了Humans,同时还传递了参数 this.age=38; // 实例属性 } var man1=new Man(); alert(man1.name); alert(man1.age); </script> </body> </html>
如果仅仅使用借用构造函数的技术,也将无法避免构造函数模式存在的问题,那就是方法都在构造函数中定义,因此函数复用就无从谈起了,而且在父类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。基于这个问题,组合继承很好地解决了这些。
组合继承
组合继承(combination inheritance)有时也叫做伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。其思路是使用原型链实现对原型属性的方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。
实例8:
<!DOCTYPE html> <html> <head> <title>test09.html</title> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="this is my page"> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <!--<link rel="stylesheet" type="text/css" href="./styles.css">--> </head> <body> <script type="text/javascript"> function Humans(name){ this.name=name; this.clothing=["trousers","dress","jacket"]; } Humans.prototype.sayName=function(){ alert(this.name); } function Man(name,age){ Humans.call(this,name); //继承属性 this.age=age; } Man.prototype=new Humans(); //继承方法 Man.prototype.sayAge=function(){ alert(this.age); } var man=new Mna("Mary",38); man1.clothing.push("coat"); alert(man1.clothing); // 输出“trousers,dress,jacket,coat” man1.sayName(); // 输出Mary man1.sayAge(); //输出38 var man2=new Man("Tom",25); alert(man2.clothing); // 输出“trousers,dress.jacket” man2.sayName(); // 输出Tom man2.sayAge(); // 输出25 </script> </body> </html>