ECMA262 Edition5. 属性描述符和属性标识符(Property Descriptor,Property Identifier)

写在开头的话.

我每篇翻译,都尽量做到.分析和注解.能力有限,难免有错漏之处,欢迎您的指正.

此篇,应该是ES5 新增的一个重要概念的总论. 纵观,ES5通篇,都离不开这些新增概念.建议仔细读一读.虽然我个人认为,"老道".在ES5上,有些地方设计的并不合理.但总体来说.是一种极大的进步了.至于新特性的应用.大家将来慢慢体会. 


如果,您的屏幕不够宽,可能排版看起来会很有问题, 解决办法 :

  1. 换一个够宽的显示器,

      2. 提高分辨率

      3. 缩小浏览器显示文字.



8.10 属性描述符和属性标识符.     


     属性描述符(Property Descriptor)
     是用于解释某一个被命名的属性具体的操作规则的特性集. 属性描述符中的对应每个字段名都会有一个值.这玩意的具体定义在 8.6.1章节中. 其中任何一个字段都可以缺省或显式的设置.
 
     属性描述符还会被进一步以字段的实际用途来分类成如-数据属性描述符、访问器属性描述符.
     一个数据属性描述符.是那种字段名中有 [[Value]] [[Writable]]的描述符. 
     一个访问器属性描述符,则是那种字段中包含 [[Get]] 或 [[Set]]的描述符.
     任何一种属性描述符都可以有一叫做[[Enumerable]]的字段以及一个叫做[[Configurable]]的字段. 
     一个属性描述符.可能同时是一个访问器属性描述符,和数据属性描述符, 又或者两者都不是.  当两者都不是的时候,他就是一个一般属性描述符.也就是说,他既不是一个访问器属性描述符,也不是一个数据属性描述符. 而一个具备所有字段的,特性完整的描述符.可以成为像8.6.1章节中 表格5或6中所定义的,任何一种属性描述符.
     
     table5:
特性名
范围
描述
[[Value]]
任何ECMA语言定义的类型.
读属性时返回的值
[[Writable]]
布尔
该值如果为 false. 则任何试图修改这个属性的.ECMAScript 的代码.将被无视.
[[Enumerable]]
布尔
如果为true,则这个属性就可以被for in所枚举.参考(12.6.4章节)
其他情况,则视该属性为不可枚举的.
[[Configurable]]
布尔
如果为false,则其他删除该属性的操作、或修改该属性成为一个访问器属性,以及修改其任何特性(除了修改[[]Value])的操作.都将失败. 

     table:6
特性名
范围
描述
[[Get]]
object或undefined
如果值是一个Object,那么就必须是一个函数对象. 当属性被访问时,该对象的内部方法[[Call]]被无参数调用,并把返回值作为属性的值返回
[[Set]]
object或undefined
如果值是一个对象,那么就必须是一个函数对象.
每次访问这个属性,并对其赋值时,都会把赋值来的值作为参数,去调用该对象的内部方法[[call]].
这种赋值操作.
[[Enumerable]]
布尔
如果为true,则这个属性就可以被for in所枚举.参考(12.6.4章节)
其他情况,则视该属性为不可枚举的.

