创建对象

这篇文章主要是根据JavaScript高级程序设计第6章总结一下JavaScript创建对象的几种方法

创建Object实例

var o=new Object();
o.name='John';
o.age=15;
o.job='Engineer';
o.sayName=function(){
    alert(this.name);
}

这样子每次添加一个属性或者方法都要写一次对象名,很麻烦,于是就有下面这种方法。

对象字面量

var o={
    name:'John',
    age:15,
    job:'Engineer',
    sayName:function(){
        alert(this.name);
    }
}

事实上,用上面的两种方法来创建对象都很不方便,除非只是创建单个对象,但是如果要创建大量类似的对象,这个过程则会产生大量的重复代码,所以就发明了一种函数,用函数来封装以特定接口创建对象的细节,也就是抽象化创建具体对象的过程然后用函数封装起来,这种设计模式就叫工厂模式。

工厂模式

function createPerson(name,age,job){
    var o=new Object();
    o.name=name;
    o.age=age;
    o.job=job;
    o.sayName=function(){
        alert(this.name);
    }
}
var person1=createPerson('John',15,'Engineer');
var person2=createPerson('Mike',15,'Student');

工厂模式虽然解决了创建多个相似对象的问题,但是没有解决对象识别的问题,即无法知道一个对象的类型。

构造函数模式

function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.job=job;
    this.sayName=function(){
        alert(this.name);
    }
}
var person1=new Person('John',15,'Engineer');
var person2=new Person('Mike',15,'Student');

构造函数与工厂模式不同之处:

  1. 没有显示创建对象
  2. 直接将属性和方法赋给this对象
  3. 没有return语句

而且为了区分构造函数和普通函数,构造函数函数名首字母大写

要创建Person的新实例,必须使用new操作符,且会经历下面4个步骤:
(1) 创建一个新对象
(2) 将构造函数的作用域赋给新对象
(3) 执行构造函数中的代码
(4) 返回新对象

而且构造函数创建的对象都有一个constructor(构造函数)属性,它默认值指向它的构造函数,例如

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

constructor最初是用来标识对象类型的,但是,提到检测对象类型,还是instanceof更可靠一些。这个例子中创建的对象既是Obejct的实例,又是Person的实例,而这一点可以通过instanceof证实。

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

这便解决了对象识别的问题。

扩展:

任何函数,只要通过new操作符来调用,那它就是作为构造函数,否则,就是普通函数。

Person('Greg',27,'Doctor');
window.sayName();//'Greg'
window.age;//27

作为普通函数调用时,this的作用域为window对象,因此属性和方法都添加到window对象中去了。我们也可以在其他对象的作用域中调用:

var o=new Object();
Person.call(o,'Susan',22,'Nurse');
o.sayName();//'Susan'

扩展结束

尽管构造函数解决了对象识别的问题,但是,它也还存在问题,就是每个方法都在每个实例中重新创建了一遍,但这些方法都是一样的,没有必要每创建一个实例就创建一次,这样很重复。可以把方法提到构造函数外:

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

这样子,方法就可以被实例所共享使用了,但是却是只能被这个构造函数的实例对象所使用,而方法却是全局方法,这使得全局作用域有点名不副实。而且,如果对象需要定义很多方法,就要定义很多个全局函数,这就没有了“封装性”特点。而这个问题可以通过使用原型模型来解决。

原型模式

我们创建的每一个函数都有一个prototype属性,它是一个指针,指向一个对象,这个对象包含了该函数实例所共享的属性和方法。也就是说,我们可以把构造函数中的属性和方法都放在它的原型对象中去,那么它的每一个实例就都可以访问了。

function Person(){
}
Person.prototype.name="John";
Person.prototype.age=15;
Person.prototype.job="Engineer";
Person.prototype.sayName=function(){
    alert(this.name);
}
var person1=new Person();
var person2=new Person();
person1.sayName();//'John'
person2.sayName();//'John'

这样就解决了无法共享方法的问题,但是!!原型对象中的所有方法和属性都是共享的,也就是说,每一个实例对象在原型对象中获得的属性值都是一样的,有时候这并不是我们想要的,我们希望我们创建的实例对象有自己的特点,而不是一模一样,这样没有意义,因此,我们应该组合起来使用。

扩展:
  • 每次创建一个新函数,就会自动创建一个prototype属性,这个属性指向该函数的原型对象,而原型对象会自动获得一个constructor属性,这个属性指向prototype属性所在的函数。但是如果重写原型对象,constructor值会改变。
Person.prototype.constructor===Person;//true
  • 当调用构造函数创建一个新实例后,该实例的内部会包含一个属性[[prototype]],指向构造函数的原型对象。
  • 使用isPrototypeOf()方法可以确定参数对象的原型是否为调用对象
Person.prototype.isPrototypeOf(person1);//true;
  • 使用Object.getPrototypeOf()可以获得对象的原型
Object.getPrototypeOf(person1)==Person.prototype;//true
Object.getPrototypeOf(person1).name;//'John'
  • 每次代码读取对象的属性时,首先在对象实例中查找,找到则返回,找不到则到原型对象中去查找,找到则返回,否则,如果该构造函数继承了其他函数,则继续往上面的原型对象中查找。具体到继承中再继续讲解。
  • 实例可以访问原型对象中的值,却不能重写原型中的值,只会在实例对象中创建一个与原型对象同名的属性屏蔽掉它。
function Person(){
}
Person.prototype.name="John";
Person.prototype.age=15;
Person.prototype.job="Engineer";
Person.prototype.sayName=function(){
    alert(this.name);
}
var person1=new Person();
var person2=new Person();
person1.name='zcn';//创建了同名属性,屏蔽了原型对象的同名属性
alert(person1.name);//'zcn'——来自实例
alert(person2.name);//'John'——来自原型

