第6章 面向对象的程序设计

6.1 理解对象

创建Object实例的方式有两种,第一种是使用new操作符后跟一个构造函数来创建的。

var person=new Object();/*创建了Object引用类型的一个实例,并把该实例保存在变量person中。使用的构造函数是Object,它只为新对象定义了默认的属性和方法。*/

person.name=”Nicholas”;

person.age=29;

person.sayName=function(){ };

……

另一种方法是使用对象字面量表示法。

var person={

      name:”Nicholas”,

      age:29,

      sayName: function(){}

};

       使用对象字面量语法时,如果留空其花括号,则可定义只包含默认属性和方法的对象:

var person={ };

person.name=”Nicholas”;

……

//与new Object()相同

访问对象属性:使用点表示法. 和方括号”[]”表示法。方括号表示法的优点是可以通过变量访问属性,如果属性名字包含空格等会导致语法错误的字符,或者属性名使用的是关键字和保留字,也可以使用方括号表示法。

如 var propertypeName=”name”;

       alert(person[protertypeName])// “Nicholas”

       person[“first  name”]=”Nicholas”;

除非必须使用变量访问属性名,建议使用点表示法。

6.1.1 属性类型

ECMAScript中有两种属性:数据属性和访问属性。为了表示是内部特性,把它们放在两对方括号中

1、数据属性

包含4个特性

[[Congigurable]]: 表示能否重新定义属性,即能否使用delete删除属性,能否修改属性的特性,或能否把属性修改为访问属性。直接在对象上定义的属性,它们的这个属性默认为true。

[[Enumerable]]: 表示能否通过for-in循环访问属性。直接在对象上定义的属性,它们的这个属性默认为true。

[[Writable]]: 表示能否修改属性的值。直接在对象上定义的属性,它们的这个属性默认为true。

[[Value]]: 包含这个属性的数据值,读取属性的时候,从这里读取,写入属性的时候,用于保存新值。默认值为undefined。直接在对象上定义的属性,它们的这个属性值为指定的值。

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

 

var person={};

Object.defineProperty(person, “name”,{writable: false, value: “Nicholas”});

alert(person.name);//”Nicholas”

person.name=”Greg”;

alert(person.name);//”Nicholas”因为name的writable特性为false,所有name的值不可修改。

2、访问器属性

访问器属性不包含数值,包含一对个getter和setter函数。

访问器属性也包含四个特性

[[Configurable]]:

[[Enumerable]]:

[[Get]]:在读取属性时调用的函数。默认值为undefined

[[Set]]:在写入属性时调用的函数。默认值为undefined

访问器属性不能直接定义,必须使用Object.defineProperty()来定义,使用该方法创建的新属性,如果不指定,configurable,enumerable和writable特性的默认值都是false。

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+=newVable-2004;

}

}

});

book.year=2005;

alert(book.edition);//2

 

6.1.2 定义多个属性

Object.defineproperties()方法可以通过描述符一次定义多个属性,这个方法接收两个参数,第一个参数是对象,第二个参数时要添加或修改的属性

var book={};

Object.defineProperties(book,{
    _year:{
        writable: true;   
        value:2004 
    },
    edtion:{
        writable: true,
        value:1},
    year:{
        get: function(){
        return this._year;
    },
        set: function(newValue){
           if (newValue>2004){
           this._year=newValue;
           this.edition+=newVable-2004;
          }
        }
    }
});

6.1.3读取属性的特性

Object. getOwnPripertyDescripor()方法可以取得该属性的描述符

var descriptor= Object. getOwnPripertyDescripor(book, “_year”);

alert(descriptor.value);//2004

alert(desctiptor.configurable);//false

6.2 创建对象

使用同一个接口创建很多对象,会产生大量的重复代码

6.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(“cuiwei”,28,”engineer”);
var person2=createPerson(“Greg”,27,”Doctor”);

6.2.2 构造函数模式

