面向对象的程序设计
面向对象的语言都一个标志,那就是他们都有类的概念,通过类可以创建任意多个具有相同属性和方法的对象。
但是JavaScript中没有类的概念。JavaScript中的对象是“无序属性的集合,其属性可以是基本值,对象或函数”。对象的每个属性都有一个名字,而每个名字都映射到一个值。
一、理解对象
1、创建对象的方法上一章已经提到过:new操作符、字面量这两种,常用的是字面量法;
2、对象属性及其属性类型:
对象在定义时会有很多属性和方法,这些属性在创建时都带有一些特征值,JavaScript通过这些特征值来定义他们的ECMAScript中有两种属性:数据属性和访问器属性;
数据属性:
-
[[Configurable]]:表示能否通过delete删除属性从而重写定义属性,能否修改属性的特征;
-
[[Enumerable]]:表示能否通过for-in循环返回属性;
-
[[Writable]]:表示能否修改属性的值;
-
[[Value]]:表示包含这个属性的数据值;
上述属性是可以修改的,通过Object.definedProperty()方法,接收三个参数:属性所在对象,属性名字,操作符对象;
var person={}
Object.definedProperty(person,"name",{
writable:false,//被设置为只读;
value:"jack"
});
alert(person.name);//jack;
person.name="Bob";
alert.name(person.name);//jack
此方法也同样适用于其他特性;
当我们在使用Object.definedProperty()方法创建一个新属性时,如果不指定configurable、writable和enumerable的值,默认是false;但对数情况下我们没有必要利用到这个方法。
访问器属性:
访问器属性不包含数据值,它包含一对setter和getter函数(不必须);
读取访问器属性时,会调用getter函数,返回有效的值;
写入访问器属性时,会调用setter函数,负责决定如何处理数据;
-
[[Configurable]]:表示能否通过delete删除属性从而重写定义属性,能否修改属性的特征;
-
[[Enumerable]]:表示能否通过for-in循环返回属性;
-
[[setter]]:写入属性时调用的函数,默认值为undefined;
-
[[]getter]:读取属性时调用的函数,默认值为undefined;
访问器属性需要通过Object.definedProperty()方法来定义:
var book={
_year:2004,
edition:1
};
Object.defineProperty(book,"year",{
get:function(){
return this._year;
},
set:function(newValue){
if(newValue>2004){
this._year=newValue;
this.edition+=newValue-2004;
}
}
})
book.year=2008;
console.log(book.year);//2008
console.log(book.edition);//5
_year表示只能通过对象方法访问的属性;
不一定非要同时制定getter和setter,只指定getter表示属性无法写入,尝试写入属性会被忽略;只指定setter表示属性无法获取,尝试获取会返回undefined;
定义多个属性:Object.defineProperties(),有两个参数,第一个是添加属性的对象,第二个是需要添加或修改的属性名;
获取属性的特征:Object.getOwnPropertyDeccriptor(),有两个参数,第一个是属性所在对象,第二个是属性名称
例如:alert(descriptor.value);
二、创建对象
1、工厂模式:
function Person(name,age,job){
var obj=new Object();
obj.name=name;
obj.age=age;
obj.job=job;
return obj;
}
var person1=Person("jack","12","student");
var person2=Person("Bob","32","teacher");
这种模式虽然解决了创建多个相似对象的问题,但却没有解决对象的识别问题;
2、构造函数模式:
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
}
var person1=new Person("jack","43","teacher");
var person2=new Person("zhang","12","student");
构造函数模式与工厂模式的不同之处:
没有显式的创建对象;
使用了new操作符;
没有把属性和方法赋给this对象;
没有return语句;
构造函数模式的一般步骤:
创建一个新对象;
将构造函数的作用域通过this赋给新对象;
执行构造函数的代码,为新对象添加属性和方法;
返回新对象
构造函数模式虽然好用但是,每个方法都要在每个实例上创建一遍,这是因为每个实例对象都包含一个Function()实例,这样的话,创建多个完成相同任务的Function()实例就没有必要了。
3、原型模式:
我们创建的函数都有一个prototype属性,这个属性是一个指针,指向一个对象,这个对象用来保存所有实例的属性和方法,
使用原型对象的好处就是可以让所有对象实例共享他所包含的属性和方法;
function Person(){};
Person.prototype.name="zhang";
Person.prototype.age=12;
Person.prototype.job="student";
var person1=new Person();
var person2=new Person();
alert(person1.name==person2.name);//ture
在任何时候,我们创建的函数都会包含一个prototype属性——原型对象。我们通过构造函数创建的实例对象中也包含了一个指针——[[prototype]],这个指针指向的是原型对象,原型对象里的一个属性——constructor也是一个指针,指向的是构造函数。person1.name访问的步骤是先查看实例对象中有没有name这个属性,如果没有就继续搜索指针指向的原型对象上查找,找到了就返回。实例对象和构造函数没有直接关联,实例对象访问属性是通过原型对象实现的。这就是查找对象属性的过程。虽然实例不包含属性和方法,但可以通过person1.name的方法来获取。
虽然可以通过对象实例访问保存在原型对象中的值,但却不能通过实例对象重写原型中的值。如果我们在实例对象中添加一个属性,该属性与原型对象中的一个属性同行名,则该属性会屏蔽掉原型中的同名属性。当我们删除实例对象中的属性时,实例对象恢复对原型对象的连接。
下面是使用最广泛的方法:
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.friends=["j","k"];
}
Person.prototype={
constructor:Person,
sayName:function(){
alert(this.name);
}
}
var p1=new Person("jack",12,"student");
var p2=new Person("zhang",23,"teacher");
p1.friends.push("hhh");
alert(p1.friends);//"j,k,hhh"
alert(p2.friends);//"j,k";
alert(p1.sayName===p2.sayName);//true,因为他们调用了公共方法,但最后的值是不同的
alert(p1.friends===p2.friends);//false
三、最常用的继承模式——组合继承(伪经典继承)
function SuperType(name){
this.name=name;
this.colors=["red","black","blue"];
}
//为SuperType的原型对象上添加一个方法;
SuperType.prototype.sayName=function(){
alert(this.name);
};
function SubType(name,age){
//继承属性,内部调用要继承的构造函数
SuperType.call(this,name);
this.age=age;
}
// 继承方法
SubType.prototype=new SuperType();//重写SubType的原型对象
SubType.prototype.constructor=SubType;
//在SubType的原型对象上添加方法
SubType.prototype.sayAge=function(){
alert(this,age);
};
var instance1=new SubType("jack",12);
instance1.colors.push("green");
alert(instance1.colors);//"red,black,blue,green"
instance1.sayName();//jack
instance1.sayAge();//12
var instance2=new SubType("zhang",21);
alert(instance2.colors);//"red,black,blue"
instance2.sayName();//zhang
instance2.sayAge();//21