[[Configurable]]
布尔
如果为false,则其他删除该属性的操作、或修改该属性成为一个数据属性,以及修改其任何特性(除了修改[[]Value])的操作.都将失败. 
  
 
     为了便于表示一个属性描述符.我们可以使用一个类似 对象直接量的语法.来定义一个 属性描述符的值. 举个例子来说, 一个 属性描述符可以这样子:
          {(注1)
               [[Value]] : 42,
               [[Writable]] : false,
               [[Configurable]] : true
          }
     就定义了一个数据属性描述符.其中字段的顺序,是无所谓的. 任何没有显示列出的字段.将被赋予默认的值.(注2) 
 
     在规范性文本和算法中. 可以使用 点号表示法("." 即所谓对象成员访问运算符).去引用一个属性描述符的字段. 举个例子: 假设 D 是一个属性描述符.那么 D[[Value]] 就表示 D的[[Value]]字段.
 
          
 
     属性标识符(Property Identifier) 
     是用于把一个属性名,和一个属性描述符相关联的. 属性标识符的值是由(name,descriptor)一对值构成的. 其中 name是一个字符串. descriptor是一个属性描述符.
 
 
 
 
     下面列出的是一些与属性描述符有关的.抽象运算.
          
          8.10.1 IsAccessorDescriptor(Desc)
          当这个抽象运算 IsAccessorDescriptor 与一个属性描述符 Desc 进行运算, 就会按照下面的步骤来进行:
               1. 如果Desc是 undefined, 返回 false.
               2. 如果[[Get]]和[[Set]]都是缺省的,那么返回 false.
               3. 返回 true.
 
 
          8.10.2 IsDataDescriptor(Desc)
          当这个抽象运算 IsDataDescriptor 与一个属性描述符 Desc进行运算,就会按照下面的步骤来进行:
               1. 如果Desc是 undefined,返回false.
               2. 如果[[Value]]和[[Writable]]都是缺省的,则返回false.
               3. 返回true.
 
          
          8.10.3 IsGenericDescriptor(Desc)
          当这个抽象运算 IsGenericDescriptor 与一个属性描述符 Desc进行运算,就会按照下面的步骤来进行:          
               1. 如果Desc是 undefined,返回false.
               2. 如果IsAccessorDescriptor(Desc) 和 IsDataDescriptor(Desc) 都是false.则返回true.
               3. 返回false.
 
 
          8.10.4 FromPropertyDescriptor(Desc)(就是一个根据内部对象-属性描述符,创建一个ECMAScript对象,并返回这个对象的东东.)
          当这个抽象运算 FromPropertyDescriptor 与一个 属性描述符 Desc进行运算,就会按照下面的步骤进行:
          在后面的算法中,我们假设Desc是一个完整的属性描述符(即各个字段都显式列出的那种)就像调用内部方法[[GetOwnProperty]]所返回的东东(参见8.12.1章节)(注3)
               1. 如果Desc是 undefined,返回undefined.
               2. 令 obj 为 一个新创建的对象,就像 表达式 new Object() 这样子.其中Object 就是标准滴那个叫做 Object的 内置构造器.
               3. 如果isDataDescriptor(Desc)是true,则
                    a.  把 'value',属性器描述符-{[[Value]]:Desc.[[Value]],[[Writable]]:true,[[Enumerable]]:true,[[Configurable]]:true},以及false,三个东东作为参数
                         去调用obj的[[DefineOwnProperty]]方法.(注4)
                    b.  把 'writable',属性描述符-{[[Value]]:Desc.[[Writable]],[[Writable]]:true,[[Enumerable]]:true,[[Configurable]]:true},以及false,三个东东作为参数
                         去调用obj的[[DefineOwnProperty]]方法.
               4.否则,如果IsAccessorDescriptor(Desc)就一定是true.然后就
                    a.  把 'get',属性器描述符-{[[Value]]:Desc.[[Get]],[[Writable]]:true,[[Enumerable]]:true,[[Configurable]]:true},以及false,三个东东作为参数
                         去调用obj的[[DefineOwnProperty]]方法.
                    b.  把 'set',属性描述符-{[[Value]]:Desc.[[Set]],[[Writable]]:true,[[Enumerable]]:true,[[Configurable]]:true},以及false,三个东东作为参数
                         去调用obj的[[DefineOwnProperty]]方法.  
               5. 把 'enumerable',属性描述符-{[[Value]]:Desc.[[Enumerable]],[[Writable]]:true,[[Enumerable]]:true,[[Configurable]]:true},以及false,三个东东作为参数
                   去调用obj的[[DefineOwnProperty]]方法.
               6. 把 'configurable',属性描述符-{[[Value]]:Desc.[[Configurable]],[[Writable]]:true,[[Enumerable]]:true,[[Configurable]]:true},以及false,三个东东作为参数
                   去调用obj的[[DefineOwnProperty]]方法
               7. 返回 obj.
 
 
 
          8.10.5 ToPropertyDescriptor(obj)(与FromPropertyDescriptor相反,这玩意就是根据一个ECMAScript对象,来获取一个内部使用的 属性描述符.或者,也可以称作转换.)
          当这个抽象运算 ToPropertyDescriptor 与一个ECMAScript Desc对象进行运算,就会按照下面的步骤进行:
          1. 如果Type(obj)不是 Object,则抛出一个 TypeError 异常.
          2. 令 desc 为一个新创建的,没有任何字段的属性描述符.
          3. 如果 以'enumerable'作为参数,调用obj的内部方法[[HasProperty]],返回的结果是true,
              则
                    a.  令 enum 为 以'enumerable'作为参数,调用obj的内部方法[[Get]]的返回值.
                    b.  设置 desc.[[Enumerable]] 为 ToBoolean(enum)的返回值.(注5)
              
          4. 如果以'configurable'为参数,调用obj的内部方法[[HasProperty]],返回的结果是true,
              则
                    a.  令conf 为,以'configuable'为参数调用obj的内部方法[[Get]]的返回值.
                    b.  设置 desc.[[Configurable]]为 ToBoolean(conf)的返回值.
 
          5. 如果以'value'作为参数,调用obj的内部方法[[HasProperty]],返回的结果是true,
              则
                    a.  令 value为, 以'value'作为参数,调用obj的内部方法[[Get]]的返回值.
                    b.  设置 desc.[[Value]]的值为 value.
 
          6. 如果以'writable'为参数,调用obj的内部方法[[HasProperty]],返回的结果是true
              则
                    a.  令 writable为,以'writable'作为参数,调用obj的内部方法[[Get]]的返回值.
                    b.  设置 desc.[[Writable]]的值为 ToBoolean(writable)的返回值.
 
          7.  如果以'get'为参数,调用obj的内部方法[[HasProperty]],返回的结果是true
               则
                    a.  令 getter 为,以'get'作为参数,调用obj的内部方法[[Get]]的返回值.
                    b.  如果IsCallable(getter)(注6) 是 false,并且getter不是undefined,则抛出一个TypeError异常.
                    c.  设置desc.[[Get]]字段的值为 getter.
          8. 如果以'set'为参数,调用obj的内部方法[[HasProperty]],返回的结果是true
              则
                    a.  令 setter 为,以'set'作为参数,调用obj的内部方法[[Get]]的返回值.
                    b.  如果IsCallable(setter)是 false,并且setter不是undefined,则抛出一个TypeError异常.
                    c.  设置desc.[[Set]]字段的值为 getter.
          9.如果desc.[[Get]]或desc.[[Set]]任何一个存在(即使值是undefined.),则
               a.  如果desc.[[Value]]或desc.[[Writable]]中,任何一个存在,则抛出一个TypeError异常.(注7)
          10. 返回 desc.
 
 
 
          8.12.1 [[GetOwnProperty]](P)(同注3)
 
 
          8.12.2 [[GetProperty]](P)(原型链查找机制的内部逻辑实现.获取一个属性描述符.)
          当以属性名P作为参数,调用对象O的内部方法[[GetProperty]]时,就会按照下面的步骤进行:
          1. 令 prop 为以属性名P作为参数,调用对象O的内部方法[[GetOwnProperty]]的返回值(一个属性描述符)
          2. 如果 prop 不是undefined,则返回prop.
          3. 令 proto 为对象O的内部属性[[Prototype]].
          4. 如果 proto 是 null, 返回 undefined.(Object.prototype.[[Prototype]]是null)(注8)
          5. 以属性名P作为参数名,递归调用对象O的内部方法[[GetProperty]].
 

          8.12.3 [[Get]](P)(通过原型链查找机制,在制定对象上获取一个属性对应的值.)
          当以属性名P作为参数调用对象O的内部方法[[Get]]时,就按下面的步骤进行:
          1. 令 desc 为以属性名P作为参数,调用对象O的内部方法[[GetProperty]]的结果
          2. 如果 desc 是 undefined,则返回 undefined.
          3. 如果 isDataDescriptor(desc)是true,则 返回 desc.[[Value]].
          4. 其他情况,IsAccessorDescriptor(desc)就一定为true, 则令 getter 为 desc.[[Get]].
          5. 如果 getter 是 undefined. 则返回 undefined.
          6. 返回,以O作为 方法getter的 this值,并无参调用 getter.[[Call]]的结果.
 
 
           8.12.4 [[CanPut]](P)(判断对象的某属性,是否是可写的.此内部方法的特点是.如果对象自身,没有该属性信息,要查询原型链.以便决定是否可以使用[[Put]]内部操作.)
          当以属性名P作为参数调用对象O的内部方法[[CanPut]]时,就按下面的步骤进行:
          1. 令desc 为 以属性名P为参数,调用对象O的内部方法[[GetOwnProperty]]的结果.
          2. 如果 desc 不是 undefined, 则
                    a.  如果isAccessorDescriptor(desc) 是 true,则
                         i.  如果 desc.[[Set]]是 undefined,返回 false.
                        ii.  否则,返回true.
                    b.  否则, desc,就一定是一个数据属性描述符.所以,返回 desc.[[Writable]]的值.
          3. 令 proto 为 对象O的内部属性[[Prototype]].
          4. 如果 proto 是 null, 则返回对象O的内部属性[[Extensible]]的值.
          5. 令 inheried 为以属性名P为参数,调用 proto 的内部方法[[GetProperty]]的结果.
          6. 如果inheried是undefined,则返回对象O的内部属性[[Extensible]]的值.
          7. 如果 IsAccessorDescriptor(inherited) 是true,则
                    a.  如果inherited.[[Set]]是undefined,则返回false.
                    b.  否则返回 true.
          8. 否则,inherited一定是一个数据属性描述符.则
                    a. 如果对象O的内部属性[[Extensible]]是false,就返回false.
                    b. 否则,返回 inherited.[[Writable]]的值.
 
 
 
           8.12.5 [[Put]](P, V, Throw)(这玩意是用来赋值用的,比如 var a = 123;)
          当以属性名P,值V,和布尔标识 Throw作为参数,去调用对象O的内部方法[[Put]]时,就按下面的步骤进行:
          1. 如果以属性名P作为参数调用对象O的内部方法[[CanPut]]的结果是false,则
                    a.  如果 Throw 是true,则抛出一个TypeError 异常.(比如严格模式,Throw就会是true,然后试图对只读的东东赋值,就会抛出异常了.)
                    b.  否则返回.
          2. 令ownDesc为 以属性名P为参数,调用对象O的内部方法[[GetOwnProperty]]的返回值.
          3. 如果 IsDataDescriptor(ownDesc) 是true,则
                    a.  令 valueDesc 为一个属性描述符 {[[Value]] : V}(对于一个已存在的,可写的数据属性,只修改其value,其他保留,此问题[[DefineOwnProperty]]也没有明确说明.值得注意.)
                    b.  以P,valueDesc,Throw为参数,调用 O的内部方法[[DefineOwnProperty]].
                    c.  返回.
          4. 令desc为以P为参数调用O的内部方法[[GetProperty]]的返回值.这个值,也许是一个独占的或继承的,访问器属性描述符以及数据属性描述符.
          5. 如果IsAccessorDescriptor(Desc) 是true,则 (注9)
                    a.  令 setter 为 desc.[[Set]] ,desc.[[Set]]不能为undefined.
                    b.  以 V作为唯一参数,并使 O为this的值,去调用 setter的内部方法[[Call]].
          6. 否则,为对象O,创建一个名为P的数据属性.过程如下:
                    a.  令 newDesc为一个属性描述符{[[Value]]:V,[[Writable]]:true,[[Enumerable]]:true,[[Configurable]]:true}.
                    b.  以P,newDesc,Throw为参数,调用 O的内部方法[[DefineOwnProperty]].
          7. 返回.
     
 
 
          8.12.6[[HasProperty]](p)(区别于Object.prototype.hasOwnProperty,这玩意,是要在整个原型链中查找地.)
          当以属性名P作为参数,去调用O的内部方法[[HasProperty]],就按下面的步骤进行:
          1. 令 desc 为 以属性名P为参数,去调用O的内部方法[[GetProperty]]的返回值.
          2. 如果 desc 是 undefined,则返回 false.
          3. 否则,返回true.
 
 
          8.12.7[[Delete]](p, Throw)(严格模式,delete一个不可删除的东西,就要抛异常了.)
          当以属性名P和 布尔标识 Throw,作为参数,去调用O的内部方法[[Delete]],就按下面的步骤进行:
          1. 令 desc 为 以属性名P为参数,去调用O的内部方法[[GetOwnProperty]]的返回值.
          2. 如果 desc 是 undefined,则返回 true.
          3. 如果 desc.[[Configurable]]是true,则
                    a.  删除O的,名为P的独占属性.
                    b.  返回true.
          4. 否则,如果 Throw是true,则抛出一个TypeError 异常.(严格模式.)
          5. 返回 false.
 
 
          8.12.8[[DefaultValue]](hint)(此方法和ES3版本无甚区别这货就是给[[toPrimitive]]方法用来把object转换为primitive的,注10)
          当参数hint 示意为String,去调用 O的内部方法[[DefaultValue]]时,就按下面的步骤进行:
          1. 令 toString 为 以'toString'为参数调用对象O的内部方法[[Get]]的返回值.
          2. 如果 isCallble(toString)是true,则
                    a.  令str为,把this的值设为O,然后无参调用toString 的内部方法[[Call]]的执行结果.
                    b.  如果str是一个原始值,则返回str.
          3. 令 valueOf 为,以'valueOf'作为参数,调用O的内部方法[[Get]]的返回值.
          4. 如果 isCallble(valueOf) 是true,则
                    a.  令val为,把this的值设为O,然后无参调用valueOf 的内部方法[[Call]]的执行结果.
                    b.  如果val是一个原始值,则返回val.
          5. 抛出一个TypeError异常.
 
          当参数hint 示意为Number,去调用 O的内部方法[[DefaultValue]]时,就按下面的步骤进行:
          1. 令 valueOf 为,以'valueOf'作为参数,调用O的内部方法[[Get]]的返回值.
          2. 如果 isCallble(valueOf) 是true,则
                    a.  令val为,把this的值设为O,然后无参调用valueOf 的内部方法[[Call]]的执行结果.
                    b.  如果val是一个原始值,则返回val.
          3. 令 toString 为 以'toString'为参数调用对象O的内部方法[[Get]]的返回值.
          4. 如果 isCallble(toString)是true,则
                    a.  令str为,把this的值设为O,然后无参调用toString 的内部方法[[Call]]的执行结果.
                    b.  如果str是一个原始值,则返回str.
          5. 抛出一个TypeError异常.
 
 
 
 
 



