【书名】:你不知道的JavaScript(上卷)
【作者】:Kyle Simpson
【本书总页码】:213
【已读页码】:136
1. [[Get]]
var myObject = {a: 2};
myObject.a; // 2
myObject.a 是一次属性访问,但这条语句并不仅仅是在 myObjet 中查找名字为 a 的属性。
在语言规范中,myObject.a 在 myObject 上实际上是实现了 [[Get]] 操作(有点像函数调用:[[Get]]())。对象默认的内置 [[Get]] 操作首先在对象中查找是否有名称相同的属性,如果找到就会返回这个属性的值。
如果没有找到名称相同的属性,按照 [[Get]] 算法的定义会遍历可能存在的 [[Prototype]] 链(原型链)。
如果无论如何都没有找到名称相同的属性,那 [[Get]] 操作会返回值 undefined。
注意,这种方法和访问变量时是不一样的。如果引用了一个当前词法作用域中不存在的变量,并不会像对象属性一样返回undefined,而是会抛出一个 ReferenceError 异常。
2. [[Put]]
[[Put]] 被触发时,实际的行为取决于许多因素,包括对象中是否已经存在这个属性(这是最重要的因素)。
如果已经存在这个属性,[[Put]] 算法大致会检查下面这些内容。
属性是否是访问描述符?如果是并且存在setter就调用setter。
属性的数据描述符中writable是否是false?如果是,在非严格模式下静默失败,在严格模式下抛出 TypeError 异常。
如果都不是,将该值设置为属性的值。
3. Getter和Setter
对象默认的 [[Put]] 和 [[Get]] 操作分别可以控制属性值的设置和获取。
在 ES5 中可以使用 getter 和 setter 部分改写默认操作,但是只能应用在单个属性上,无法应用在整个对象上。getter 是一个隐藏函数,会在获取属性值时调用。setter 也是一个隐藏函数,会在设置属性值时调用。
当给一个属性定义 getter、setter 或者两者都有时,这个属性会被定义为“访问描述符”(和“数据描述符”相对)。对于访问描述符来说,JavaScript 会忽略它们的 value 和writable 特性,取而代之的是关心 set 和 get(还有 configurable 和 enumerable)特性。
var myObject = {
//给 a 定义一个 getter
get a() {return 2;}
};
Object.defineProperty(
myObject, // 目标对象
"b", // 属性名
{
// 描述符
// 给 b 设置一个 getter
get: function(){ return this.a * 2 },
// 确保 b 会出现在对象的属性列表中
enumerable: true
}
);
myObject.a; // 2
myObject.b; // 4
var myObject = {
//给 a 定义一个 getter
get a() {return 2;}
};
myObject.a = 3;
myObject.a; // 2
由于只定义了 a 的 getter,所以对 a 的值进行设置时 set 操作会忽略赋值操作,不会抛出错误。而且即便有合法的 setter,由于我们自定义的 getter 只会返回 2,所以 set 操作是没有意义的。
为了让属性更合理,还应当定义 setter,和期望的一样,setter 会覆盖单个属性默认的[[Put]](也被称为赋值)操作。通常来说 getter 和 setter 是成对出现的(只定义一个的话通常会产生意料之外的行为):
var myObject = {
//给 a 定义一个 getter
get a() {return this._a_;},
// 给 a 定义一个 setter
set a(val) {this._a_ = val * 2;}
};
myObject.a = 2;
myObject.a; // 4
4. 存在性
myObject.a 的属性访问返回值可能是 undefined,但是这个值有可能是属性中存储的 undefined,也可能是因为属性不存在所以返回 undefined。那么如何区分这两种情况呢?
可以在不访问属性值的情况下判断对象中是否存在这个属性:
var myObject = {a:2};
("a" in myObject); // true
("b" in myObject); // false
myObject.hasOwnProperty( "a" ); // true
myObject.hasOwnProperty( "b" ); // false
in 操作符会检查属性是否在对象及其 [[Prototype]] 原型链中。相比之下,hasOwnProperty(..) 只会检查属性是否在 myObject 对象中,不会检查 [[Prototype]] 链。
所有的普通对象都可以通过对于 Object.prototype 的委托来访问hasOwnProperty(..), 但 是 有 的 对 象 可 能 没 有 连 接 到Object.prototype( 通 过 Object.create(null) 来创建。在这种情况下,形如 myObejct.hasOwnProperty(..)就会失败。
这时可以使用一种更加强硬的方法来进行判断:Object.prototype.hasOwnProperty.call(myObject,"a"),它借用基础的 hasOwnProperty(..) 方法并把它显式绑定到 myObject 上。
关于枚举
var myObject = { };
Object.defineProperty(myObject,"a",
//让 a 像普通属性一样可以枚举
{ enumerable: true, value: 2 }
);
Object.defineProperty(myObject,"b",
//让b不可枚举
{ enumerable: false, value: 3 }
);
myObject.b; // 3
("b" in myObject); // true
myObject.hasOwnProperty( "b" ); // true
// .......
for (var k in myObject) {console.log( k, myObject[k] );}
// "a" 2
可以看到,myObject.b 确实存在并且有访问值,但是却不会出现在 for..in 循环中(尽管可以通过 in 操作符来判断是否存在)。原因是“可枚举”就相当于“可以出现在对象属性的遍历中”。
也可以通过另一种方式来区分属性是否可枚举:
var myObject = { };
Object.defineProperty(myObject,"a",
//让 a 像普通属性一样可以枚举
{ enumerable: true, value: 2 }
);
Object.defineProperty(myObject,"b",
//让 b 不可枚举
{ enumerable: false, value: 3 }
);
myObject.propertyIsEnumerable( "a" ); // true
myObject.propertyIsEnumerable( "b" ); // false
Object.keys( myObject ); // ["a"]
Object.getOwnPropertyNames( myObject ); // ["a", "b"]
propertyIsEnumerable(..) 会检查给定的属性名是否直接存在于对象中(而不是在原型链上)并且满足 enumerable:true。
Object.keys(..) 会返回一个数组,包含所有可枚举属性,Object.getOwnPropertyNames(..)会返回一个数组,包含所有属性,无论它们是否可枚举。
in 和 hasOwnProperty(..) 的区别在于是否查找 [[Prototype]] 链,然而,Object.keys(..)和 Object.getOwnPropertyNames(..) 都只会查找对象直接包含的属性。
(目前)并没有内置的方法可以获取 in 操作符使用的属性列表(对象本身的属性以及 [[Prototype]] 链中的所有属性)。不过可以递归遍历某个对象的整条[[Prototype]] 链并保存每一层中使用 Object.keys(..) 得到的属性列表——只包含可枚举属性。
网友评论