日期:2014-05-16  浏览次数:20365 次

JavaScript 深入理解对象创建方式

???? 在JS中,为了改进语言熟悉程度,也引入了“构造函数”这样的机制,但是在JS中构造函数本身也是函数,只是可以用来创建对象。在JS中创建对象,也需要用到new操作符,它的实际过程是这样的:

????

1:创建一个对象(这一过程完全在new操作符之前)
2:将构造函数的作用域赋给该对象(因此this也就指向了该对象)
3:执行构造函数(创建属性和方法)
4:返回新对象

?

??? 值得一提的是在Python的“构造方法”中,机制与这几乎一致(只是Python将这个方法移到类中,并称之为__init__方法)

?

????创建对象有以下几种方式,包括工厂模式、构造函数模式、原型模式等等。

??? 首先来看工厂模式:

???

function createPerson(person,age){
    var o=new Object();
    o.person=person;
    o.age=age;
    o.sayName=function(){
        alert(o.person);
    }
    return o;
}
var ted=createPerson("ted",15);
var marry=createPerson("mary",11);
ted.sayName();//输出ted
marry.sayName();//输出mary

??

?? 工厂模式的缺点就是创建了一个对象,但创建了两个引用。同样它并没有解决对象识别的问题,即怎么样知道一个对象的类型。

?? 再来看构造函数模式:

???

function Person(person,age){	
      this.person=person;
      this.age=age;
      this.sayName=function(){
	alert(this.person);
     }
}
var ted=new Person("ted",15);
var marry=new Person("marry",13);
ted.sayName();//输出ted
marry.sayName();//输出marry
//按照惯例,这里构造函数首字母采用大写

??

? ?注意在这种模式下,这两个对象都具有一个构造函数属性,称为constructor(实质上是Person构造函数的原型对象的属性,稍后将介绍原型对象),例如:

??

alert(ted.constructor);
alert(marry.constructor);
//不出意外的话,会打印整个Person函数

?? 而在第一种方式下,尝试打印它们的constructor属性话,会直接打印object的构造函数。

?

?? 但是这样随之而来,也引出了一个问题,ted和marry都创建了一个具有完全功能的函数对象,但这两个函数对象却不是同一个。可以像下面这样稍微改造下:

??

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

?

?? 这样不用创建重复的函数对象了,但还是引来另外一个问题。如果这个对象拥有很多函数,那么这样一一声明全局函数,但这个全局函数实质上只能供Person使用,所以这丝毫没有体会到封装性的好处。

?? 是时候到重量级“人物”出场了--原型模式!

?? 先来了解下什么是原型。我们创建的每一个函数都有一个Prototype属性,这一个属性是一个对象,它包括一个constructor属性,这个属性又重新指向了拥有该Prototype属性的对象。是不是感觉晕掉了?来看看它们的图。

?? 假如我们这样创建了2个对象,其实际内部运行机制是什么样的?先看下下面代码:

??

function Person(){	
}
Person.prototype.name="ted";
Person.prototype.age=11;
Person.prototype.sayName=function(){
	alert(this.name);
}
var ted=new Person();
var marry=new Person();
ted.sayName();//输出ted
marry.sayName();//输出ted

?

?? 它的基本运行流程是这样的:

???

???

?

?

?

?

?? 上面图是我自己手绘的,呵呵,请诸兄不要笑话。

?? 要检测对象和原型之间的关系的方法是:

??

alert(Person.prototype.isPrototypeOf(ted));//弹出true
alert(Person.prototype.isPrototypeOf(marry));//弹出true

??

? 每当代码读取对象的一个属性的时候,都会执行一次搜索。目标是具有给定名字的属性。方法是:先查看对应实例里,是否有该名字,如果没有,则查看对应的原型对象里,是否有该属性。例如:

???

??

ted.name="haha";
alert(ted.name);//输出haha
alert(marry.name);//输出ted

?

?? 从以上代码可以看出,的确实例属性遮盖了原型对象的属性。

?? 但是可以利用delete操作符删除这个实例属性,例如:

??

delete ted.name;
alert(ted.name);//输出ted

?

??? 后来的Python,也借鉴了这种做法,不过Python采用的是del操作符,它们的思想都是类似的,都是让对象的引用次数减一。

??? 要验证一个属性是否是实例属性还是系统属性,请查看:

???

alert(ted.hasOwnProperty("name"));//输出true
alert(marry.hasOwnProperty("name"));//输出false

?

??? 在Python中则对应hasAttribute()方法,这是顶层类object的方法。

??? 在JS中可以利用in操作符和hasOwnProperty()两个操作结合起来,来判断一个属性究竟是存在于对象还是实例中。

???

alert(checkNameInClass(ted,"name"));//输出true
alert(checkNameInPrototype(ted,"name"));//输出false
alert(checkNameInClass(marry,"name"));//输出false
alert(checkNameInPrototype(marry,"name"));//输出true
function checkNameInClass(obj,name){
    return obj.hasOwnProperty(name);
}
function checkNameInPrototype(obj,name){
   return !obj.hasOwnProperty(name)&&(name in obj);
}

?

?但是上面的原型方法略显复杂,可以改用对象字面量方法来定