注1: 这里是指内部实现.而不是指ECMAScript的语法. 反应到 ECMAScript的语法.那他应该是一个标准的 ECMAScript 对象直接量表示法. 就像这样:
          {
               value : 42,
               writable : false,
               configurable : true
          }
 
 
注2: 所谓默认值是啥呢? 参考8.6.1章节中的table 7 :
     
特性名
默认值
[[Value]]
undefined
[[Get]]
undefined
[[Set]]
undefined
[[Writable]]
false
[[Enumerable]]
false
[[Configurable]]
false
  
 
 
注3:    内部方法[[GetOwnProperty]](p) (获取一个对象的指定属性的属性描述符副本)
          当对象o的内部方法[[GetOwnProperty]],把属性名p作为其参数,被调用后,就会按照下面的步骤进行操作:
 
               1.如果 O 没有一个名为P的自身属性,则返回undefined.
               2.令 D 为一个新创建的,不包含任何字段的,属性描述符.
               3.令 X 为 O 的名为P的自身属性.
               4.如果 X 是一个数据属性,则
                    a. 设置 D.[[Value]] 为 X 的[[Value]]特性的值.
                    v. 设置 D.[[Writable]] 为 X 的[[Writable]]特性的值.
               5.否则 X 是一个访问器属性,所以
                    a. 设置 D.[[Get]] 为 X 的[[Get]]特性的值.
                    b. 设置 D.[[Set]] 为 X 的[[Set]]特性的值.
               6.设置 D.[[Enumerable]] 为 X 的[[Enumerable]]特性的值.
               7.设置[[Configurable]] 为 X 的[[Configurable]]特的值.
               8.返回 D.
 
          另外,如果O是一个字符串对象,那么其内部方法[[GetOwnProperty]],会有更加复杂的操作过程.(参见15.5.5.2.章节.)
 
     ps:(个人理解)
          这个内部方法[[GetOwnProperty]]说白了.主要就干一件事,那就是获取一个对象的指定属性的属性描述符副本,并返回.但要注意,步骤4和5.说明一个问题. 如果一个对象的属性,同时是数据属性,和访问器属性.那么是会优先获取其[[Value]]和[[Writable]]特性作为副本的有效字段返回的. 也就是说.这个方法,不会返回另外一个 所有字段都具备的.所谓两边都算的属性.  
          另外一个不得不提的就是这个内部方法的名字.在语义上.明显是有问题的. 这有可能是ES5的一小处错误. 更理想的,语义化的名字,应该是 [[GetOwnPropertyDescriptor]] 参考 Object.getOwnPropertyDescriptor 方法.
          最后,不得不说,这里应该是es5的一处bug.我们可以看出返回的descriptor不可能是一个所有字段都被显式列出的描述符. 
 
 
 
