JavaScript高级程序设计4--面向对象的程序设计(上)

一、理解对象

1.属性类型

ECMA-262第5版在定义只有内部采用的特性时,描述了属性的各种特征。

ECMAScript中有两种属性:数据属性和访问器属性。

1)数据属性

数据属性有四个描述其行为的特性。

  • [[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。
  • [[Enumerable]]:表示能否通过for-in循环返回属性。
  • [[Writable]]:表示能否修改属性的值。
  • [[Value]]:包含这个属性的数据值。读取属性的时候,从这个位置读;写入属性的时候,把新值保存在这个位置,默认值为undefined。

要修改属性默认的特性,必须使用ES5的Object.defineProperty()方法。

Object.defineProperty():接收三个参数(属性所在的对象、属性的名字和一个描述符对象),其中描述符对象的属性必须为configurable、enumerable、writable和value。

var person = {};
Object.defineProperty(person, "name", {
	writable: false,
	value: "Nicholas"
});

alert(person.name);		//"Nicholas"
person.name = "Greg";
alert(person.name);		//"Nicholas"
注意:一旦把属性定义为不可配置的,就不能再把它变回可配置了。
2)访问器属性

访问器属性有如下4个特性。

  • [[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。
  • [[Enumerable]]:表示能否通过for-in循环返回属性。
  • [[Get]]:在读取属性时调用的函数。默认值为undefined。
  • [[Set]]:在写入属性时调用的函数。默认值为undefined。

//访问器属性的定义
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
//_year前面的下划线是一种常用的记号,表示只能通过对象方法访问的属性。
2.定义多个属性

Object.defineProperties()方法可以通过描述符一次定义多个属性。

var book = {};
Object.defineProperties(book, {
	_year: {
		writable: true,
		value: 2004
	},

	edition: {		
		writable: true,
		value: 1
	},

	year: {
		get: function(){
			return this,_year;
		},
		set: function(newValue){
			if (newValue>2004){
				this._year = newValue;
				this.edition += newValue - 2004;
			}
		}
	}
});
3.读取属性的特性

Object.getOwnPropertyDescriptor()方法可以取得给定属性的描述符。

接收两个参数:属性所在的对象和要读取其描述符的属性名称。

var book = {};
Object.defineProperties(book, {
	_year: {
		writable: true,
		value: 2004
	},

	edition: {		
		writable: true,
		value: 1
	},

	year: {
		get: function(){
			return this,_year;
		},
		set: function(newValue){
			if (newValue>2004){
				this._year = newValue;
				this.edition += newValue - 2004;
			}
		}
	}
});
//读取数据属性的特性
var descriptor = Object.getOwnPropertyDescriptor(book, "_year");
alert(descriptor.value);	//2004
alert(descriptor.configurable);		//false
alert(typeof descriptor.get);	//"undefined"

//读取访问器属性的特性
var descriptor = Object.getOwnPropertyDescriptor(book, "year");
alert(descriptor.value);	//undefined
alert(descriptor.enumerable);		//false
alert(typeof descriptor.get);	//"function"
二、创建对象
1.工厂模式

使用简单的函数创建对象,为对象添加属性和方法,然后返回对象。

function creatPerson(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("Nicholals", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");
缺点:没有解决对象识别的问题,即怎样知道一个对象的类型。这个模式后来被构造函数模式所取代。
2.构造函数模式

可以创建自定义引用类型,可以像创建内置对象实例一样使用new操作符。

function Person(name, age, job){
	this.name = name;
	this.age = age;
	this.job = job;
	this.sayName = function(){
		alert(this.name);
	};
}
var person1 = new Person("Nicholals", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
//按照惯例,构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。
将构造函数当做函数

//当做构造函数使用
var person = new Person("Nicholals", 29, "Software Engineer");
person.sayName();	//"Nicholals"
//作为普通函数调用
Person("Greg", 27, "Doctor");		//添加到window
window.sayName();		//"Greg"
//在另一个对象的作用域中调用
var o = new Object();
Person.call(o, "Kristen", 25, "Nurse");
o.sayName();	//"Kristen"	
缺点:它的每个成员都无法得到复用,包括函数
3.原型模式

使用构造函数的prototype属性来指定那些应该共享的属性和方法。

1)理解原型对象
无论什么时候只要创建了一个新函数,就会根据一定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性是一个指向prototype属性所在函数的指针。

function Person(){
	Person.prototype.name = "Nicholas";	
	Person.prototype.age = 29;	
	Person.prototype.job = "Software Engineer";
	Person.prototype.sayName = function(){
		alert(this.name);
	};
}
var person1 = new Person();
person1.sayName();	//"Nicholas"
var person2 = new Person();
person1.sayName();	//"Nicholas"
alert(person1.sayName == person2.sayName);	//true
下图展示了以上使用Person构造函数和Person.prototype创建实例的代码的各个对象之间的关系:

isPrototypeOf()方法:可以确定对象之间是否存在这种关系,即实例指向构造函数的原型对象。

alert(Person.prototype.isPrototypeOf(person1));	//true
alert(Person.prototype.isPrototypeOf(person2));	//true
Object.getPrototypeOf()方法:返回[[Prototype]]的值。

alert(Object.getPrototypeOf(person1) == Person.prototype);	//true
alert(Object.getPrototypeOf(person1).name);	//"Nicholas"
注意:在实例中添加一个与实例原型中同名的属性,那么该属性将会屏蔽原型中的那个属性。只有使用delete操作符完全删除实例属性,才可以重新访问原型中的属性。

hasOwnProperty()方法:可以检测一个属性是存在于实例中(返回true),还是存在于原型中(返回false)。

2)原型与in操作符

in操作符会在通过对象能够访问给定属性时返回true。

因此同时使用hasOwnProperty()方法和in操作符,可确定该属性是存在于对象中还是原型中。

function hasPrototypeProperty(object, name){
	return !object.hasOwnProperty(name)&&(name in object);
}
在使用for-in循环时,返回的是所有能够通过对象访问的、可枚举的属性。
Object.keys()方法:接收一个对象作为参数,可返回一个包含所有可枚举属性的字符串数组。

Object.getOwnPropertyNames()方法:可以获得所有实例属性,无论它是否可枚举。

3)更简单的原型语法

function Person(){
}
Person.prototype = {
	name: "Nicholas",
	age: 29,
	job: "software Engineer",
	sayName: function () {
		alert(this.name);
	}
};
上面的原型写法本质上完全重写了默认的prototype对象,因此constructor也就变成了新对象的constructor属性,不再指向Person函数。
可以使用下面这种方式特意将它设置回适当的值。
function Person(){
}
Person.prototype = {
	constructor: Person,
	name: "Nicholas",
	age: 29,
	job: "software Engineer",
	sayName: function () {
		alert(this.name);
	}
};
但是,以这种方式重设constructor属性会导致它的[[Enumerable]]特性被设置为true,默认情况下,原生的constructor是不可枚举的。

因此使用Object.defineProperty()可解决上述问题。

function Person(){
}
Person.prototype = {
	name: "Nicholas",
	age: 29,
	job: "software Engineer",
	sayName: function () {
		alert(this.name);
	}
};
//重设构造函数,只适用于ES5兼容的浏览器
Object.defineProperty(Person.prototype, "constructor", {
	enumerable: false,
	value: Person
});
4)原型的动态性

实例中的指针仅指向原型,而不指向构造函数,因此重写原型对象时,已创建的实例仍然指向先前的原型对象。
5)原生对象的原型

所有原生引用类型(Object、Array、String等)都在其构造函数的原型上定义了方法,比如String.prototype中可以找到substring()方法,当然,你也可以通过原生对象的原型,定义新方法,不过,不推荐这么做。

6)原型对象的问题

  • 不能为构造函数传递初始化参数
  • 对于包含引用类型值的属性来说,实例不能拥有属于自己的全部属性
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("Nicholals", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
alert(person1.friends);	//"Shelby,Count,Van"
alert(person2.friends);	//"Shelby,Count"
alert(person1.friends === person2.friends);	//false
alert(person1.sayName === person2.sayName);	//true
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("Nicholals", 29, "Software Engineer");friend.sayName();
6.寄生构造函数模式

这个模式与工厂构造模式是一模一样的,它可以在特殊的情况下用来为对象创建构造函数。

7.稳妥构造函数模式

稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同:

  • 新创建对象的实例方法不引用this;
  • 不使用new操作符调用构造函数;
这种模式非常适合在某些安全执行环境下引用。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值