function Person(name, age, job){ //使用Person()函数代替createPerson()函数
       this.name=name; //没有显示的创建对象o
       this.age=age;  //将属性和方法直接赋给了this对象
       this.job=job;
       this.sayName=function(){
              alert(this.name);
}
}
var person1=new Person(“cuiwei”,28,”engineer”);// this-person1
var person2=new Person(“Greg”,27,”Doctor”);// this-person2

以这种方式定义的构造函数是定义在Global对象(在浏览器中是window对象)中的。

person1和person2都是Person的一个实例,这两个对象都包含一个constructor属性,改属性指向Person.

alert(person1.constructor==Person);//true

alert(person1.instanceof Person);// true

alert(person1 instanceof Object);//true

1、将构造函数当作函数

任何函数,只要通过new操作符来调用,那他就可以当做构造函数,不通过new操作符来调用那他和普通函数也没区别。但按照惯例,构造函数都是使用一个大写字母开头,非构造函数都是使用一个小写字母开头。

上例中的Person()函数可以使用下列任何一种方式来调用

var person=new Person(“cuiwei”,28,”Engineer”);//当做构造函数

var person=Person(“cuiwei”,28,”Engineer”);// 当做普通函数

Person(“cuiwei”,28,”Engineer”);//当做普通函数,添加到window

window.Sayname;//”cuiwei”

var o=new Object();

Person.call(o, “Greg”,28,”Engineer”);

o.sayName();//”Greg”

2. 构造函数的问题

每个方法都要在每个实例上重新创建一遍。

6.2.3 原型模式

每个函数都要一个prototype(原型)属性,这个属性是一个指针,指向一个对象(原型对象)。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。

function Person(){ 
}
Person.protorype.name=”Nicholas”;
Person.prototype.age=29;
Person.prototype.job=”Engineer”;
Person.prototype.sayName=function(){
   alert(this.name);
};
var person1=new Person();
person1.sayName();// “Nicholas”
person2.sayName();//”Nicholas”

1.理解原型对象

alert(Person.prototype.constructor==Person);//true

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

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

不能通过实例对象重写原型中的值,否则会屏蔽原型对象中的值。

function Person(){ 
}
Person.protorype.name=”Nicholas”;
Person.prototype.age=29;
Person.prototype.job=”Engineer”;
Person.prototype.sayName=function(){
       alert(this.name);
};
var person1=new Person();
       var person2=newPerson();
       person1.name=”Greg”;
       alert(person1.hasOwnProperty(“name”));//true
       alert(person1.name);//”Greg”编译器会先搜索实例中的属性
       alert(person2.name);//”Nicoholas”
       deleate person1.name;
       alert(person1.name);//”Nicoholas” 来自实例
       alert(person1.hasOwnProperty(“name”));//false

使用hasOwnProperty()方法可以检测一个属性是否存在于实例中

2、原型与in操作符

function Person(){ 
}
Person.protorype.name=”Nicholas”;
Person.prototype.age=29;
Person.prototype.job=”Engineer”;
Person.prototype.sayName=function(){
              alert(this.name);
};
var person1=new Person();
alert(“name”in person1);//true
person1.name=”Greg”;
alert(“name”in person1);//true
deleate person.name;
alert(“name”in person1);//true 来自原型

Object.keys()方法,这个方法接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。

3、更简单的原型方法

function Person(){
}
Person.prototype={
   ame: “Nicholas”,
   age: 29,
   job: “Engineer”,
   sayName: function(){
       alert(this.name);
   };
}

使用此方法后constructor属性不再指向Person了,而是指向Object

var friend=new Person();

alert(friend instanceof Object);// true

alert(friend instanceof Person);// true

alert(friend.constructor==Person);//false

alert(friend.cinstructor==Object);//true

如果constructo的值很重要,可以使用如下方法,使用这种方式重设constructor属性会使他的[[Enumerable]]特性被设置为true。

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

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: “cuiwei”,
       age: 28,
       job: Engineer,
       sayName: function(){
              alert(this.name);
}
};//重写原型
friend.sayName();//error

5.原生对象的原型

