学习JavaScript红宝书(九)——数据类型之Object:简述对象、对象的属性
对象
ECMAScript 中的对象是一组数据和功能的无序集合。严格来说,这意味着对象就是一组没有特定顺序的值。对象的每个属性或方法都由一个名称来标识。可以把 ECMAScript 的对象想象成一张散列表,内容就是一组名/值对,值可以是数据或者函数。
创建对象
对象可以通过 new 操作符创建。
let person = new Object();
在没有参数的情况下,也可以省略括号:
let person = new Object(); // 合法,但不推荐
还有下面这种最简单的方式也可以创建一个对象:
let person = {};
ECMAScript 中的 Object 是派生其他对象的基类。Object 类型的所有属性和方法在派生对象上同样存在。
每个 Object 实例都有如下属性和方法:
-
constructor: 用于创建当前对象的函数。在前面的例子中,这个属性的值就是 Object()函数。
-
hasOwnProperty(propertyName):用于判断当前对象实例(不是原型)上是否存在给定的属性。要检查的属性名必须是字符串(如 0.hasOwnProperty("name"))或符号。
-
isPrototypeOf(object): 用于判断当前对象是否是另一个对象的原型。
-
propertyIsEnumerable(propertyName): 用于判断给定的属性是否可以使用 for-in 语句枚举。与 hasOwnProperty()一样,属性名必须是字符串。
-
toLocaleString(): 返回对象的字符串表示,该字符串反映对象所在的本地化执行环境。
-
toString(): 返回对象的字符串表示。
-
valueOf(): 返回对象对应的字符串、数值或布尔值。通常与 toString()的返回值相同。
在后面的内容,会详细说明更多创建对象的方法。
添加属性和方法
let person = new Object();
person.name = "Klaus";
person.sayName = function () {
console.log(this.name);
};
这个例子创建名为 person 的对象,具有一个属性 name 和一个方法 sayname()。sayname()会显示 this.name 的值,这个属性会被解析为 person.name。
这里红宝书认为sayname()是一个方法,而不是一个属性。因为它是和name分开说的。所以后面对属性的说明,应该也是不包括sayname()在内。
对象字面量
对象字面量是更加流行的用来创建对象的方式。前面的例子可以改成:
let person = {
name: "Klaus",
sayName() {
console.log(this.name);
},
};
属性的类型
属性有两种: 数据属性和访问器属性。
ECMA-262 使用一些内部特性来描述属性。开发者不能在 JavaScript 中直接访问这些特性。规范用[[]]将特性标识为内部特性,比如[[Enumerable]]。
数据属性
数据属性包含一个保存数据值的位置。值会从这个位置读取,也会写入到这个位置。
数据属性有 4 个特性:
- [[Configurable]]: 表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特性,是否可以把它改为访问器属性。默认情况为 true。
- [[Enumerable]]: 表示属性是否可以通过 for-in 循环返回。默认情况为 true。
- [[Writable]]: 表示属性的值是否可以被修改。默认情况为 true。
- [[Value]]: 包含属性实际的值。就是前面提到读取和写入属性值的位置。默认值是 undefined。
像前面例子那样将属性显式地添加到对象之后,[[Configurable]]、[[Enumerable]]、[[Writable]]就会被设置为 true,[[value]]就是设置的值。比如之前的 name 属性,它的[[value]]就是“Klaus”。
Object.defineProperty()
要添加非默认特性的数据属性,必须使用 Object.defineProperty()方法。
Object.defineProperty()方法接收三个参数: 要添加属性的对象、属性名、描述符对象。描述符对象上的属性可以包含:configurable、enumerable、writable、value,用来设置值。它可以用来添加新属性,也可以用来修改原有属性。
let person = {};
Object.defineProperty{person, "name", {
writable: false,
value: "Klaus"
}};
person.name = "Mike" // 尝试修改
console.log(person.name) // Klaus,值不会被修改
因为 name属性的writable 设置为 false 所以不可修改,并且在严格模式尝试修改一个不可修改的值,还会抛出错误。
[[configurable]]更加特别。一旦设置为不可配置,甚至不能使用 Object.defineProperty()方法再对其进行修改。
let person = {};
Object.defineProperty(person, "name", {
configurable: false,
value: "Klaus",
});
// 试图修改已经是false的configurable
Object.defineProperty(person, "name", {
configurable: true,
value: "Klaus",
});
// TypeError: Cannot redefine property: name
访问器属性
访问器属性不包含数据值。他们包含一个获取 getter 函数和设置 setter 函数。
在读取访问器属性时,会调用获取函数,这个函数会返回一个有效的值。再写入访问器属性时,会调用设置函数,并传入新值。
访问器属性有四个特性:
- [[Configurable]]: 表示属性是否被 delete 并重新定义,是否可以修改它的特性,是否可以改为数据属性。默认为 true。
- [[Enumerable]]: 表示属性是否可以通过 for-in 循环返回。默认情况为 true。
- [[Get]]: 获取函数,读取属性时调用。默认为 undefined。
- [[Set]]: 设置函数,写入属性时调用。默认为 undefined。
必须使用 Object.defineProperty()。
访问器属性和数据属性不同,不能直接定义。
set()
添加具有set()特性的访问器属性:
let person = {
age: 25,
older: 0,
};
Object.defineProperty(person, "changeAge", {
set(newValue) {
if (newValue > 28) {
this.age = newValue;
this.older = this.age - 25;
}
},
});
person.changeAge = "29";
console.log(person.older);
这里定义了一个 changeAge 属性,并给它赋予了设置函数。接收到的值就是changeAge,根据写好的逻辑它会改变age的值,而当 age 改变时,older 也会根据一定规则改变。这是访问器属性的典型使用场景。
get()
添加具有get()特性的访问器属性:
let person = {
age_: 25,
};
Object.defineProperty(person, "getAge", {
get() {
return this.age;
},
});
console.log(person.getAge);
age_中的下划线表示这个属性不被外部访问。有的时候一些属性不想被外部访问,我们可以添加带有获取函数的属性来得到它。
对象的属性的方法
Object.defineProperties()
Object.defineProperties()方法可以一次性定义多个属性。
它接收两个参数:对象、一个或多个描述符对象。
let book = {};
Object.defineProperties(book, {
year_: {
value: 2017,
},
edition: {
value: 1,
},
year: {
get() {
return this.year_;
},
set(newValue) {
if (newValue > 2017) {
this.year_ = newValue;
this.edition += newValue - 2017;
}
},
},
});
注意!!!用这个方法定义的数据属性,其 configurable、enumerable 和 writable 特性值默认情况下都是 false,比如year的configurable、enumerable、writable就会默认为false,这个直接定义属性后特性默认为true不一样。
Object.getOwnPropertyDescriptor()
Object.getOwnPropertyDescriptor()方法可以取得指定属性的属性描述符。
Object.getOwnPropertyDescriptor()方法接收两个参数:属性所在的对象、属性名。返回值是一个对象,访问器属性包含 configurable、enumerable、get、set,数据属性包含 configurable、enumerable、writable、value。
let book = {};
Object.defineProperties(book, {
year_: {
value: 2017,
},
edition: {
value: 1,
},
year: {
get() {
return this.year_;
},
set() {
if (newValue > 2017) {
this.year_ = newValue;
this.edition += newValue - 2017;
}
},
},
});
let descriptor = Object.getOwnPropertyDescriptor(book, "year_");
console.log(descriptor.configurable); // false
console.log(descriptor.enumerable); // false
console.log(descriptor.writable); // false
console.log(descriptor.value); // 2017
console.log(typeof descriptor.get); // "undefined"
let descriptor2 = Object.getOwnPropertyDescriptor(book, "year");
console.log(descriptor2.configurable); // false
console.log(descriptor2.enumerable); // false
console.log(descriptor2.writable); // undefined
console.log(descriptor2.value); // undefined
console.log(typeof descriptor2.get); // "function"