注4:    [[DefineOwnProperty]](P, Desc, Throw) (根据参数Desc的各个特性字段,定义或修改某个对象的名为P的属性,如果操作成功,则返回true.)
          在后面的算法中, 术语"Reject"(拒绝),代表的意思是,如果 throw为true,则抛出一个TypeError异常,否则 返回false.该算法包含对一个属性描述符 Desc 各种字段定义值的测试.这种方式的测试,需要被测试字段,并不实际存在于desc中.如果一个字段是缺省的,那么就认为它的值为false.
 
          当以属性名P,属性描述符Desc,布尔标识Throw,作为参数,去调用对象O的内部方法[[GetOwnProperty]],就会按照下面的步骤进行:
          1.  令 current 为 以P为属性名参数,调用O的内部方法[[GetOwnProperty]]的返值(参考 注3 , current应该是一个属性描述符)
          2.  令 extensible 为对象O的内部属性[[Extensible]](一个代表该对象是否可扩展的内部属性.这也是es5的一个新特性.)的值.
          3.  如果 current 是 undefined 并且 extensible 是 false, 则 Reject.
          4.  如果 current 是 undefined 并且 extensible 是 true, 则
                    a.  如果IsGenericDescriptor(Desc) 或 IsDataDescriptor(Desc)是 true, 则
                         i.  为对象O创建一个独占(own)的,名为P的,数据属性.并根据 Desc,来设置该属性的[[Value]],[[Writable]],[[Enumerable]],[[Configurable]]四个特性.
                             如果Desc上的某一个特性是缺省的,则那个新创建的属性,所对应的特性,就被设置为默认值(默认值,布尔的那几个都是false,其他都是undefined).
 
                    b.  否则,Desc就一定是一个访问器属性描述符,所以,
                         i.  创建一个属性名为P的,独占的,访问器属性.并根据Desc对应的特性,设置该属性的[[Get]],[[Set]],[[Enumerable]],[[Configurable]]四个特性.
                             如果Desc的某一个特性字段,是缺省的.则那个新创建的属性,所对应的特性,就被设置为默认值.
                    c. 返回 true.
          5.  如果Desc中的每个字段都是缺省的,则返回true.(desc=={},就啥都不干,直接返回true.)
          6.  如果Desc中有的每个字段,在current中也有,并且通过调用 SameValue算法(基本等价于===算法,不对运算元做ToPrimitive操作,区别是以NaN和NaN,或+0与-0作为运算元时,返回false.那么在用defineProperty时如果两次值都是NaN也不会抛出异常,而如果是+0 ,-0则会抛出异常)(参见9.12章节).发现,值都是相同的.那么直接返回true.
          7.  如果current.[[Configurable]] 是false,则 (此处,即当目标属性特性Configurable为false时,其Configurable和Enumerable,是不可修改的.)
                    a.  如果Desc.[[Configurable]]是true, Reject.
                    b.  如果Desc具有[[Enumerable]]字段,并且current和Desc的[[Enumerable]]字段,作为布尔值是相悖的.则 Rejct.
          8.  如果IsGenericDescriptor(Desc)是true,则没啥需要验证的了.(又坑爹了,没啥验证的,你总要有返回值啊..个人猜测,此处应该返回true.)
          9.  否则,如果 IsDataDescriptor(current) 和 IsDataDescriptor(Desc)有不同的结果(进入数据属性和访问器属性的转换逻辑),则
                    a.  current.[[Configurable]]是false,则 Reject.
                    b.  如果isDataDescriptor(current)是true,则
                         i.  把对象O的,名为P的属性,从一个数据属性,转换成一个访问器属性.保留该属性原来的[[Configuable]],和[[Enumerable]]特性.并把其剩余的特性,设置为默认值.
                    c.  否则
                         i.  把对象O的,名为P的属性,从一个访问器属性,转换成一个数据属性.保留该属性原来的[[Configuable]],和[[Enumerable]]特性.并把其剩余的特性,设置为默认值.
          10.否则,如果IsDataDescriptor(current) 和 IsDataDescriptor(Desc)都是true,则
                    a.  如果current.[[Configable]]是false,则
                         i.  如果current.[[Writable]]是false,并且Desc.[[Writable]]是true.则 Reject
                        ii.  如果current.[[Writable]]是false,则
                              1. 如果,Desc具备[[Value]]字段,并且 SameValue(Desc.[[Value]], current[[Value]]) 是false,则 Reject
                    b.  否则,如果current.[[Configurable]]是true,则任何修改,都被接受.(如果某个字段是缺省的,则沿用current的老的字段的值.此处标准并未描述.但浏览器实现很一致.也合情合理.符合语义)
          11.否则,如果IsAccessorDescriptor(current)和IsAccessorDescriptor(Desc)都是true,则
                    a.  如果current.[[Configurable]]是false,则
                         i.  如果Desc具备[[Set]]字段,并且, SameValue(Desc.[[Set]], current[[Set]])是false.则 Reject
                        ii.  如果Desc具备[[Get]]字段,并且, SameValue(Desc.[[Get]], current[[Get]])是false.则 Reject
          12.根据Desc上存在的每个字段,为对象O的,名为P的属性,一一对应的,把特性设置上去.
          13.返回true.
 
          另外,如果O是一个数组对象,那么其内部方法[[DefineOwnProperty]],在15.4.5.1章节,有更细致的定义.
          
          note: 
               对步骤10.b来说,在current.[[Configurable]]为true时,Desc上任何字段和 current上所对应字段的值,都可以是不同的. 即使被修改属性的[[Writable]]特性是false,时去修改该属性的[[Value]].这是因为,特性[[Configurable]]为true,意味着会有这样的后续操作,先把[[Writable]]设置为true,然后去设置新的[[Value]].然后再把[[Writable]]设为false.
 
