JS简记-原型一
js中的对象大多数都会有一个__proto__属性,该属性指向一个对象,当使用this查找属性时,就会沿着__proto_链查找,知道找到为止,否则返回undefined。
下面首先来看一下最为基础的__proto__,也即Object的prototype(Object是一个函数对象,js中所有函数都会有一个prototype属性)。
var o1= { a: 1 }; var o2 = new Object(); o2.a = 2;
以上代码在全局对象上添加了o1和o2两个属性,这两个属性分别指向两个对象,可以看到这两个对象的内容完全相同,并且都拥有一个__proto__。
以o1为例来看__proto__对象:
其中hasOwnProperty、isPrototypeOf、toString、valueOf都是对象比较常见的方法。
__defineGetter__/__defineSetter__用来定义getter/setter,语法为obj.__defineGetter__(prop, func)/obj.__defineSetter__(prop, fun)。但是这两个方法已被废弃不推荐使用了,现在应该使用字面量或defineProperty来定义getter/setter,比如为对象o定义属性a的getter方法:
1 var o = { 2 get a(){ 3 return 2; 4 } 5 } 6 //或者 7 var o = Object.create(Object.prototype); 8 Object.defineProperty(o, "a", { 9 get: function(){ 10 return 2; 11 } 12 });
__lookupGetter__/__lookupSetter用来查找某个属性的getter/setter方法,同样已被废弃不推荐使用,语法是obj.__lookupGetter__(sprop)/obj.__lookupSetter__(sprop),现在推荐使用Object.getOwnPropertyDescriptor:
var o { get a(){ return 1; } } var getterOfA = Object.getOwnPropertyDescriptor(o, "a").get;
get __proto__/set __proto__看名字就知道其是用来访问__proto__属性的(大多数对象都会拥有一个__proto__属性,在存取该属性时,就会顺着__proto__链找到这两个方法并调用。使用Object.create(null)创建的对象没有__proto__,也就没有这两个方法,我们只能通过Object.setPrototypeOf为该对象增加__proto__,所以猜测这两个方法其实就是在调用Object.getPrototypeOf和Object.setPrototypeOf)。
下面来进一步看一下__proto__。
1 var o = { 2 a: 1 3 } 4 o.__proto__ = true;//没效果,number/string/boolean/undefined全无效 5 o.__proto__ = Object.create(null, { 6 b: { 7 value: 2 8 } 9 });//生效,说明__proto__的setter会判断接收到的值类型,如果是基础类型则忽略,只有对象类型才生效 10 console.log(Object.getOwnPropertyDescriptor(o, "__proto__"));//undefined,说明__proto__并不属于o的属性 11 console.log(Object.getOwnPropertyDescriptor(Object.prototype, "__proto__"));//正常输出,说明__proto__是Object.prototype的属性(默认以getter/setter形式存在)
控制台输出如下,由于__proto__的setter的存在,o的__proto__确认改变了:
由上分析可知,对于Object子对象来说,__proto__不属于其正常属性,但js引擎会顺着这个特殊的__proto__组成的链查找this.xxx,说明__proto__是引擎内部与对象其他属性独立的一个数据结构。
为了进一步验证__proto__是引擎内部与对象其他属性独立的一个数据结构,进行以下实验
1 efineProperty(Object.prototype, "__proto__", { 2 value: Object.create(null),//原本应该为Object.getOwnPropertyDescriptor(Object.prototype, "__proto__").get() 3 writable: true 4 });//将Object.prototype中__proto__的getter/setter修改为value 5 Object.prototype.c = 3; 6 o = { 7 a: 1 8 } 9 o.__proto__ = Object.create(null, { 10 b: { 11 value: 2 12 } 13 })//由于已经将Object.prototype中__proto__的getter/setter修改为value,所以这里对__proto__的设置不存在内部逻辑,仅仅是对属性的赋值 14 console.log(Object.getOwnPropertyDescriptor(o, "__proto__"));//正常输出,说明已经设置了属性__proto__ 15 console.log(o.b);//undefined,说明js引擎并没有顺着属性__proto__查找属性d,如果顺着查找应该输出4。 16 console.log(o.c);//3,说明存在属性__proto__的同时,js引擎还维护着一个特殊的__proto__,而该__proto__在对象创建时由js引擎通过Object.prototype提供。 17 console.log(o);
控制台输出,由于此时对__proto__的赋值操作相当于对普通属性赋值,所以o的特殊__proto__并没有改变(使用Object.setPrototypeOf可以改变),新增加的属性__proto__却没有显示,但通过Object.getOwnPropertyDescriptor发现其确实存在:
可见__proto__并不是对象的一个普通属性,如果想修改一个对象的__proto__也不仅仅是修改该属性那么简单。之所以我们可以像修改属性那样修改__proto__,是因为Object.prototype中为__proto__提供了getter/setter,如果没有这对方法(比如var o = Object.create(null),o就没有这对方法),我们就需要使用Object.setPrototypeOf来设置__proto__了,此时如果仍旧使用普通属性赋值操作,则只会对普通属性__proto__赋值,而不是那个特殊的__proto__。
注意与函数对象的prototype属性区分,prototype是函数对象的一个普通属性,Object的prototype不可配置且不可写(不可写意味着Object.prototype不可被赋其他值,但可以对其增加属性,Object.prototype.a=1),普通函数的prototype不可配置但可写。