也就是只会阻止我们访问原型中的属性,但不会修改原型中的属性。我们可以用delete完全删除实例属性,从而重新访问到原型中的属性。

delete person1.name;
alert(person1.name);//'John'——来自原型
  • 一直用Person.prototype来添加属性和方法很累赘,其实可以使用对象字面量来重写整个原型对象。
function Person(){
}
Person.prototype={
    name:'John',
    age:29,
    job:'Engineer',
    sayName:function(){
        alert(this.name);
    }
};

但是这样子会改变constructor的值,使它指向了Object,因为重写之后,实际上是Object构造函数造了它。但instanceof仍然返回之前的结果,只是constructor不能确定对象的类型了。

var friend=new Person();
alert(friend instanceof Object);//true
alert(friend instanceof Person);//true
alert(friend.constructor==Person);//false
alert(friend.constructor==Object);//true

因为它是可配置的,所以如果需要,我们可以把它改回来。

function Person(){
}
Person.prototype={
    constructor:Person;//这种方法创建,三大特性默认值为true
    name:'John',
    age:29,
    job:'Engineer',
    sayName:function(){
        alert(this.name);
    }
};

重设constructor值之后,它的特性[[Enumetable]]被设置成true,默认是false,我们可以把它改回来。

function Person(){
}
Person.prototype={
    name:'John',
    age:29,
    job:'Engineer',
    sayName:function(){
        alert(this.name);
    }
};
Object.defineProperty(person.prototype,'constructor',{
    enumerable:false,//该方法定义三大特性默认为false,此处不写也可以
    value:Person
});
  • 原型的动态性,我们可以随时给原型对象添加新的属性和方法,而且都可以在实例中反映出来, 不管是先创建实例还是先添加属性方法,因为实例的[[prototype]]是一个指针。而如果是重写原型对象,则不会影响在这重写之前的创建的对象,只会反映在重写之后创建的对象中。

    扩展结束

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

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

var person1=new Person('John',15,'Engineer');
var person2=new Person('Mike',21,'Student');

person1.sayName();//'John'
person2.sayName();//'Mike'
person1.sayName===person2.sayName;//true

如此以来,每个实例对象既有自己的一份实例属性的副本,又同时共享着对原型对象中共享属性和方法的引用。
这种模式是目前在ES中使用最广泛、认同度最高的一种创建自定义类型的方法。可以说,这是用来定义引用类型的一种默认模式。

动态原型模式

独立的构造函数和原型,看起来有点奇怪,动态原型模式便将所有信息都封装在构造函数中,而通过检查任意一个应该存在的方法是否有效,来决定是否要初始化原型。

function Person(name,age,job){
    //属性
    this.name=name;
    this.age=age;
    this.job=job;
    if(typeof this.sayName != 'function'){
    //if(!this.sayName instanceof Function){
        //初始化原型,此处必须使用Person,this的作用域是实例,而实例是无法给prototype增加方法或属性
        Person.prototype.sayName=function(){
            alert(this.name);
        }
    }
}

附加

寄生构造函数模式

在上述几种模式都不适用的情况下,可以用这种模式,比如我们想创建一个具有额外方法的数组,又由于我们无法直接修改Array的构造函数,此时可使用这种模式。

function SpecialArray(){
    var arr=new Array();
    //
    arr.push.apply(arr,arguments);
    arr.toPipedString=function(){
        return this.join("|");
    };
    return arr;
}
var colors=new SpecialArray('red','blue');
colors.toPipedString();//"red|blue"

但这种模式只是封装了创建对象的代码,它创建的对象与该构造函数是没有任何关系的,因此也就不能用instanceof来确定对象类型,所以,在上述模式可以使用的情况下,不建议使用这种模式。

稳妥构造函数模式

这种模式与上一种模式类似,它是用于创建稳妥对象的一种模式,稳妥对象就是没有公共属性,它的方法也不引用this的对象,而且创建时并不使用new,适用于安全环境中。

function Person(name,age,job){
    var o=new Object();
    o.name=name;
    o.age=age;
    o.job=job;
    o.sayName=function(){
        alert(name);
    }
    return o;
}
var p=Person('John',28,'Engineer');
p.sayName();//'John'

这种模式创建的对象,属性值只能是通过调用特定的方法来访问,比如例子中的name只能是sayName()来访问,即使添加其他方法,也不可能有别的方法访问传入到构造函数中的原始数据。与寄生构造函数类似,instanceof对于这种对象也没有意义。

总结

总结一下思路,创建Object构造方法的实例或者对象字面量的方法,创建多个相似对象时代码重复,于是采用工厂模式,工厂模式又不能进行对象识别,于是有了构造函数,构造函数不能共享函数,每一个对象都重新创建了功能一样的方法,很占用空间也很多余,于是出现了原型模式,原型模式中的属性和方法都是被共享的,每一个对象实例拥有的属性值都一致,并不能完全令我们满意,于是就有了组合使用构造函数和原型模式,这样子每一个对象既有自己的属性,又能访问到原型对象中共享的(属性和)方法。最后,还可以把原型模式写入到构造函数中,即动态构造函数

最后附加了两种特殊的模式,寄生构造函数模式稳妥构造函数模式,寄生构造函数模式是其他模式都不能用的情况下再考虑使用,而稳妥构造函数模式则是在安全环境中,或者防止数据被其他应用程序改动时使用。这两种模式创建的对象都跟构造函数没有关系。

这就是创建对象的九种模式,如果文中有什么表达错误的地方,希望大家指出来,谢谢大家。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值