所有原生引用类型(Object、Array、String等等)都在其构造函数的原型上定义了方法。通过原生对象的原型不仅可以取得所有默认方法的引用,而且也可以定义新方法。如下段代码就给基本包装类型String添加了一个名为startsWith()的方法。

String.prototype.startsWith=function(text){

       return this.indexOf(text)==0;

};

var msg=”Hello word”;

alert(msg.startsWith(“Hello”));//true

6.原型对象的问题

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

对于包含引用类型的属性来说,问题更严重

function Person(){}
Person.prototype={
friends:[“Shelby”,”Court”]
}
var person1=new Person();
var person2=new Person();
person1.friends.push(“Van”);
alert(person1.friends);//”Shelby,Court,Van”
alert(person2.friends);//”/Shelly,Court, Van”两个实例指共享一个原型

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,”Software Engineer”);
var person2=new Person(“Greg”,27,”Doctor”);
person1.friends.push(“Van”);
alert(person1.friends);//”Shelby,Count,Van”
alert(person2.friends);//”Shelby,Count”

person1和person2是Person的两个实例修改了person1.friends并不会影响person2.friends,因为他们分别引用了不同的数组。与原型对象不同,在 原型对象中, person1和person2也是Person的两个实例,但这两个实例都指向Person.prototype。

6.2.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);
              };//这段代码只会在初次调用构造函数时才会运行。

        }

}

这样只在sayName()方法不存在的情况下才会将它添加到原型中。

6.2.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 friend=new Person(“Nicholas”,29,”Engineer”);
这个模式可以在特殊情况下为对象创建构造函数。
function SpecialArray(){
       var values=new Array();
       values.push.applay(values, arguments);
       values.toPipedString=function(){
              return this.join(“|”)
       }
 return values;
}
var colors=new SpecialArray(“red”,”blue”,”green”);
alert(colors.toPipedString());//”red|blue|green”

6.2.7 稳妥构造函数模式

function Person(name, age, job){

       var o=new Object();

       //可以在这里定义私有变量和函数。添加方法     

o.sayName=function(){

alert(name);

};

return o;

}

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

6.3 继承

许多OO语言都支持两种继承方式:接口继承和实现继承。接口继承只继承方法名,而实现继承则继承实际的方法。ECMAScript只支持实现继承。

6.3.1原型链

利用原型让一个引用类型继承另一个引用类型的属性和方法。实例都包含一个指向原型对象的内部指针

实现原型链的基本模式:

function SuperType(){

       this.property=true;

}//定义一个超函数

SuperType.propotype.getSuperValue=function(){

       return this.property;

};

function SubType(){

       this.subproperty=false;

}//定义子函数

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

SubType.prototype.getSubValue=function(){

       return this.subproperty;

}

var instance=new SubType();

alert(instance.getSuperValue);//true

instance指向SubType的原型,SubType的原型又指向SuperType的原型。getSuperValue()方法仍然还在SuperType.prototype中,但property则位于SubType中。因为property是一个实例属性,而getSuperVale()则是一个原型方法。

调用instance.getSuperValue()会经历三个搜索步骤:1. 搜索实例;2.搜索SubType.prototype; 3. 搜索SuperType.prototype最后一步才会找到该方法。

1、别忘记默认的原型

 所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。

2、确定原型和实例的关系

alert(instance instanceof Object);// true

alert(instance instanceof SuperType);// true

alert(instance instanceof SubType);// true

alert(Object.prototype. isPrototypeOf(instance));//true

alert(SuperType.prototype.isPrototypeOf(instance));//true

alert(SubType.prototype.isPrototypeOf(instance));//true

3、谨慎的定义方法

子类型有时候需要覆盖超类型中的某个方法,或者添加超类型中不存在的某个方法,给原型添加方法的代码一定要放在替换原型(继承)语句之后

在通过原型链实现继承时,不能使用对象字面量创建原型方法,因为这样做会重写原型,是继承无效。

4、原型链问题

最主要的问题来自包含引用类型值的原型。

function SuperType(){

       this.colors=[“red”,”blue”,”green”];

}

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”