PS:(个人理解)
     对于Value特性来说, Writable 对属性的value特性,拥有语义上的操纵权限. 即使是设置属性描述符的方式(比如Object.defineProperty).但是对于属性描述符操作value来说, Writable 和 Configurable有一个为真, 则value都是可以被重新设置的. 只不过语义上有区别. 一个是值可修改,一个是,属性描述符所代表的特性集可修改.
     
     对于步骤12来说,可以进入此步骤,则应是从步骤9之后的其他非return 以及, Reject的步骤而来, 比如步骤9.b.i和9.c.i .最终进入步骤12,才进一步说明,新设置的[[enumerable]]和[[Configurable]]覆盖掉原来滴.
 
     对于上面提到的数组对象的内部方法[[DefineOwnProperty]].之所以要单独说明,是因为,你需要考虑下面类似的情况:
          
var arr = [];
Object.defineProperty(arr, '0', {value : 'franky'});
Object.defineProperty(arr, 'length', {value : 10});
arr.length = 0 ;
alert([arr.length, arr[0]]);

这种代码很坑爹,各个浏览器的实现也都有差异,可以说,目前没有任何两款浏览器的实现,是相同的.
我个人觉得,我们永远不应该对数组做这件事情.否则.我们就会很纠结. 

IE9 : 1,franky
FF4+ : 抛出异常,说用defineProperty接口定义 数组的length,是不被支持的.(就这一点来说,Firefox才是符合标准的,因为我们没有设置其writable:true)
Safari: 0, undefined
Chrome : 0,franky 

    关于length,在15.4.5.2章节中对于length的描述中,明确的说,length是一个除了[[Writable]]为true的,其他特性都为false的,数据属性.且值总要是一个number.

    但对于v8引擎来(chrome14- 以及node 0.52-)说,就又有点其他问题.上例中如果去掉对length的define语句. 则居然会去影响 arr[0]的结果. 此时,它居然会打印undefined,而不是Franky了(行为和safari一样鸟).之所以,会产生这个问题.我们就要深入思考一下,ES5对length的定义是否是合理的. 我们知道 length = n;数组会同时删除掉所有大于等于n的元素. 这显然不是一个 数据属性,能做到的. 那么它真的应该是一个,访问器属性. 在setter中做额外的逻辑处理. 所以大概Chrome,是把它实现成了一个访问器属性.而调用Array.prototype.[[DefineProperty]]方法,去处理length时.本来不应该生效.而抛异常的操作.在v8中,在某方面.居然生效了. 他导致 被操作的数组对象的length真的成为了一个数据属性. 也就失去了原来的 删除对应元素的处理能力. 好消息是,这个问题在将来的版本,应该会被修复. “莫”同学,有提到,他最近checkout 的一个版本,没有这个问题.
    但是,不得不说,就实现上来说,最接近标准的是IE浏览器,至少标准提到的行为他都有遵守了. 这里指的不是 length 为1 ,而是指 一但我在设置length时,writable:true. 那么是应该可以操作length的值的.但是比如firefox就不应该总是无视这个差别,而抛出异常,而chrome 和safari却都是0.

    Safari离谱的地方在于,不仅仅不让define length ,连元素也不行无论其属性描述符的字段是啥,都没用.

