深入理解JavaScript系列(18):面向对象编程之E(10)


// 如果不能给属性写值,就退出
if (!O.[[CanPut]](P)) {
  return;
}
 
// 如果对象没有自身的属性,就创建它
// 所有的attributes特性都是false
if (!O.hasOwnProperty(P)) {
  createNewProperty(O, P, attributes: {
    ReadOnly: false,
    DontEnum: false,
    DontDelete: false,
    Internal: false
  });
}
 
// 如果属性存在就设置值,但不改变attributes特性
O.P = V
 
return;


例如:

复制代码 代码如下:


Object.prototype.x = 100;
 
var foo = {};
console.log(foo.x); // 100, 继承属性
 
foo.x = 10; // [[Put]]
console.log(foo.x); // 10, 自身属性
 
delete foo.x;
console.log(foo.x); // 重新是100,继承属性
请注意,不能掩盖原型里的只读属性,赋值结果将忽略,这是由内部方法[[CanPut]]控制的。

// 例如,属性length是只读的,我们来掩盖一下length试试
 
function SuperString() {
  /* nothing */
}
 
SuperString.prototype = new String("abc");
 
var foo = new SuperString();
 
console.log(foo.length); // 3, "abc"的长度
 
// 尝试掩盖
foo.length = 5;
console.log(foo.length); // 依然是3


但在ES5的严格模式下,如果掩盖只读属性的话,会保存TypeError错误。

属性访问器

内部方法[[Get]]和[[Put]]在ECMAScript里是通过点符号或者索引法来激活的,如果属性标示符是合法的名字的话,可以通过“.”来访问,而索引方运行动态定义名称。

复制代码 代码如下:


var a = {testProperty: 10};
 
alert(a.testProperty); // 10, 点
alert(a['testProperty']); // 10, 索引
 
var propertyName = 'Property';
alert(a['test' + propertyName]); // 10, 动态属性通过索引的方式

这里有一个非常重要的特性——属性访问器总是使用ToObject规范来对待“.”左边的值。这种隐式转化和这句“在JavaScript中一切都是对象”有关系,(然而,当我们已经知道了,JavaScript里不是所有的值都是对象)。

如果对原始值进行属性访问器取值,访问之前会先对原始值进行对象包装(包括原始值),然后通过包装的对象进行访问属性,属性访问以后,包装对象就会被删除。

例如:

复制代码 代码如下:


var a = 10; // 原始值
 
// 但是可以访问方法(就像对象一样)
alert(a.toString()); // "10"
 
// 此外,我们可以在a上创建一个心属性
a.test = 100; // 好像是没问题的
 
// 但,[[Get]]方法没有返回该属性的值,返回的却是undefined
alert(a.test); // undefined


那么,为什么整个例子里的原始值可以访问toString方法,而不能访问新创建的test属性呢?

答案很简单:

首先,正如我们所说,使用属性访问器以后,它已经不是原始值了,而是一个包装过的中间对象(整个例子是使用new Number(a)),而toString方法这时候是通过原型链查找到的:

复制代码 代码如下:


// 执行a.toString()的原理:
 
1. wrapper = new Number(a);
2. wrapper.toString(); // "10"
3. delete wrapper;


接下来,[[Put]]方法创建新属性时候,也是通过包装装的对象进行的:

复制代码 代码如下:


// 执行a.test = 100的原理:
 
1. wrapper = new Number(a);
2. wrapper.test = 100;
3. delete wrapper;

我们看到,在第3步的时候,包装的对象以及删除了,随着新创建的属性页被删除了——删除包装对象本身。

然后使用[[Get]]获取test值的时候,再一次创建了包装对象,但这时候包装的对象已经没有test属性了,所以返回的是undefined:

复制代码 代码如下:


// 执行a.test的原理:
 
1. wrapper = new Number(a);
2. wrapper.test; // undefined


这种方式解释了原始值的读取方式,另外,任何原始值如果经常用在访问属性的话,时间效率考虑,都是直接用一个对象替代它;与此相反,如果不经常访问,或者只是用于计算的话,到可以保留这种形式。

继承

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wgfgzd.html