JavaSctipt之prototype原型深入分析: prototype的属性是副本,引用,还是固定的查找方式?
//
JavaSctipt之prototype原型是什么原理呢?实例被创建时,它是复制原型,得到原型属性的副本,还是使实例的属性引用原型的属性值,
还是实例被创建时,根本任何没有与原型同名的属性,仅靠"向原型查找"这种固定方式访问原型呢?
请看例子分析:
01_修改prototype的属性
02_增加prototype的属性
03_向实例增加prototype的同名属性
---------------------------------------------------------------------------------------------------------华丽的分分隔线---------------------------------------------------------------------------------------------------------
<script type="text/javascript"> var Person = function() { }; Person.prototype.username = new String("zhangsan"); var person1 = new Person(); var person2 = new Person(); alert("实例创建后,person1.username: "+person1.username); alert("实例创建后,person2.username: "+person2.username); //修改原型的属性值: Person.prototype.username = new String("lisi"); alert("原型被修改后,person1.username: "+person1.username); alert("原型被修改后,person2.username: "+person2.username); </script>
---------------------------------------------------------------------------------------------------华丽的分分隔线-----------------------------------------------------------------------------------------------------
//
<script type="text/javascript"> var Person = function() { }; Person.prototype.username = "zhangsan"; var person1 = new Person(); var person2 = new Person(); alert("实例创建后,person1.username: "+person1.username); alert("实例创建后,person2.username: "+person2.username); alert("向原型增加属性age前,person1.age: "+person1.age); alert("向原型增加属性age前,person2.age: "+person2.age); //向原型增加属性 Person.prototype.age = new String("18"); alert("向原型增加属性age后,person1.age: "+person1.age); alert("向原型增加属性age后,person2.age: "+person2.age); </script>
//
输出:
实例创建后,person1.username: zhangsan
实例创建后,person2.username: zhangsan
向原型增加属性age前,person1.age: undefined
向原型增加属性age前,person2.age: undefined
向原型增加属性age后,person1.age: 18
向原型增加属性age后,person2.age: 18
问题: 为什么向原型的增加属性后,2个实例person1,person2也增加了对应的属性呢?
假设1副本: N个实例创建时都持有原型的属性的副本,向原型增加age属性后,接着向N个实例都增加age属性,且值与原型一样(副本)
--要进行N次操作,可能性不高!
假设2引用: N个实例创建时都持有原型的属性的引用,向原型增加age属性后,接着向N个实例都增加age属性(引用),此时Person.prototype.age 和 person.age引用同一个对象String("18")
--要进行N次操作,可能性不高!
假设3固定查找方式:
N个实例创建时没有原型同名的属性,如person创建时,它自身并没有username属性!向原型增加age属性后,N个实例上同样没有age属性,当访问person1.age时,因为它自身并没有age属性,所以按照固定的查找方式来查找:
因为person1是Person的实例(person1 instanceof Person 得到的是true),所以查找Person.prototype.age的属性,如果没找到,继续向上一层原型查找,在这里,js引擎找到了Person.prototype.age属性,故输出!
--仅需修改prototype,然后按固定查找方式查找,能解译此例!
//
<script type="text/javascript"> var Person = function() { }; Person.prototype.username = new String("zhangsan"); var person = new Person(); alert("实例创建后,person.username: "+person.username); //向实例增加与prototype同名的属性 person.username = new String("lisi"); alert("向实例增加与prototype同名的属性后,person.username: "+person.username); //删除实例上的username属性,然后访问[实例].username得到的会是undefined吗? delete person.username; alert("删除实例上的username属性后,person.username: "+person.username); </script>
//
输出:
实例创建后,person.username: zhangsan
向实例增加与prototype同名的属性后,person.username: lisi
删除实例上的username属性后,person.username: zhangsan
问题: 向实例增加与prototype同名的属性后(person.username = new String("lisi")),然后又删除该属性后,再次访问person.username,为什么得到的不是undefined,而是与原型相同的属性值呢?
假设1副本:
person实例创建时持有原型的属性的副本,然后将实例的username修改为String("lisi"),当删除person.username属性后
又同时向实例增加原型的属性的副本username=new String("zhangsan"),所以再次访问person.username的值也是"zhangsan"..
--删除person的username属性后又向person增加属性值为String("zhangsan")副本的username属性,显然不太可能嘛!
假设2引用:
person实例创建时持有原型的属性的引用,此时Person.prototype.username 和 person.username 都是引用同一个对象String("zhangsan"),person.username = new String("lisi")执行后,person.username引用新的对象 String("lisi"),当删除person.username属性后,又同时将person.username重新引用Person.prototype.username引用的对象String("zhangsan")!
--删除person的username后,又同时向person增加引用String("zhangsan")的username属性,显然不太可能!
假设3固定查找方式:
person实例创建时没有与原型同名的username属性,person.username = new String("lisi")表示向person实例增加与原型同名的username属性,它引用新对象String("lisi"),此时Person.prototype.username引用对象String("zhangsan"),而person.username则引用String("lisi"),此时访问person.username时,因为person自己都有username属性,所以直接输出lisi,当delete person.username后再访问person.username,因为它自身并没有username属性,所以按照固定的 查找方式来查找: 因为person是Person的实例(person instanceof Person 得到的是true),所以查找Person.prototype的属性,如果没找到,继续向上一层原型查找, 在这里,js引擎找到了Person.prototype.username属性,故输出!
--就是当person.username属性存在时,它就会"屏蔽"了原型的username属性,删除person.username后,原型的username"屏蔽"解除,比较合理!
---------------------------------------------------------------------------------------------------华丽的分分隔线-----------------------------------------------------------------------------------------------------
总结:从自身延原型链向上查找的方式
从自身延原型链向上查找的方式:
访问对象object.xxx的属性时,如果object自己没有xxx属性,则向上延原型链查找,如果找到,则输出,没找到,则输出undefined.
PS: 因为每个对象和原型都有一个原型(注:原型也是一个对象 ),对象的原型指向对象的父,而父的原型又指向父的父,我们把这种通过原型层层连接起来的关系撑为原型链。这条链的末端一般总是默认的对象原型。
如图,var person = new Person() 后,得到的person实例 与 原型Person.prototype 是这样的关系,初始时,person并没有username或age的属性! person.username执行时,因为自身没有username属性, 所以通过向上查找原型的属性得到的!
看图就明白了,关键是:原型的.xxx属性 与 实例.xxx属性 是两个不同的变量,不过是同名而已!
执行person.username = new String("lisi")后,person得到了username属性,既然person得到了username属性,所以没必要向原型查找了,此时alert(person.username)输出是lisi,而alert(person.age)还是要向原型查找,输出的是18....
原型真正魅力体现在多个实例共用一个通用原型的时候。原型对象(注:也就是某个对象的原型所引用的对象 )的属性一旦定义,就可以被多个引用它的实例所继承(注:即这些实例对象的原型所指向的就是这个原型对象 ),这种操作在性能和维护方面其意义是不言自明的!
-----------------------------------------
PS: 下面,我们来看看什么是原型链? <<原型链深入分析>>