一、基本性质
function obj(){ this.name1 = ‘可以被delete删除‘; } obj.prototype.name2 = ‘不能被delete删除‘; obj.prototype.name = ‘成功调用getName()方法‘; obj.getName = function(){ console.log(this.prototype.name); } var o = new obj(); delete o.name1 //输出:‘undefined‘, 成功删除属性name1 console.log(o.name1); delete o.name2 //输出:‘不能被delete删除‘,因为对象没有原型,访问不到属性name2 console.log(o.name2); //报错:‘Uncaught TypeError: undefined is not a function’,因为,当obj被实例化为o时,obj 就把它的 prototype 赋给了 o 的 __proto__,并且 o 的 constructor 指向了 obj o.getName(); //输出:‘成功调用getName()方法‘,因为getName方法是挂载在o.constructor上的 o.constructor.getName();
结论:对象没有原型,而构造器(Constructor)有(<constructor>.prototype指向原型),对象没有“持有某个原型”的问题,只有“构造自某个原型”的问题。
//取原型对象 proto = Object.prototype; //列举对象成员并计数 var num = 0; for (var n in proto) { num++; } //显示计数:0 alert(num);
结论:object()构造器的原型是一个空的对象。
obj1 = new Object(); obj2 = {};
那么,obj1和obj2也是“空的对象”,因为它们都是从Object.prototype复制出了一个“对象”的映像来。
结论:空的对象是所有对象的基础
//弹出 Object alert(typeof null); //false alert(null instanceof Object); //true alert({} instanceof Object); //true alert(Object instanceof Object);
注意:空对象(null) != 空的对象
空对象(null):是一个保留字,它属于对象类型,但这个对象是空值的,它没有方法和属性。
空的对象:是一个标准的,通过Object()构造的对象实例。此外,对象直接量也会隐性的调用Object()构造实例。空的对象具有对象的一切特性,它可以存取预定义属性和方法(如toString、valueof等),instanceof运算也会返回true
小结
原型的含义指:如果构造器有一个原型对象A,那么由这个构造器所创建的实例(Instance)都必然复制自A。
“原型也是对象实例”是一个关键的性质,这是他与“类继承体系”最根本的区别。举例说明:“类”可以是一个内存块或者一段描述文本,而不必是一个有对象特性(例如可以调用方法或存取属性)的结构。
二、 写复制
上图说明每构造一个实例,都从原型中复制出一个实例,新的实例和原型占用了相同的内存空间,造成了内存空间的浪费,有没有策略让新实例和原型共用一个内存空间呢,这就是——写复制(一种欺骗系统的技术:操作系统中的动态链接库(DLL),它的内存区总是写时复制的),下面是机制图:
obj1和obj2都是指向原型的引用,这时它们并不占用内存,当需要写对象obj2的属性时,就会复制一个原型的映像出来,并使以后的操作指向该原型就行了。这种方法的优点就是只有第一次写操作的时候才会分配内存,以后就不会了,因为访问映像与访问原型的效率是一致的;缺点就是如果需要大量写操作的时候,就不太经济了。
接下来,重点来了,JavaScript的实现机制是:仅当写某个实例的成员时,将成员的信息复制到实例映像中(需进一步论证),但当写对象属性时(如obj2.value=10),会产生一个包含属性的成员列表,此时,obj2仍然是一个指向原型的引用,并没有创建对象实例,所以不会占用内存,但obj2需要维护一张成员列表。机制图如下: