4 javascript 面向对象的程序设计

一.理解对象
1.属性类型
ES5在定义只有内部才用的特性时,描述了属性的各种特征。为了表示特性时内部值,该规范把它们放在了两队括号中,例如[[Enumerable]]
javascript中有两种属性:数据属性和访问器属性
1.数据属性
[[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为true
[[Enumberable]]:表示能否通过for-in循环访问属性。默认为true
[[Writable]]:表示能否修改属性的值。默认为true
[[Value]]:包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存到这个位置。这个特性的默认值为undefined

要修改属性默认的特性,必须使用ES5的Object.defineProperty( )方法。这个方法接受三个参数:属性所在的对象、属性的名字和一个描述符对象
Object.defineProerty(person, "name", {
writable: false,
value: "Nicholas"
});




alert(person.name); // "Nicholas"
person.name = "Greg";
alert(person.name); // "Nicholas"


一般而言,可能没必要利用Object.defineProperty()方法提供的这些高级能能,不过,理解这些概念对理解Javascript对象却非常有作用


2.访问器属性
访问器属性有如下4个特性:
[[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,能否把属性修改为数据属性,默认为true
[[Enumerable]]:表示能否通过for-in循环返回属性。对于直接在对象上定义的属性,这个值默认为 true
[[Get0]]:在读取属性时调用的函数。默认值为undefined
[[Set]]: 在写入属性时调用的函数。默认值为undefined
访问器属性不能直接定义,必须使用Object.defineProerty()来定义。请看下面的例子。
var book = {
_year: 2004,
edition: 1
};
Object.defineProerty(book, "year", {
get: function(){
return this._year;
}
set: function(newValue){
this._year = newValue;
this.edition += newValue - 2004;
}
});

book.year = 2005;
alert(book.edition); // 2

2.定义多个属性
由于对象定义多个属性的可能性很大,ES5又定义了一个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.读取属性的特性
var descriptor = Object.getOwnPropertyDescriptor(book, "_year");
descriptor.value;
descriptor.configurable;

二.理解对象
虽然Object构造函数或对象字面量都可以用来创建对象,但这些方式有个很明显的缺点:使用同一个接口创建很多对象,会产生大量重复的代码
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("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");
工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎么样知道一个对象的类型)

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("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

1.将构造函数当作函数
构造函数与其他函数的唯一区别,就在于调用它们的方式不同。不过,构造函数毕竟也是函数,不存在定义构造函数的特殊语法。任何函数只要通过new操作符来调用,
那么它就可以作为构造函数

// 当作构造函数
var Person = new Person("Nicholas", 29, "Software Engineer");
person.sayName(); // "Nicholas"

// 作为普通函数
Person("Greg", 27, "Doctor"); //添加到window
window.sayName(); // "Greg"

// 在另一对象的作用域中调用
var o = new Object();
Person.call(o, "Kristen", 25, "Nruse");
o.sayName(); // "Kristen"

2.构造函数的问题
重复定义:
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = new Function("alert(this.name)"); //与声明函数在逻辑上是等价的
}
改进:
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}

function sayName(){
alert(this.name);
}

var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

新的问题:
如果对象需要定义很多方法,那么就要定义很多个全局函数,于是我们这个自定义的引用类型就丝毫没有封装性可言了

3.原型模式
我们创建的每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。
如果按照意思来理解,那么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();
person2.sayName(); // "Nicholas"

alert(person1.sayName == person2.sayName); // true 

在此,我们将sayName()方法和所有属性直接添加到了Person的prototype属性中,构造函数变成了空函数。即使如此,也仍然可以通过调用构造函数来创建对象,而且新
对象还会具有相同的属性和方法。但与构造函数模式不同的是,新对象的这些属性和方法是由所有实例共享的。换句话说,person1和person2访问的都是同一组属性和同一个
sayName()函数。要理解原型模式的工作原理,必须先理解javascript中原型函数的性质。
1.理解原型函数
无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会
自动获得一个constructor(构造函数)属性,这个属性是一个指向prototype属性所在函数的指针。就拿前面的例子来说,Person.prototype.constructor指向Person。而通过
这个构造函数,我们还可继续为原型函数添加其他属性和方法。
虽然我们无法访问到[[Prototype]],但可以通过isPrototypeOf()方法来确定对象之间是否存在这种关系。从本质上讲,如果[[Prototype]]指向调用isPrototypeOf()方法
的对象(person.prototype),那么这个方法就返回true,如下:
alert(Person.prototype.isPrototypeOf(person1)); //true
alert(Person.prototype.isPrototypeOf(person2)); //true

使用hashOwnProperty()方法可以检测一个属性是否存在于实例中,还是存在于原型中

2.原型与in操作符
有两种方式使用in操作符,单独使用和在for-in循环中使用。在单独使用时,in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中。
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();
var person2 = new Person();

alert(person1.hasOwnProperty("name")); // false
alert("name" in person1); // true

person1.name = "Grey";
alert(person1.name); // "Grey" ——来自实例
alert(person1.hasOwnProperty("name"); // true
alert("name" in person1); // true

在使用for-in循环时,返回的是所有能够通过对象访问的、可枚举的属性,其中即包括存在于实例中的属性,也包括存在于原型中的属性。屏蔽了原型中不可枚举属性的实例
属性也会在for-in循环中返回,因为根据规定,所有开发人员定义的属性都是可枚举的——只有在IE8及更早版本中例外

var o = {
toString: function(){
return "My Object";
}
};

for (var prop in o){
if (prop == "toString"){
alert("Funound toString"); // IE中不会显示
}
}

要取的对象上所有可枚举的实例属性,可以使用object.keys():
function Person(){
}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
}

var keys = Object.keys(Person.prototype);
alert(keys); // "name,age,job,sayName"

var p1 = new Person();
p1.name = "Rob";
p1.age = 31;
var p1keys = Object.keys(p1);
alert(p1); // "name,age"

若要得到所有实例属性,无论它是否可枚举,可以使用Object.getOwnPropertyNames()
var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys); // "constructor,name,age,job,sayName"

3.更简单的原型语法
function Person(){
}

Person.prototype = {
name : "Nicholas",
age : 29,
job : "Software Engineer",
sayName : function(){
alert(this.name);
}
};
注意,这种写法从本质上重写了prototype,因此constructor属性不再指向Person了,而是对象的constructor属性(Object构造函数)

4.原型的动态性
可随时为原型添加属性和方法,并且修改能够立即在所有对象实例中反映出来
var friend = new Person();

Person.prototype.sayHi = function(){
alert("hi");
}
friend.sayHi(); // "hi"(没有问题)

但重写整个原型对象,切断了构造函数与最初原型之间的联系。实例中的指针仅指向原型,而不指向构造函数

function Person(){
}

var friend = new Person();

Person.prototype = {
constructor : Person;
name : "Nicholas",
age : 29,
job : "Software Engineer",
sayName : function(){
alert(this.name);
}
};

friend.sayName(); // error

5.原声对象的原型
例如在Array.prototype中可以找到sort()方法,而在String.prototype中可以找到substring()方法,不介意修改原声对象的原型的属性和方法

6.原声对象的问题
值相同、被共享
function Person(){
}

Person.prototype = {
constructor : Person;
name : "Nicholas",
age : 29,
job : "Software Engineer",
friend : ["Shelby", "Court"],
sayName : function(){
alert(this.name);
}
};

var person1 = new Person();
var person2 = new Person();

person1.friend.push("Van");

alert(person1.friend); // "Shelby,Court,Van"
alert(person2.friend); // "Shelby,Court,Van"
alert(person1.friend === person2.friend); // true

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, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

person1.friends.push("van");
alert(person1.friend); // "Shelby,Court,Van"
alert(person2.friend); // "Shelby,Court"
alert(person1.friends === person2.friends); // false
alert(person1.sayName === person2.sayName); // true

5.动态原型模式
function Person(name, age, job){
// 属性
this.name = name;
this.ageg = age;
this.job = job;

// 方法
if (typeof this.sayName != "function"){
person.Prototype.sayName = function(){
alert(this.name);
};
}
}

var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();

6.寄生构造函数模式

7.稳妥构造函数模式

3.继承
javascript只支持实现继承,而且其实现继承主要是依靠原型链来实现的
1.原型链
function SuperType(){
this.propert = true;
}
SuperType.prototype.getSuperValue = function(){
return this.propert;
}

function SubType(){
this.subproperty = false;
}

//继承了SuperType
SubType.prototype = new SuperType();

SubType.prototype.getSuperValue = function(){
return this.subproperty;
};

var instance = new SubType();
alert(instance.getSuperValue()); // true;

1.别忘记默认的原型
Object默认也有一个原型

2.确定原型和实例的关系
instanceof和isPrototypeOf

3.谨慎的定义方法
必须在SuperType的实例替换原型之后,再重定义方法
不能使用对象字面量创建原型方法,因为这样做就会重写原型链

4.原型链的问题
1.在通过原型实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性了
2.没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数

2.借用构造函数
function SuperType(){
this.colors = ["red", "blue", "green"];
}
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"

1.传递参数
相对于原型链而言,借用构造函数有一个很大的优势,即可以在子类构造函数中向超类构造函数传递参数
2.问题
方法都在构造函数中定义

3.组合继承
function SuperType(name){
this.name = name;
this.colors = ["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;
SubType.prototype.sayAge = function(){
alert(this.age);
};

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

var instance2 = new SubType("Greg", 27);
alert(instance2.colors); // "red,blue,green"
instance1.sayName(); // "Greg"
instance1.sayAge(); // 27

4.原型式继承
Object.create();

5.寄生式继承

6.寄生式组合继承
function inheritPrototype(subType, superType){
var prototype = Object(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
subType.prototype = prototype; //指定对象
}

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;
}

inheritPrototype(SubType, SuperType);

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

四.总结
Javascript支持面向对象(OO)编程,但不使用类或者借口。对象可以在代码执行过程中创建和增强,因此具有动态性而非严格定义的尸体。在没有类的情况下,可以采用下列模式
创建对象。
工厂模式,使用简单的函数创建对象,为对象添加属性和方法,然后返回对象。这个模式后来被构造函数模式所取代
构造函数模式,可以创建自定义引用类型,可以像创建内置对象实例一样使用new操作符。不过,构造函数模式也有缺点,即它的每个成员都无法得到复用,包括函数。由于函数
可以不局限于任何对象(即与对象具有松散耦合的特点),因此没有理由不再多个对象共享函数
原型模式,使用构造函数的prototype属性来指定那些应该共享的属性和方法。组合使用构造函数模式和原型模式,使用构造函数定义实例属性,而使用原型定义共享的属性和方法

javascript主要通过原型链实现继承。原型链的构建是通过将一个类型实例赋值给另一个构造函数的原型实现的。这样,子类型就能够访问超类型的所有属性和方法,这一点与基
与类的继承很相似。原型链的问题是对象共享实例所有继承的属性和方法,因此不适宜单独使用。解决这个问题的技术是借用构造函数,即在子类构造函数的内部调用超类型构造函数。
这样就可以做到每个实例都具有自己的属性,同时还能保证只使用构造函数模式来定义类型。使用最多的继承模式是组合继承,这种模式使用原型链继承共享的属性和方法,而通过借用
构造函数继承实例属性。
此外,还可以在下列可供选择的继承模式
原型式继承,可以在不必预先定义构造函数的情况下实现继承,其本质是执行对给定对象的浅复制。而复制得到的副本还可以得到进一步改造
寄生式继承,与原型式继承非常相似,也是基于某个对象或某些信息创建一个对象,然后增强对象,最后返回对象。为了解决组合继承模式由于多次调用超类型构造函数
而导致的低效率问题,可以将这个模式与组合模式继承一起使用
寄生组合式继承,集寄生,式继承和组合式继承的优点与一身,是实现基于类型继承的最有效方式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值