面向对象的程序设计

1.理解对象

数据属性:

[[Configurable]]:能否通过delete删除属性而重新定义属性

[[Enumerable]]:表示能否通过for-in循环返回属性

[[Writable]]:表示能否修改属性的值。

[[Value]]:包含这个属性的数据值。

要修改属性默认的特性,必须使用E5的Object.defineProperty()方法。这个方法接收三个参数:属性所在的对象、属性的名字和一个描述符对象。

访问器属性:

函数:

getter:读取访问器属性
setter:写入访问器属性

[[Configurable]]:能否通过delete删除属性而重新定义属性

[[Enumerable]]:表示能否通过for-in循环返回属性

[[get]]:在读取属性时调用的函数

[[set]]:在写入属性时调用的函数

访问器属性不能直接定义,必须使用E5的Object.defineProperty()方法来定义。

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 = 2005; alert(book.edition);

2.创建对象

(1)工厂模式

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 = createPerson("xun",25,"front-end engineer");

(2)构造函数模式

function Person(name,age,job){
	this.name = name;
	this.age = age;
	this.job = job;
	this.sayName = function(){
		alert(this.name);
	};
}
var person1 = new Person("xun",25,"front-end engineer");

这种构造函数的方法

1.创建一个新对象
2.将构造函数的作用域付给新对象(this指向新对象)
3.执行构造函数中的代码(为这个新对象添加属性)
4.返回新对象

a.将构造函数当做函数

b.构造函数存在问题

缺点:使用构造函数的主要问题,就是每个方法都要在每个实例上重新创建一遍。 person1和person2都有一个sayName()方法,但那两个方法不是同一个Function的实例。因为js中函数是对象,因此,每定义一个函数,也就是实例化一个对象。 相当于:

function Person(name,age,job){
	this.name = name;
	this.age = age;
	this.job = job;
	this.sayName = new Function('alert(this.name);'};
}

(3)原型模式

我们创建的每一个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,

isPrototypeOf()方法来确定对象之间是否存在这种关系。

alert(Person.prototype.isPrototypeOf(person1)); //true

Object.getPrototypeOf()

alert(Object.getPrototypeOf(person1) == Person.prototype); //true

hasOwnPrototype()方法可以检测一个属性是否存在于实例中,还是存在于原型中。只有给定属性存在于对象实例中时,才会返回true。

原型与in操作符

可以访问本对象的属性,和其原型上的属性。

原生引用类型:Object/Array/string都在构造函数的原型上定义了方法。

Array.prototype.sort()
String.prototype.subString()

原型对象的问题:他省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都取得相同的属性值。

原型中所有属性是被很多实例共享的,这种共享对于函数非常适合。对于啊些包含基本值的属性,通过在实力上添加一个同名属性,可以隐藏原型中的对应属性。然而,对于包含引用类型的属性来说,问题比较突出。

function Person(){}

Person.prototype = {
	constructor:Person,
	name:'Nicholas',
	age:29,
	firends:["xun","yun"],
	sayName: function(){
		alert(this.name);
	}
}

var person1 = new Person();
var person2 = new Person();
person1.friends.push("van");

alert(person1.friends); //'xun,yun,van'
alert(person2.friends); //'xun,yun,van'
alert( person1.friends == person2.friends )

由于friends数组存在于Person.prototype而非person1中,person1和person2指向同一个数组。所以原型模式一般也很少单独使用。

(4)组合使用构造函数模式和原型模式

创建自定义方式的最常见方式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着方法的引用。最大限度地节省了内存。

function Person(name,age,job){
	this.name = name;
	this.age = age;
	this.job = job;
}

Person.prototype = {
	constructor:Person,	
	sayName: function(){
		alert(this.name);
	}
}

var person1 = new Person();
var person2 = new Person();
person1.friends.push("van");

alert(person1.friends); //'xun,yun,van'
alert(person2.friends); //'xun,yun'
alert( person1.friends == person2.friends );  //false
alert( person1.sayName == person2.sayName );  //true

这种方法是目前在ES中使用最广泛、认同度最高的一种创建自定义类型的方法。这是定义引用类型的一种默认模式。

(5)动态原型模式

function Person(name,age,job){
	this.name = name;
	this.age = age;
	this.job = job;
}

if(typeof this.sayName != "function"){
	Person.prototype.sayName = function(){
		alert(this.name);
	}
}

var friend = new Person("xun",29,"FE");
friend.sayName();

使用动态原型模式时,不能使用对象字面量重写原型。

(6)寄生构造函数模式

function Person(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 = new Person("xun",25,"front-end engineer");
person.sayName();//"xun"

(7)稳妥构造函数模式

function Person(name,age,job){
	var o = new Object();
	o.name = name;
	o.age = age;
	o.job = job;
	o.sayName = function(){
		alert(name);
	};
	return o;
}

这种模式创建的对象中,除了使用sayName()方法之外,没有其他方法访问name的值。

var person1 = Person("xun",25,"front-end engineer");
person.sayName();//"xun"

3.继承

依靠原型链来实现

(1)原型链

基本思想:利用原型让一个引用类型继承另一个引用类型的属性和方法。

确定原型与实例的关系:

alert(instance instanceof object) //true
 
alert(object.prototype.isPrototypeOf(instance)); //true

原型链的问题:

a.来自包含引用类型值的原型。包含引用类型值的原型属性会被所有实例共享;而这也是要在过早函数中,而不是原型对象中定义属性的原因。

function SuperType() {
	this.colors = ["red","green","blue"];
}
function SubType(){}

SubType.prototype = new SuperType();

var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors);  //"red,blue,green,black"

var instance2 = new SubType();
alert(instance2.colors);  //"red,blue,green,black"

在通过原型实现继承时,原型实际上会变成另一个类型的实例。于是,原来的实例属性也就变成了现在的原型属性了。

b.在创建子类型的实例时,不能像超类型的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。

很少单独使用原型链

(2)借用构造函数

基本思想:在子类型构造函数的内部调用超类构造函数。

apply()和call()可以在新创建的对象上执行构造函数。

function SuperType() {
	this.colors = ["red","green","blue"];
}
function SubType(){
	//继承了SuperType
	SuperType.call(this);
}

var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors);  //"red,blue,green,black"

var instance2 = new SubType();
alert(instance2.colors);  //"red,blue,green"

通过使用call()方法(apply()方法也可以),在新创建的SubType实例下调用了SuperType构造函数。这样就会在新SubType对象上执行SuperType()函数中定义的所有对象初始化代码。SubType的每一个实例都会具有自己的colors属性副本。

a.传递参数

function SuperType(name) {
	this.name = name;
}
function SubType(){
	//继承了SuperType
	SuperType.call(this,'xun');
	this.age = 25;
}

var instance = new SubType();
alert(instance.name); //'xun' 
alert(instance.age);  //25

b.借用构造函数的问题:

函数复用无法实现。

(3)组合继承

将原型链和借用构造函数的技术组合到一起。使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。即通过在原型上定义方法实现了函数复用,又能够保证每个实例都有自己的属性。

function SuperType(name) {
	this.name = name;
	this.colors = ["red","blue","green"];
}
SuperType.prototype.sayName = function(){
	alert(this.name);
};
function SubType(name,age){
	//继承SuperType属性
	SuperType.call(this,name);
	this.age = age;
}
//继承方法
SubType.prototype = new SuperType();

SubType.prototype.sayName = function(){
	alert(this.age);
}

var instance1 = new SubType("xun",25);
instance1.colors.push("black");
alert(instance.name); //'xun' 
alert(instance.age);  //25
alert(instance.colors);  //"red,blue,green,black"

var instance2 = new SubType("yun",27);
alert(instance.name); //'yun' 
alert(instance.age);  //27
alert(instance.colors);  //"red,blue,green"

组合继承避免了原型链和借用构造函数的缺陷,融合了他们的优点,称为js中最常用的继承方式。而且 instanceof、isPrototypeOf()也能够用于识别基于组合继承创建的对象。

。。。未完

4.小结

原型链的问题是对象实例共享所有继承的属性和方法,因此不适合单独使用。解决方法就是借用构造函数,即在子类型构造函数的内部调用超类型构造函数。这样就可以做到每个实例又具有自己的属性,同时还能保证只使用构造函数模式来定义类型。

使用最多的继承模式是组合继承。这种模式使用原型链继承共享属性和方法,通过借用构造函数继承实例属性。