理解对象及对象属性

理解对象

ECMA-262 把对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数”。
对象的每个属性或方法(保存了函数的属性)都有一个名字,而每个名字都映射到一个值,也就是说名字和对应的值构成了一个属性,而一组属性的散列表构成了一个对象。

var me = {
name: "Paco",
age: 26,
job: "student",
saySomething: function() {
alert("Hi, I'm" + " " + this.name);
}
}

上面代码是一个 me 对象,包含了三个属性和一个方法

对象属性

JavaScript 中有两种属性:数据属性和访问器属性。

数据属性

数据属性包含一个数据值的位置。在这个位置可以读取或写入值。数据属性有4个描述其行为的特性:

  • [[Configurable]]:表示能否修改属性的特性,能否将属性修改成访问器属性,能否通过 delete 删除属性。默认值为 true。
  • [[Enumerable]]:表示能否通过 for-in 循环返回属性。默认值为 true。
  • [[Writable]]:表示能否修改属性的值。默认值为 true。
  • [[Value]]:这个属性的数据值。从这里读写属性值。默认为 undefined。
var me = {
name: "Paco"
};

这里在 me 对象中创建了一个 name 属性,并为它指定值为“Paco”。也就是说此时,name 属性的[[Value]]特性被设置为“Paco”。如果将 name 属性的值修改为 “xiaoming”,那么[[Value]]特性被重新设置为 “xiaoming”。

如果想要修改对象属性的默认特性值,需要用到 Object.defineProperty() 方法。这个方法接收三个参数,分别是 属性所在的对象属性名(字符串形式,带引号),描述符对象。描述符对象的属性必须是:configurable, enumerable, writable 和 value 中的一个或几个。例如:

var me = {};
Object.defineProperty(me, "name", {
configurable: false,
writable: false,
value: "Paco"
});
alert(me.name); // Paco
me.name = "xiaoming";
alert(me.name);// Paco
delete me.name; // false
alert(me.name); // Paco
Object.defineProperty(me, "name", {
configurable: true
writable: true
});// Uncaught TypeError: Cannot redefine property: name

上面代码我们可以看出,将 configurable 设置为 false 后,不能删除属性也不能对属性的特性进行修改。 将 writable 设置为 false 后,不能对属性的值进行重写。

需要注意的是,当用 Object.defineProperty() 去创建一个属性的时候,即原来对象中没有这个属性,configurable, enumerable, writable 的默认值都为 false。上面的代码中,如果我们将 writable: false 删去,得到的结果是一样的。

访问器属性

访问器属性不包含数据值,它们是一对 getter 和 setter 函数(这两个函数都不是必须的)。在读取访问器属性时,会调用 getter 函数,这个函数负责返回有效的值。在写入访问器属性时,会调用 setter 函数并传入新值,这个函数负责决定如何处理数据。访问器属性有如下4个特性:

  • [[Configurable]]:表示能否修改属性的特性,能够将属性修改成数据属性,能否通过 delete 删除属性。默认值为 false。
  • [[Enumerable]]:表示能否通过 for-in 循环返回属性。默认值为 false。
  • [[Get]]:在读取属性时调用的函数。默认值为 undefined。
  • [[Set]]:在写入属性时调用的函数。默认值为 undefined。

访问器属性不能直接定义,必须使用 Object.defineProperty() 来定义。

var smartisan = {
_year: 2014,
edition: "T1",
};
Object.defineProperty(smartisan, `year`, {
get: function () {
return this._year;
},
set: function (newValue) {
if (newValue > 2014) {
this._year = newValue;
this.edition = "T" + (1 + newValue - 2014);
};
}
});
delete smartisan.year; // false
alert(smartisan.year); // 2014
smartisan.year = 2016;
alert(smartisan.edition); // T3

上面的代码创建了一个 smartisan 对象,并给它定义了两个默认的属性:_yearedition_year之前的下划线是一种常用的记号,用于表示只能通过对象方法访问的属性。而访问器属性 year 则包含一个 getter 和一个 setter 函数。当对象直接调用这个访问器属性时,比如 smartisan.year。那就会调用 getter 函数,看 getter 函数返回了什么默认属性的值。如果对象调用访问器属性,并对其进行赋值,比如smartisan.year = 2016;,那么就会调用 setter 函数,并且将赋的值传入到 setter 函数中,对默认属性进行修改。

不一定非要同时指定 getter 和 setter。只指定 getter 意味着属性是不能写的,尝试写入属性会被忽略。类似的,只指定 setter 函数的属性也不能读。

这里还有个 兼容性的问题,支持 Object.defineProperty() 方法的浏览器有 IE9+(IE8 只是部分实现)、Firefox 4+、 Safari 5+、 Opera12+ 和 Chrome 。所以在这些浏览器版本之下的浏览器要创建访问器属性一般采用两个非标准的方法:_defineGetter__defineSetter_.之前的例子重写后就变成:

var smartisan = {
_year: 2014,
edition: "T1"
}
smartisan._defineGetter_("year" , function() {
return this._year;
});
smartisan._defineSetter_("year", function(newValue) {
if (newValue > 2014) {
this._year = newValue;
this.edition = "T" + (1 + newValue - 2014);
};
});
alert(smartisan.year); // 2014
smartisan.year = 2016;
alert(smartisan.edition); // T3

用 Object.defineProperties() 方法定义多个属性

利用 Object.defineProperties() 方法可以通过描述符一次定义多个属性。这个方法接受两个参数:第一个对象是要添加和修改其属性的对象,第二个对象的属性与第一个对象中要添加和修改的属性一一对应。例如:

var smartisan = {};
Object.defineProperties(smartisan, {
_year: {
value: 2014
},
edition: {
value: "T1"
},
year: {
get: function() {
return this._year;
};
set: function(newValue) {
if (newValue > 2014) {
this._year = newValue;
this.edition = "T" + (1 + newValue - 2014);
};
};
};
});

用 Object.getOwnPropertyDescriptor() 方法读取属性的特性

这个方法接收两个参数:第一个参数是属性所在的对象;第二个参数是到读取描述符(特性)的属性名。返回一个对象,如果是访问器属性,这个对象的属性有 configurable, enumerable, get, set; 如果是数据属性,这个对象的属性有 configurable, enumerable, writable, value。例如:

var smartisan = {};
Object.defineProperties(smartisan, {
_year: {
value: 2014
},
edition: {
value: "T1"
},
year: {
get: function() {
return this._year;
};
set: function(newValue) {
if (newValue > 2014) {
this._year = newValue;
this.edition = "T" + (1 + newValue - 2014);
};
};
};
});
var descriptor = Object.getOwnPropertyDescriptor(smartisan, _year);
alert(descriptor.value); // 2014
alert(desctiptor.configurable); false
alert(typeof descriptor.get); // undefined
var desctiptor = Object.getOwnPropertyDescriptor(smartisan, year);
alert(descriptor.value); // undefined
alert(typeof descriptor.get); // function

本文参考:
JavaScript 高级程序设计(第3版)