SubType.prototype是SuperType的一个实例,它会有一个SubType.prototype.colors属性,所有SubType都会共享这个实例。

在创建子类型的实例时不能像超类型的构造函数传递参数。继承与使用构造函数创建对象不同。

6.3.2借用构造函数

通过apply()和call()方法在新创建的对象上执行构造函数。

function SuperType(){

       this.colors=[“red”,”blue”,”green”];

}

function SubType(){

       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”,每个实例都有自己的colors属性副本

1、传递函数

相对于原型链,借用构造函数有一个很大的优势,可以在子类型中向超类型构造函数传递参数。

function SuperType(name){

       this.name=name

}

function SubType(){

       SuperType.call(this.”Nicholas”);

       this.age=29;

}

var instance=new SubType();

alert(instance.name);//”Nicholas”

alert(instance.age);//29

2、借用构造函数的问题

不能实现函数复用。在超类型中定义的方法对子类型而言也是不可见的

6.3.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.name);

};//增加自身原型的方法。不能使用表达式方式,否则会重写原型

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”

instance2.sayName();//”Greg”

instance2.sayAge();//27

两个不同的SubType实例分别拥有自己的属性包括colors属性,又可以使用相同的方法。

6.3.4原型式继承

道格拉斯·克罗克福德,必须有一个对象作为另一个对象的基础。把这个对象传给object函数,然后根据需求对得到的对象加以修改即可。

function object(o){

       function F(){}

       F.prototype=o;

       return new F();

}

var person={

       name:”Nicholas”,

       friends:[“Shelby”,”Court”,”Van”]

};//作为基础的对象

var anotherPerson=object(person);

anotherPerson.name=”Greg”;

anotherPerson.friends.push(“Rob”);

var yetAnotherPerson=object(person);

       yetAnotherPerson.name=”Linda”;

       yetAnotherPerson.friends.push(“Barbie”);

alert(person.friends);//”Shelly,court,Van,Rob,Barbie”

 

ECMAScript5通过新增Object.creat()方法规范化了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象和一个作为新对象定义额外属性的对象。在传入一个参数的情况下Object.creat()和object()方法的行为相同。

var person={

       name:”Nicholas”,

       friends:[“Shelby”,”Court”,”Van”]

};

var anotherPerson=Object.creat(person);

anotherPerson.name=”Greg”;

anotherPerson.friends.push(“Rob”);

var yetAnotherPerson=Object.creat(person);

       yetAnotherPerson.name=”Linda”;

       yetAnotherPerson.friends.push(“Barbie”);

alert(person.friends);//”Shelly,court,Van,Rob,Barbie”

Object.creat()方法的第二个参数与Object.defineProperties()方法的第二个参数格式相同,以这种方式定义的任何属性都会覆盖原型对象上同名属性。

var person={
     name:”Nicholas”,
     friends:[“Shelby”,”Court”,”Van”]
};
var anotherPerson=Object.creat(person,{
name:{
     value: “Greg”
}
});
alert(another.name);//”Greg”

6.3.5寄生式继承

function creatAnother(original){
       var clone=object(original);
       clone.sayHi=function(){
              alert(“hi”);//在原型式继承的基础上增加了自己的方法
};
return clone;
}
var person={
       name:”Nicholas”,
       friends:[“Shelby”,”Court”,”Van”]
};
var anotherPerson=creatAnother(person);//新对象不仅有person的所有属性和方法还有自己的sayHi()方法。
anotherPerson.sayHi();// Hi

6.3.6寄生组合式继承

通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。

function inheritPrototype(subtype. superType){
       var prototype=Object(superType.prototype);//创建对象
       prototype.constructor=subType;//增强对象
       subType.prototype=prototype;//指定对象
}
inheritPrototype()函数接收两个参数:子类型构造函数和超类型构造函数。
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;
}
inheritPrototype(SubType,SuperType);
SubType.prototype.sayAge=function(){
       alert(this.age);
}

这个例子的高效率体现在它只调用了一次Supertype构造函数

 

猜你喜欢

转载自blog.csdn.net/willard_cui/article/details/81193423