第六章 面向对象的程序设计
6.1 理解对象
6.1.1 属性类型
ECMAScript中有两种属性:数据属性和访问器属性
1.数据属性
- Configurable:能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。
- Enumerable:表示能否通过for-in循环返回属性
- Writable:表示能否修改属性的值。
- Value:包含这个属性的数据值
var person = {};
Object.defineProperty(person,"name",{
writable:false;
value:"Nicholas";
});
person.name = "Greg";
alert(person.name);//"Nicholas"
2.访问器属性
getter
setter
6.1.2 定义多个属性
6.1.3 读取属性特性
用Object构造函数或对象在字面量创建单个对象有个明显的缺点:使用同一个借口创建很多对象,会产生大量的重复代码。人们开始使用工厂模式的一种变体。
6.2.1 工厂模式
工厂模式抽象了创建具体对象的过程。
考虑到在ECMAScript中无法创建类,开发人员就发明了一种函数,用函数来封装以特定接口创建对象的细节。
function createPerson(name,age,job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
}
return o;
}
var person1 = creatPerson("Greg",27,"Doctor");
var person2 = creatPerson("Nicholas",29,"SE");
工厂模式虽然解决了创建多个相似对象的问题,但没有解决对象识别问题
6.2.2 构造函数模式
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
}
}
var person1 = Person("Greg",27,"Doctor");
var person2 = Person("Nicholas",29,"SE");
- 没有显示创建对象
- 直接将属性和方法赋给this对象
- 没有return语句
Person使用大写字母P。构造函数始终都应该以大写字母开头。
要创建Person的新实例,必须使用new操作符。以这种方式调用构造函数会经历以下四个步骤:
- 创建一个新对象
- 将构造函数的作用域赋给新对象
- 执行构造函数中的代码
- 返回新对象
1.将构造函数当作函数
任何函数,只要通过new操作符来调用,那它就可以作为构造函数
如果不通过new操作符来调用,那它跟普通函数也不会有什么两样
var person = new Person("Nicholas",29,"SE");
person.sayName();
Person("Greg",27,"Doctor");
window.sayName();
var o = new Object();
Person.call(o,"Kristen",25,"Nurse");
o.sayName();
2.构造函数的问题
使用构造函数的主要问题,就是每个方法都要在每个实例上重新创建一遍。
6.2.3 原型模式
使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。
不用在构造函数中定义对象实例信息,而是可以将这些信息直接添加到原型对象中
function Person(){}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "SE";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName();
var person2 = new Person();
person2.sayName();
alert(person1.sayName == person2.sayName); //true
1.理解原型对象
创建一个新函数,就会根据一组特定的规则为函数创建一个prototype属性,指向函数的原型对象
所有原型对象会自动获得一个constructor属性,包含一个指向prototype属性所在函数的指针
当为对象实例添加一个属性是,这个属性就会屏蔽原型对象中保存的同名属性;只会组织我们访问原型中的那个属性,但不会修改那个属性。使用delete操作符则可以完全删除实例属性,从而让我们能够重新访问原型属性
function Person(){}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "SE";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.name = "Greg";
delete person1.name;
isPrototype of ()
getPrototypeOf()
hasOwnProperty()
2.原型与in操作符
在单独使用时,in操作符会在通过对象能够访问给定属性时返回true
同时使用in和hasOwnProperty()就可以确定该属性到底是在对象中还是在原型中
hasPrototypeProperty()
3.更简单的原型语法
function Person(){
}
Person.prototype{
name:"Nicholas",
age:29,
job:"SE",
sayName:function(){
alert(this.name);
}
};
constructor不再指向Person了。
function Person(){
}
Person.prototype{
constructor:Person,
name:"Nicholas",
age:29,
job:"SE",
sayName:function(){
alert(this.name);
}
};
4.原型的动态性
对原型对象做任何修改都能立即从实例上体现出来
function Person(){
}
var friend = new Person();
Person.prototype{
constructor:Person,
name:"Nicholas",
age:29,
job:"SE",
sayName:function(){
alert(this.name);
}
};
friend.sayName();//error
重写原型对象切断了现有原型和之前已经存在的对象实例之间的联系
5.原生对象的原型
6.原型对象的问题
省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。
最大的问题是由其共享性所致。
原型中的所有属性是被很多实例共享的。对包含引用类型值的属性来说问题就很突出。
6.2.4组合使用构造函数模式和原型模式
构造函数模式用于定义实例属性
原型模式用于定义方法和共享属性
每个实例都会有自己的一份实例属性的副本
但同时共享着对方法的引用
最大限度的节省了内存
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Shelby","Court"];
}
Person.prototype{
constructor:Person();
sayName:function(){
alert(this.name);
}
};
var person1 = new Person("Nicholas",29,"SE");
var person2 = new Person("Greg",27,"Doctor");
person1.friends.push("Van");
alert(person1.friends); //"S,C,V"
alert(person2.friends); //"S,C"
alert(person1.friends == person2.friends); //false
alert(person1.sayName == person2.sayName); //true
6.2.5 动态原型模式
动态原型模式吧所有信息封装在了构造函数中,而通过在构造函数中初始化原型,又保持了同时使用构造函数和原型的有点。
可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。
6.2.6 寄生构造函数模式
6.3继承
6.3.1原型链
原型链作为实现继承的主要方法
基本思想:利用原型让一个引用类型继承另一个引用类型的属性和方法
1.所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype
2.确定原型和实例的关系
instance of
isPrototypeOf
3.子类需要覆盖超类某个方法,或者添加超类中不存在的某个方法,但不管怎样给原型添加方法的代码一定要放在替换原型的语句之后
4.原型链的问题
最主要的问题来自包含引用类型值的原型,属性会被所有实例共享
创建子类型的实例时,不能向超类型的构造函数中传递参数
实践中很少单独使用原型链
6.3.2 借用构造函数
在子类型构造函数的内部调用超类型的构造函数
1.传递参数
可以在子类型构造函数中向超类型构造函数传递参数
2.借用构造函数的问题
方法都在构造函数中定义,函数不能复用
6.3.3 组合继承
使用原型链实现对原型属性和方法的继承
通过借用构造函数实现对实例的继承
function SuperType(name){
this.name = name;
this.color = ["red","blue","green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name,age){
//继承属性
SuperType.call(this,name);
this.age = age;
}
//继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubTupe.prototupe.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType("Nicholas",29);
instance1.colors.push("black");
alert(instance1.colors); //r,b,g,b
instance1.sayName(); //N
instance1.sayAge(); //29
var instance2 = new SubType("Greg",27);
alert(instance2.colors); //r,b,g
instance2.sayName(); //G
instance2.sayAge(); //27