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不可配置但可写。

posted @ 2018-05-02 22:15  holoyong  阅读(153)  评论(0编辑  收藏  举报