目录
理解Object的键
Object类型的键只有:①字符串 ②数字 ③符号(Symbol)这三种类型。如果把其它非上述三种类型的实例作为一个键,则会进行强制类型转换,将其转换为string类型。
这里举例一个简单的例子进行说明:
// 这个对象将作为一个键
const objAsKey = {
toString() {
return '作为一个键的对象'
}
}
const obj = {
// 注意,这里要用中括号属性访问器,补充一点小知识:中括号可以通过访问变量访问属性
[objAsKey]: 'value',
};
console.log(obj); // { '作为一个键的对象': 'value' }
console.log(obj[objAsKey]); // value
console.log(obj['作为一个键的对象']); // value
// 当把一个对象作为一个对象的键时,会进行类型转换,也就是调用了toString方法
理解Object的属性
属性的类型:
Object的属性分两种:数据属性以及访问器属性。
数据属性:
数据属性包含一个保存数据值的位置,可以理解为一个存东西的地方。数据属性有四个特性描述它的行为:
[[Configurable]]:①能否通过delete删除并重新定义该属性 ②能否修改该属性的特性(包括其它特性) ③能否将该属性修改为访问器属性。默认情况下,直接定义在对象上的属性的这个特性是true。
// 当属性直接定义在对象上时
const obj = {
name: 'javascript',
};
// Object.getOwnPropertyDescriptor 读取属性特性
console.log(Object.getOwnPropertyDescriptor(obj, 'name')); // {configurable: true}
Object.defineProperty(obj, 'name', { configurable: false });
// tips:严格模式会抛出错误,非严格模式会忽略这条语句
delete obj.name; // Uncaught TypeError: Cannot delete property 'name' of #<Object>
// 一旦将属性的configurable设置为false之后就会收到限制,比如这里再将其设置为true
Object.defineProperty(obj, 'name', { configurable: true });
// 抛出错误:Uncaught TypeError: Cannot redefine property: name
// --------------------------------------------------
// 当属性通过Object.defineProperty定义时,其对应的属性的特性默认为false
Object.defineProperty(obj, 'age', {})
console.log(Object.getOwnPropertyDescriptor(obj, 'age')); // {configurable: false}
[[Enumberable]]:表示该属性能否被枚举。默认情况下,直接定义在对象上的属性的这个特性是true。
const obj = {
name: 'javascript',
age: '28',
}
// 输出 name age
for (let key in obj) {
console.log(key);
}
// 尝试获得键、值以及键值对的迭代器对象
console.log(Object.keys(obj)); // ['name', 'age']
console.log(Object.values(obj)); // ['javascript', '28']
console.log(Object.entries(obj)); // [['name', 'javascript'], ['age', '28']]
// 将name的enumerable设置为false
Object.defineProperty(obj, 'name', { enumerable: false });
// 输出 age
for (let key in obj) {
console.log(key);
}
// 尝试获得键、值以及键值对的迭代器对象
console.log(Object.keys(obj)); // ['age']
console.log(Object.values(obj)); // ['28']
console.log(Object.entries(obj)); // [['age', '28']]
// 还是可以通过Object.getOwnPropertyNames枚举出该属性(不包括Symbol值作为键的属性)
console.log(Object.getOwnPropertyNames(obj)); // ['name', 'age']
[[Writable]]:表示该属性能否被修改。默认情况下,直接定义在对象上的属性的这个特性是true。
"use strict"
// 直接定义在对象上的属性的该特性默认为true
const obj = {
name: 'javascript',
};
obj.name = 'C#';
console.log(obj.name); // 'C#'
Object.defineProperty(obj, 'name', {
writable: false,
})
// 严格模式下会抛出错误,非严格模式会忽略对它的重新赋值
obj.name = 'java'; // Uncaught TypeError: Cannot assign to read only property 'name' of object '#<Object>'
console.log(obj.name); // 'C#'
// 通过Object.defineProperty定义的属性的该特性默认为false
Object.defineProperty(obj, 'age', {});
// 严格模式下会抛出错误,非严格模式会忽略对它的重新赋值
obj.age = 28; // Uncaught TypeError: Cannot assign to read only property 'age' of object '#<Object>'
console.log(obj.age); // undefined (未赋值,默认为undefined)
[[Value]]:包含属性的实际值(注意前面说的包含!是读取和写入属性值的位置!)。默认值是undefined。
const obj = {};
Object.defineProperty(obj, 'name', { value: 'javascript' });
console.log(obj.name); // javascript
// 默认为undefined
Object.defineProperty(obj, 'age', {});
console.log(obj.age); // undefined
访问器属性:
访问器属性是不包含数据值的,它包含一个对属性的访问器(get)和修改器(set)。
[[Configurable]]:①能否通过delete删除并重新定义该属性 ②能否修改该属性的特性(包括其它特性) ③能否将该属性修改为数据属性。默认情况下,直接定义在对象上的属性的这个特性是true。
[[Enumberable]]:表示该属性能否被枚举。默认情况下,直接定义在对象上的属性的这个特性是true。
[[Get]]:获取函数。读取属性时调用,默认为undefined。
[[Set]]:设置函数。设置属性时调用,默认为undefined。
如果只定义了该属性的get函数,那么就意味着这个属性是只读的,如果尝试修改属性的值,在非严格模式下会忽略,而严格模式下会抛出错误。
类似,如果只定义了该属性的set函数,那么就意味着该属性是只写的,如果尝试读取该属性的值,在非严格模式下返回undefined,严格模式下抛出错误。
定义属性的访问器和修改器有两种方式:①在对象初始化时 ②通过Object.defineProperty
const obj = {
age_ : 10,
get age() {
return this.age_;
},
set age(value) {
this.age_ = value;
}
}
console.log(obj.age); // 10
obj.age = 20;
console.log(obj.age); // 20
这里有一个需要注意的地方,就是为什么get函数不直接返回this.age呢?因为这样会导致栈溢出错误。set函数同理。
const obj = {
get age() {
return this.age; // this.age 会调用age的get函数,因此产生栈溢出错误
},
}
console.log(obj.age); // Uncaught RangeError: Maximum call stack size exceeded
方式一:在对象初始化时定义get、set函数
"use strict";
// 第一种方式:在对象初始化时定义get、set函数
const obj = {
// 同时定义了get、set函数
age_: 28,
get age() {
return this.age_;
},
set age(value) {
this.age_ = value;
},
// 只定义了get函数
name_: 'javascript',
get name() {
return this.name_;
},
// 只定义了set函数
gender_ :'男',
set gender(value) {
this.gender_ = value;
},
}
// 能够读取、修改同时定义了get、set函数的属性
console.log(obj.age); // 10
obj.age = 20;
console.log(obj.age); // 20
// 只能读取属性值
console.log(obj.name);
// 非严格模式下忽略赋值,严格模式下抛出错误
obj.name = 'typescript'; // Uncaught TypeError: Cannot set property name of #<Object> which has only a getter
console.log(obj.name);
// 只能修改属性值
console.log(obj.gender); // undefined
// 这里理论上存在报错,但是实际并没有,无论是在严格模式或者非严格模式
obj.gender = '女';
console.log(obj.gender); // undefined
方式二:通过Object.defineProperty定义get、set函数
"use strict";
// 第二种方式:通过Object.defineProperty定义get、set函数
const obj = {
name_: 'javascript',
};
// 同时定义get、set
Object.defineProperty(obj, 'name', {
set: function (value) {
this.name_ = value;
},
get: function () {
return this.name_;
}
})
console.log(obj.name); // javascript
obj.name = 'java';
console.log(obj.name); // java
tips:除了以上两种方式外,在ES5之前,还可以通过两个非标准的方法创建访问器属性:__defineGetter__ 以及 __defineSetter__,但是这两个方法已经废弃了,不建议使用。
const obj = {
name_: 'javascript',
};
obj.__defineGetter__("name", function () {
return this.name_;
});
obj.__defineSetter__("name", function (value) {
this.name_ = value;
})
console.log(obj.name); // javascript
obj.name = 'java';
console.log(obj.name); // java