这里最终,不去列出关于数组对象使用[[DefineOwnProperty]].是以为我觉得,标准本身就有问题.他定义的一些行为,会导致类似chrome出现这种问题, 以及很多不符合语义和预期的结果.比如它居然会允许设置length的[[Writable]] 为false. 那么接着我们就要考虑 一些api操作数组length的时候,你是允许呢 还是不允许呢?虽然es5试图解释每种情况.但总体来说,这是老道的一大败笔.考虑这样的代码:

   
 Object.defineProperty(arr, 'length', {value : 10,writable : false});
    arr.push(1);//只有ie9抛出异常,说不支持这个方法...好吧,IE9是遵守了es5. 但是这个异常信息也太雷了...
    alert([arr.length, arr[0]]);

悲剧就不再列举了...
 
 
 
 
 
 
注5: ToBoolean(enum) ,也就保证了, 当我们在ECMAScript中设置一个 obj 作为property descriptor .在特性字段值依赖布尔型时,都会自动转型. 参考下面的代码:
     
     var obj = {};
            Object.defineProperty(obj,'abc', {value : 123, writable : false,configurable:true});
            obj.abc = 456;
            alert(obj.abc); //123 
            Object.defineProperty(obj,'abc', {value : 123, writable : {},configurable:true});
            obj.abc = 456; 
            alert(obj.abc)//456

