您的位置:首页 > Web前端 > JavaScript

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

2016-12-06 16:59 309 查看
一、理解对象

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操作符调用构造函数;

这种模式非常适合在某些安全执行环境下引用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: