JavaScript面向对象的程序设计之创建对象

1、工厂模式

考虑到在ECMAScript中无法创建类,开发人员发明了一种函数,用函数来封装以特定接口创建对象的细节。如下面的例子所示。

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('zhangsan',25,'software engineer');
var person2 = createPerson('lisi',29,'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('zhangsan',25,'software engineer');
var person2 = new Person('lisi',29,'doctor');

构造函数模式与工厂模式的不同之处:(1)没有显式地创建对象。(2)直接将属性和方法 赋值给了this对象。(3)没有return语句。

注意:(1)创建新实例需要使用new操作符。(2)构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。

优点:可以识别对象类型。

alert(person1 instanceof Object);//true
alert(person1 instanceof Person);//true
alert(person2 instanceof Object);//true
alert(person2 instanceof Person);//true

在这个例子中,创建的person1person2既是Object的实例(所有对象均继承自Object),又是Person的实例。

问题:使用构造函数的主要问题,就是每个方法(sayName()方法)都要在每个实例上重新创建一遍。解决办法是把函数定义转移到构造函数外部,设置成全局的sayName函数,但是又有新问题,在全局作用域定义的函数实际上只能被某个对象调用,另外,如果对象需要定义很多方法,那么就要定义很多个全局函数。

3、原型模式

我们创建的每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象。这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。
好处:使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。

function Person(){
}
Person.prototype.name = 'zhangsan';
Person.prototype.age = 25;
Person.prototype.job = 'software engineer';
Person.prototype.sayName = function(){
    alert(this.name);
}

var person1 = new Person();
person1.sayName();//'zhangsan'
var person2 = new Person();
person2.sayName();//'zhangsan'

问题:所有实例在默认情况下都取得相同的属性值,在实例上添加一个同名属性将会屏蔽原型中的那个属性。如下例所示,访问person1.name时,先在实例上搜索,如果在实例上确实存在,就返回实例的值而不必再搜索原型了。在实例上添加的属性只会阻止我们访问原型中的那个属性,但不会修改原型的那个属性。

function Person(){
}
Person.prototype.name = 'zhangsan';
Person.prototype.age = 25;
Person.prototype.job = 'software engineer';
Person.prototype.sayName = function(){
    alert(this.name);
}
var person1 = new Person();
var person2 = new Person();
//hasOwnProperty()方法可以检测一个属性是存在于实例中,还是原型中,在实例中时返回true
alert(person1.hasOwnProperty("name"));//false
person1.name = 'lisi';
alert(person1.name);//'lisi'——来自实例
alert(person1.hasOwnProperty("name"));//true
alert(person2.name);//'zhangsan'——来自原型
delete person1.name;//delete操作符可以完全删除实例属性
alert(person1.name);//'zhangsan'——来自原型

最大的问题:原型中所有属性是被很多实例共享的,这种共享对于函数非常合适,对于那些包含基本值的属性,通过在实例上添加一个同名属性,可以隐藏原型中的对应属性。然而,对于包含引用类型值得的属性来说,问题就比较突出了。如下:

参考:js中的基本类型和引用类型

function Person(){
}
Person.prototype = {//更简单的原型语法
    constructor:Person,//所有的原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个纸箱prototype属性所在函数的指针。简单的原型语法会造成constructor属性不再指向Person,特意设置constructor的值为Person,保证通过该属性能够访问到适当的值。
    name:'zhangsan',
    age:25,//基本类型值属性
    job:'software engineer',//基本类型值属性
    firends:['lisi','wangwu'],//引用类型值属性
    sayName:function(){
        alert(this.name);
    }
}
var person1 = new Person();
var person2 = new Person();
person1.friends.push('liliu');
alert(person1.friends);//'lisi,wangwu,liliu'
alert(person2.friends);//'lisi,wangwu,liliu'

实例一般都是要有属于自己的全部属性,这个问题正是很少有人单独使用原型模式的原因所在。

4、组合使用构造函数模式和原型模式

创建自定义类型的最常见方式

构造函数模式用来定义实例属性,原型模式用于定义方法和共享的属性。结果每个实例都会有自己的一份实例属性的副本,同时又共享着对方方法的引用,最大限度地节省了内存。另外,这种混杂模式还支持向构造函数传递参数。

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ['wangwu','liliu'];
}
Person.prototype = {
    constructor : Person,
    sayName : function(){
        alert(this.name);
    }
}
var person1 = new Person('zhangsan',25,'software engineer');
var person2 = new Person('lisi',29,'doctor');
person1.friends.push('xiaoming');
alert(person1.friends);//'wangwu,liliu,xiaoming'
alert(person2.friends);//'wangwu,liliu'

5、动态原型模式
把所有信息都封装在构造函数中,在构造函数中初始化原型。可以通过检查某个应该存在的方法是否有效来决定是否需要初始化原型。只需要检查一个初始化后应该存在的任何属性或方法。还可以用instanceof操作符确定类型。

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    if(typeof this.sayName ! = "function"){
        Person.propotype.sayName = function(){
            alert(this.name);
        }
    }
}

6、寄生构造函数模式
除了使用new操作符并把使用的包装函数叫做构造函数外,其实和工厂模式一模一样。假设我们想创建一个具有额外方法的特殊数组,由于不能直接修改array构造函数,因此可以使用这个模式,缺点是不能依赖instanceof操作符来确定对象类型。
7、稳妥构造函数模式
所谓稳妥对象,指的是没有公共属性,而且其方法也不引用this的对象。稳妥构造函数模式和寄生构造模式类似,但有两点不同:(1)创建新对象的实例方法不引用this。(2)不使用new操作符调用构造函数。
注意除了调用构造函数中的方法是无法访问其数据成员的。

猜你喜欢

转载自blog.csdn.net/xuedan1992/article/details/80920315