首页 > 代码库 > 对Javascript中原型的深入理解

对Javascript中原型的深入理解

  理解原型对象
在Javascript中无论什么时候,只要创建一个新的函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象(这个对象的用途是包含可以有特定类型的所有实例共享的属性和方法)。如果按照字面上的意思来理解,那么原型属性就是通过调用构造函数而创建的那个对象的实例的原型对象。

默认的情况下,所有的原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针。如下图


function Person(){
}


上图中创建了一个构造函数Person(function Person()),Person函数中有一个prototype的属性指向Person的原型对象,在原型对象中有一个constructor的属性指向了Person函数,所有可以通过new Person()创建对象。

创建了自定义的构造函数后,其原型对象默认只会取得constructor属性,至于其他的方法,都是从Object继承而来的。其中上图中Person.prototype .constructor指向Person.而通过这个构造函数,我们可以继续为原型对象添加其他的属性和方法。如下图:

 

上图的js代码就是

Person.prototype={
  name:"Nicholas",
  age:23,
  sayName:function(){
    return this.name;
  }
};

当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。ECMA-262第5版中管这个指针叫[[Prototype]],虽然在脚本中没有标准方式访问,我们可以借由Chrome在每个对象上都支持一个属性_proto_(私有属性不可见),这个链接的存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。


var p1=new Person();


当创建一个新的p2之后依然会有一个_proto_属性指向Person的原型(Persion.prototype),换句话说每个实例与构造函数没有直接的关系,虽然这实例都不包含属性和方法,但我们却可以调用p1.name查找对象属性的个过程来实现。此时如果通过p2.name设置属性值后会在对象自己的内存空间中存储name的值,当调用sayName()方法的时候在寻找name时,在自己空间中找到之后就不会去原型对象中查找(注意:原型中的值是不会被替换,仅仅是在查找的时候被覆盖)


var p1=new Person();
 alert(p1.name);//Nicholas
 var p2=new Person();

原型的检测
在所有实现中都无法访问到[[Prototype]],但是我们可以通过isPrototypeOf()方法来确定对象直接是否存在这种关系。从本质上讲,如果[[Prototype]]指向调用isPrototypeOf()方法的对象(Person.prototype)。
ECMAScript5中增加一个新的方法.叫做Object.getPrototypeOf(),这个方法返回[[Prototype]]的值
== 可以检测某个对象的constructor
检测某个属性是否是自己是自己的属性用(不是对象原型中的实现)hasOwnProperty(),可以清楚的知道什么时候访问的自己的属性,什么时候访问的是原型属性了。
delete可以用来删除实例属性(自己的属性)
in操作:有两种方式操作in操作符。单独的使用for-in循环中使用。单独是用时,in
会通过对象能够访问给的属性的返回值,无论属性存在于实例中还是原型中

//检查某个对象是否是某个函数的原型
  alert(Person.prototype.isPrototypeOf(p1));//true
   ECMAScript5中增加一个新的方法.叫做Object.getPrototypeOf(),在所有支持的实现中,这个方法返回[[Prototype]]的值
   alert(Object.getPrototypeOf(p1)==Person.prototype);
   alert(Object.getPrototypeOf(p1).name);
  //alert(p1.constructor==Person);
  //alert(p2.hasOwnProperty("name"))
  delete p2.name;
  //alert("name" in p1)//来自原型
   p1.id=12;
 // alert("id" in p1);//来自实例
  alert(hasPrototypeProperty(p1,"name"))
 /**
  检查某个属性是否在原型中存在
  */
  function hasPrototypeProperty(obj,prop){
    return ((!obj.hasOwnProperty(prop))&&(prop in obj));
  }

在上面给原型添加的属性中存在着已问题constructor属性不指向Person了。上面写法的本质是完全重写了默认的prototype对象,因此constructor属性也变成了新的对象的constructor属性(指向Object构造函数),不在指向Person函数,尽管instanceof操作还是正确返回结果,但是通过constructor已经无法确定对象的类型了。
   为了避免这个问题可是手动指定constructor
alert(p1 instanceofPersion);alert(p1.constructor==Persion)
原型重写存在的问题
 由于在原型中查找值得过程是一次搜索,我们对原型对象所做的任何修改都能够立即从实例上反映出来,即使先创建实例后修改原型也是可以的

var p2=new Person();
  Person.prototype.sayHi=function(){
    alert(this.name);
  }
  p2.sayHi();//可以获取值

可以获取值是因为实例与原型之间的松散连接关系,当我们调用p2.saHi()时,首先回去实例中搜索名为sayHi的属性,在没有找到的情况下,会继续搜索原型,因为实例和原型之间只不过是一个指针,而不是副本,因此可以在原型中找到新的sayHi属性并返回保存在哪里的函数。
如果是重写整个原型就不一样了。我们知道调用构造函数是会为实例添加一个指向最初原型的[[Prototype]]指针。把原型修改为另外一个对象就等于切断了构造函数和最初的原型之间的联系。切记:实例中的指针仅指向原型。而不是构造函数。

function Person(){
}

  var p1=new Person();
  
  //所有的"范围对象"都继承自这个对象  重写原型对象
  Person.prototype.sayHi=function(){
    alert(this.name);
  }
  //所有的"范围对象"都继承自这个对象  重写原型对象
  Person.prototype={
  constructor:Person,//显示的设置构造函数的反向引用
  name:"Nicholas",
  age:23,
  sayName:function(){
    return this.name;
  }
};
  
  p1.sayHi();//undefined
  alert(p1.sayName());//报错
 var p2=new Person(); 
 alert(p2.sayName());//Nichola

重写之前与重写之后如图:




原型存在的问题
原型对象中的属性是被很多实例共享的,这个共享对于函数是非常合适的,对于原型中基本值得属性是没有问题的但是,如果属性值包含引用类型值就会有一定的问题了。

function Person(){
}

    Person.prototype={
  constructor:Person,//显示的设置构造函数的反向引用
  name:"Nicholas",
  age:23,
  friends:["Shelby","Cunrt"],
  sayName:function(){
    return this.name;
  }
};
 var p1=new Person();
 var p2=new Person();
 
 p1.friends.push("Jack");
 alert(p1.friends);//"Shelby","Cunrt","Jack"
 alert(p2.friends);//"Shelby","Cunrt","Jack"
 alert(p1.friends==p2.friends);//true

解决这个为题做好的方法如下:

function Person(name,age,friends){
  this.name=name;
  this.age=age;
  this.friends=friends;
}

    Person.prototype={
  constructor:Person,//显示的设置构造函数的反向引用
  sayName:function(){
    return this.name;
  }
};
 var p1=new Person("Tom",20,"Shelby");
 var p2=new Person("Alisa",20,["Cunrt","Jack"]);
 alert(p1.friends);//"Shelby"
 alert(p2.friends);//"Cunrt","Jack"
 alert(p1.friends==p2.friends);//false