也就是说,对于 writable 来说,设置的值,最终会被转换为 布尔值.  所以 writable : {}  最终会被转换为 writable : true. 
而对于这一特性来说,所有浏览器都严格遵守了.
 
 
 
 
注6: IsCallable(getter) ,这个方法其实就是看getter是不是一个函数对象.es中的定义很简单.如果getter不是object,则返回false. 如果是object.则看其是否实现了[[Call]]接口.如果实现了.则返回true.
 
 
注7: 可以看出.ToPropertyDescriptor 和 [[GetOwnProperty]] 在细节上的差异. 前者会在发现 数据属性和访问器属性都存在(冲突)时,抛出异常.而后者,则是以数据属性作为偶先考虑.(参考,注3)那么,从实际应用角度出发,这是为了容错,因为ToPropertyDescriptor的运算元,是一个ECMAScript object.也就是说,这玩意可能来自ECMAScript代码编写者的定义. 那么在我们无法得知.代码编写者的明确意图时,抛出一个异常,来提醒.是有必要的. 而后者.则是从一个ECMAScript object的既有属性上,获取内部操作用的属性描述符.即该属性是已经通过其他方法设置了属性描述符的东西.则不会存在歧义.
 
总结来说, ToPropertyDescriptor是用来保证用户输入的描述符,不产生歧义.也就是说,对于 Object.defineProperty这种对外的ECMAScript接口中, 就有使用此方法.来处理,用户输入的描述符.如果用户输入的描述符,不符合条件,就抛出异常.比如用户输入这样一个描述符:{value:123, get : undefined}. 就要抛出一个异常(即语义上,writable和value,都和 getter和setter是互斥的.).即使get的值是一个undefined(undefined和function object都是get ,set的合法值).
 
 
注8: 当然,并不一定会一直查找到Object.prototype.[[Prototype]]. 参考下面的代码:
          
       Object.prototype.abc = 111;
       var a = new String(123);
       a.__proto__ = null;
       alert(a.abc);

