web前端之JavaScript高级程序设计三:面向对象的程序设计

web前端之JavaScript高级程序设计三:面向对象的程序设计

面向对象的语言有一个标志,那就是他们都有类的概念,而通过类可以创建任意多个具有相同属性和方法的对象。前面提到过, ECMAScript 中没有类的概念,因此它的对象也与基于类的语言中的对象有所不同。

每个对象都是基于一个引用类型创建的,也可以是开发人员定义的类型。

理解对象:

创建自定义对象的最简单方式就是创建一个 Object 的实例

var person=new Object();
person.name="FZW";
person.age=21;
person.job="SoftWare Engineer";

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

上面的例子创建了一个名为 person 的对象,并为它添加了三个属性(name、 age 和 job)和一个方法(sayName())。

现在的主流的书写方式,可以写成下面这样:

var person={
    name:"FZW"
    ,age=21
    ,job="soft"
    ,sayName:function(){
        alert(this.name);
    }
};

数据属性包含一个数据值的位置。在这个位置可以读取和写入值。数据属性有 4 个描述其行为的特性。
[[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为 true。
[[Enumerable]]:表示能否通过 for-in 循环返回属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为 true。
[[Writable]]:表示能否修改属性的值。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为 true。
[[Value]]:包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。这个特性的默认值为 undefined。

对于像前面例子中那样直接在对象上定义的属性,它们的[[Configurable]]、 [[Enumerable]]和[[Writable]]特性都被设置为 true,而[[Value]]特性被设置为指定的值。

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

var person={};
Object.defineProperty(person,"name",{
    writable:false,
    value:"FZW"
});
alert(person.name);
person.name="德玛西亚";
alert(person.name);

这个例子创建了一个名为 name 的属性,它的值”Nicholas”是只读的。这个属性的值是不可修改的,如果尝试为它指定新值,则在非严格模式下,赋值操作将被忽略;在严格模式下,赋值操作将会导致抛出错误。

可以多次调用 Object.defineProperty()方法修改同一个属性,但在把 configurable特性设置为 false 之后就会有限制了。在调用 Object.defineProperty()方法时,如果不指定, configurable、 enumerable 和writable 特性的默认值都是 false。

访问器属性不包含数据值;它们包含一对儿 getter 和 setter 函数(不过,这两个函数都不是必需的)。在读取访问器属性时,会调用 getter 函数,这个函数负责返回有效的值;在写入访问器属性时,会调用setter 函数并传入新值,这个函数负责决定如何处理数据。访问器属性有如下 4 个特性。
[[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。对于直接在对象上定义的属性,这个特性的默认值为true。
[[Enumerable]]:表示能否通过 for-in 循环返回属性。对于直接在对象上定义的属性,这个特性的默认值为 true。
[[Get]]:在读取属性时调用的函数。默认值为 undefined。
[[Set]]:在写入属性时调用的函数。默认值为 undefined。
访问器属性不能直接定义,必须使用 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);

以上代码创建了一个 book 对象,并给它定义两个默认的属性: _year 和 edition。 _year 前面的下划线是一种常用的记号,用于表示只能通过对象方法访问的属性。而访问器属性 year 则包含一个getter 函数和一个 setter 函数。 getter 函数返回_year 的值, setter 函数通过计算来确定正确的版本。因此,把 year 属性修改为 2005 会导致_year 变成 2005,而 edition 变为 2。这是使用访问器属性的常见方式,即设置一个属性的值会导致其他属性发生变化。

不一定非要同时指定 getter 和 setter。只指定 getter 意味着属性是不能写,尝试写入属性会被忽略。在严格模式下,尝试写入只指定了 getter 函数的属性会抛出错误。类似地,只指定 setter 函数的属性也不能读,否则在非严格模式下会返回 undefined,而在严格模式下会抛出错误。

定义多个属性:
由于为对象定义多个属性的可能性很大, ECMAScript 5 又定义了一个 Object.defineProperties()方法。利用这个方法可以通过描述符一次定义多个属性。这个方法接收两个对象参数:第一个对象是要添加和修改其属性的对象,第二个对象的属性与第一个对象中要添加或修改的属性一一对应。

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

以上代码在 book 对象上定义了两个数据属性(_year 和 edition)和一个访问器属性(year)。
最终的对象与上一节中定义的对象相同。唯一的区别是这里的属性都是在同一时间创建的。支持 Object.defineProperties()方法的浏览器有 IE9+、 Firefox 4+、 Safari 5+、 Opera 12+和Chrome。

读取属性的特性:

使用 ECMAScript 5 的 Object.getOwnPropertyDescriptor()方法,可以取得给定属性的描述符。这个方法接收两个参数:属性所在的对象和要读取其描述符的属性名称。返回值是一个对象,如果是访问器属性,这个对象的属性有 configurable、 enumerable、 get 和 set;如果是数据属性,这个对象的属性有 configurable、 enumerable、 writable 和 value。

var book={};
Object.defineProperties(book,{
    _year:{
        value:2004
    }
    ,edition:{
        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);
alert(descriptor.configurable);
alert(typeof descriptor.get);
var descriptor=Object.getOwnPropertyDescriptor(book,"year");
alert(descriptor.value);
alert(descriptor.enumerable);
alert(typeof descriptor.get);

对于数据属性_year, value 等于最初的值, configurable 是 false,而 get 等于undefined。对于访问器属性 year, value 等于 undefined, enumerable 是 false,而 get 是一个指向 getter函数的指针。

创建对象:

工厂模式:

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("FZW","21","H");
var person2=createPerson("李白","510","唐");

构造函数模式:

function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.job=job;
    this.sayName=function(){
        alert(this.name);
    };
}
var person1=new Person("FZW",21,"H");
var person2=new Person("李白","510","唐");

在这个例子中,Person()函数取代了createPerson()函数。Person中的代码有以下不同之处:
没有显式地创建对象;
直接将属性和方法赋给了 this 对象;
没有 return 语句。

函数名 Person 使用的是大写字母 P。按照惯例,构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。区别于 ECMAScript 中的其他函数;因为构造函数本身也是函数,只不过可以用来创建对象而已。

person1和person2的sayName()方法是不一样的。所以可以改成:

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

原型模式:

我们创建的每个函数都有一个 prototype(原型)属性,prototype 就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。

function Person(){};
// Person.prototype.name="FZW";
// Person.prototype.age=21;
// Person.prototype.job="H";
// Person.prototype.sayName=function(){
//  alert(this.name);
// };
Person.prototype={
    constructor:Person
    ,name:"FZW"
    ,age:21
    ,sayName:function(){
        alert(this.name);
    }
};
var person1=new Person();
alert(person1.name);
var person2=new Person();
person2.name="李白";//对象实例
person2.sayName();
alert(person1.sayName==person2.sayName);

将 sayName()方法和所有属性直接添加到了 Person 的 prototype 属性中,构造函数变成了空函数。即使如此,也仍然可以通过调用构造函数来创建新对象,而且新对象还会具有相同的属性和方法。但与构造函数模式不同的是,新对象的这些属性和方法是由所有实例共享的。换句话说,person1 和 person2 访问的都是同一组属性和同一个 sayName()函数。

动态原型模式:
可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。

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("FZW",21,"H");
friend.sayName();

这里只在sayName()方法不存在的情况下,才会将它添加到原型中。这段代码只会在初次调用构造函数时才会执行。此后,原型已经完成初始化,不需要再做什么修改了。不过要记住,这里对原型所做的修改,能够立即在所有实例中得到反映。因此,这种方法确实可以说非常完美。其中, if 语句检查的可以是初始化之后应该存在的任何属性或方法——不必用一大堆。if 语句检查每个属性和每个方法;只要检查其中一个即可。对于采用这种模式创建的对象,还可以使用 instanceof 操作符确定它的类型。

寄生构造函数模式:
这种模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。

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 firend=new Person("FZW",21,"H");
firend.sayName();

在这个例子中, Person 函数创建了一个新对象,并以相应的属性和方法初始化该对象,然后又返回了这个对象。除了使用 new 操作符并把使用的包装函数叫做构造函数之外,这个模式跟工厂模式其实是一模一样的。构造函数在不返回值的情况下,默认会返回新对象实例。而通过在构造函数的末尾添加一个 return 语句,可以重写调用构造函数时返回的值。这个模式可以在特殊的情况下用来为对象创建构造函数。

稳妥构造函数模式:
所谓稳妥对象,指的是没有公共属性,而且其方法也不引用 this 的对象。稳妥对象最适合在一些安全的环境中(这些环境中会禁止使用 this 和 new),或者在防止数据被其他应用程序(如 Mashup程序)改动时使用。稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同:一是新创建对象的实例方法不引用 this;二是不使用 new 操作符调用构造函数。

function Person(name,age,job){
    //创建要返回的对象
    var o=new Object();
    //可以在这里定义私有变量和函数

    //添加方法
    o.sayName=function(){
        alert(name);
    };
    //返回对象
    return o;
};

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

你可能感兴趣的:(web前端,JavaScript,javascript,web前端)