在支持 __proto__的浏览器中,会打印undefined.  当然这东西并不符合标准.  而ES5,虽然提供了Object.create(proto,Properties)接口,来指定所创建的对象的[[Prototype]].但是参数proto是受限的.如果它不是一个object类型或者是null.则会抛出异常.所以,在ES5的范围内(不考虑特殊宿主环境实现).原型链查找就总是会查找到Object.prototype.__proto__上.
 
 
 
 
注9: [[Put]] 方法,对应了 赋值语句. 也就是说,ES5的赋值语句,和ES3已经有了一定变化. 什么变化呢?用语言描述一下先:
          首先如果是一个变量被赋值,那么他就被看做 variableObject的属性.所以.一律可以看最为某个对象的某属性进行赋值.然后,ES5需要先看这个属性是不是可以被设置.就去调用内部方法[[CanPut]].
          如果可以,再看是不是这个对象自身上有这个属性,而不是原型链上.并且,该属性是一个数据属性.否则.通过[[GetProperty]]进行原型链查找,如果属性在原型链上存在,并且该属性是一个访问器属性,却值不能是undefined.则,调用这个访问器属性,并把值传递进去.且指定this为该对象. 这是一个多么坑爹的设计啊?参考下面的代码:
     

var obj,

    pro = {};

Object.defineProperty(pro, 'abc' , {set : function () {}}); //此处所有浏览器中 set为undefined,同function对象效果一直.

obj = Object.create(pro);

obj.abc = 123;

alert([obj.abc + '', obj.hasOwnProperty('abc')]);


Firefox4.0 - Firefox6: undefined,true
Chrome5+ : undefined,false
IE9+: undefined,false
Safari5: undefined,false

    好吧,我们看到了,Firefox的结果简直就是奇葩(好在,firefox7+修复了这个问题).
   
    但是如果set是一个undefined.结果相同.这明显不合理.因为后面你需要调用set(V). 这你调用就得抛异常.你不处理 算什么事呢? 当然,这里不是浏览器的错,而是标准说的不够清晰.

 
如果上面这个代码不够坑爹,我们换成数组对象看看:
 
Object.defineProperty(Object.prototype, '0', {set : undefined,get : function () {return 'franky'}});
var a = [];
//a[0] = 1;
a.push(1)
alert(a[0])

用push代替a[0] =1.结果会完全不同. push的结果,很符合我们的预期. 但很不幸. 根据标准.所有的浏览器实现,都是错的. 正确的实现是应该 和 a[0] = 1;的结果一致. 也就是说,写入1失败.读取a[0] 打印 franky. 同样的 a = [1] ; 同 push(1)相同. 符合预期.  但同样是不符合标准的行为. 因为直接量表示也好,push也好.根据标准,都应该同一般的赋值语句相同.内部最终,都是要调用[[Put]]方法. 那么,看看现在的情况多么有趣. 标准不合理,而引擎严格遵守就会跟着变的不合理.但是如果不遵守标准.那标准又有什么意义呢? 老道在此处.实在是又一次,让人无语了.
 
 
 
 
注10:   [[DefaultValue]]方法是必须要返回一个原始值的,如果对象的toString,和valueOf(存在原型链查找)都返回非原始值,则会抛出异常,否则先以hint的示意值,对应调用toString和valueOf,一但优先使用对应方法,无法满足原始值条件,则去调用另外一个. 但是IE8-,并没有完全遵守. 当两个方法都不返回一个原始值时,IE8之前的版本.并不会抛出异常,而是返回 '[object]' .我无法解释这一现象. 因为在我修改了Object.prototype.toString,甚至赋予了valueOf方法后.才得以确定.并没有使用此两个方法中的任何一个. IE8以及其之前的版本,到底是如何处理的,我不得而知.参考下面的代码:
Object.prototype.toString = Object.prototype.valueOf = Array.prototype.toString = Array.prototype.valueOf = function () { return '坑爹啊'};
var obj = {};
obj.toString = function () {return [1,2,3]};
obj.valueOf = function () {return [1,2,3]};
alert(obj);

IE8和之前的版本.不抛出异常,并打印'[object]',就好像打印一个宿主对象那样. 好在,IE9解决了这个问题.

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
  
posted @ 2011-08-17 22:32  Franky  阅读(3012)  评论(3编辑  收藏  举报