对象的属性有两种类型:数据属性和访问器属性。
-
数据属性 (有4种特性)
1.
Configurable
(可配置性):决定属性是否可以删除(delete)、是否可以再次修改属性的特性以及是否可以转化为访问器属性。 一旦通过Object.defineProperty()
方法将特性设置为false,后面就无法对该属性其他特性进行修改;也无法删除该属性。(有一例外,configurable:false
情况下,只允许writable的状态由true变为false,无法由false变为true)默认值为truevar person = { name: "zhangsan", age: 18, sex: "male" } Object.defineProperty(person, "age" , { configurable: false }); delete person.age; // 无法删除age属性, 严格模式下会报TypeError Object.defineProperty(person, "age", { writable: false }) person.age = 20; console.log( Object.getOwnPropertyDescriptor(person, "age")) // {"value":18,"writable":false,"enumerable":true,"configurable":false}
-
Enumerable
(可枚举性):决定属性是否出现在对象的可枚举属性中。比如for...in 、Object.keys() 、Object.assign() 、JSON.stringify()
遍历的都是enumerable:true
的属性。默认值为true
可以通过obj.propertyIsEnumerable(prop)
检查属性prop
是否可枚举,必须是对象自身属性
,不查找原型链上的。
-
writable
(可写性):决定是否修改属性的value。默认值为true
-
value
(数据值):表示属性的数据值。默认值为undefined
扩展: 对象的不变性
当希望对象或者属性是不可改变时,有以下实现方法:- 对象常量
-
// 定义一个常量属性
var myObj = {};
Object.defineProperty(myObj, "FAVORITE_NUMBER", { configurable: false, writable: false, value: 1 })
- 禁止扩展
Object.preventExtensions(obj)
// 禁止一个对象添加新属性,保留现有属性
var myObj2 = {a: 1};
Object.preventExtensions(myObj2);
myObj2.b = 1;
console.log(myObj2.b) // undefined; 严格模式下会报TypeError
密封
Object.seal( ... )
会创建一个密封的对象,这个方法实际会对一个对象调用Object.preventExtensions()
方法,并把所有属性标记configurable: false
。
意味着这个对象无法添加新属性,并无法删除任何已有属性以及无法配置属性的特性。(但可以修改属性value)冻结
Object.freeze(...)
会创建一个冻结对象,这个方法是在Object.seal(...)
的基础上对所有属性标记writable: false
。意味着这个对象无法对所有直接属性进行修改。
注意:以上方法创建的都是浅不变形,意思是它们只会影响目标对象和直接属性。如果目标对象引用了其他对象(数组、对象、函数等),其他对象内容不受影响,仍然是可变的
主要看属性值
存储的方式(简单类型存储在栈内存
,复杂类型存储在堆内存
),对象的属性只是相当于指针,指向值存储的位置。所以引申一下,delete
删除对象属性,并不会释放内存,只是一个操作而已。
var foo = {a: 1};
var arr = [1]
var myObj2 = {a: 1, b: 2, foo, arr};
Object.freeze(myObj2);
// 引用属性不受影响,仍然可变
arr.push(2);
foo.b = 2;
// 直接属性无法修改
myObj2.a = 3;
console.log(Object.isExtensible(myObj2)); // false
console.log(myObj2) // {"a":1,"b":2,"foo":{"a":1,"b":2},"arr":[1,2]}
深度冻结一个对象,即在一个对象上调用Object.freeze(...)
,然后遍历它引用的所有对象并在这些对象上调用Object.freeze(...)
。有一定风险,可能会无意中冻结其他(共享)对象。所以最好不要使用。
(function deepFreeze(obj){
Object.freeze(obj);
for(var prop in obj){
if(obj[prop] && ((typeof obj[prop] === "object") || (typeof obj[prop] === 'function'))){
deepFreeze(obj[prop])
}
}
})(myObj2)
console.log(Object.isExtensible(myObj2)); // false
arr.push(2) // 会报TypeError,Cannot add property 1, object is not extensible
-
访问器属性
1.
Configurable
:同上-
Enumerable
: 同上 -
getter
:读取属性时调用的函数。默认值为undefined -
setter
:写入属性时调用的函数。默认值为undefined
-
var obj = {
// 给a定义一个getter
get a() {
return 2;
}
};
obj.a = 3;
console.log(obj.a) // 2
obj
对属性a进行赋值会失败,首先是并没有对属性a进行set赋值操作;然而即使有了合法的setter,也只会返回2,所以getter这么定义是没有意义的。
为何需要访问器属性或者讲什么时候使用访问器属性?
(1)当属性之间有关联,或者需要对属性进行一些额外操作时
let machine = {
id: 1010,
get num() {
return `${this.id}-${this.id}`
},
set num(id) {
this.id = id
}
}
machine.id= 1201;
console.log(machine.num) // 1201-1201
(2) 实现数据的私有化
// (1)利用块作用域
{
let engine= "Ⅰ类型";
var machine = {
id: "1001",
get type() {
return engine + this.id
},
set type(val) {
engine = val
}
}
}
var engine = "Ⅲ类型";
machine.type = "Ⅱ类型";
console.log(machine.type); // Ⅱ类型1001
// (2)利用函数
function getMachine() {
var engine = "一类型";
return {
id: "1001",
get type() {
return engine + this.id
},
set type(val) {
engine = val
}
}
}
let machine1 = getMachine();
console.log(machine1.type) // 一类型1001
(3)项目后期维护需要对某个对象属性进行格式统一修改,为了最大程度减少工作量,可以将该属性改为访问器属性。举例:更改日期格式,由原来的yyyy.MM.dd改为yyyy-MM-dd
let obj = { date:"2018.08.08"};
// 更改后的版本
let editedObj = {
_date : "2018.08.08",
get date(){
return this._date.split(".").join("-")
},
set date(time){
this._date = time
}
}
扩展:
let myObj = { a: 1, b: undefined };
console.log(myObj.b); // undefined
console.log(myObj.c); // undefined
当属性返回值为undefined
时,有可能是属性中存储的undefined
,也有可能属性不存在,返回undefined
。所以如何判断属性是否存在?
当属性访问时,会先在对象中查找是否有名称相同的属性,如果找到即返回这个属性的值;如果查找不到,会遍历可能存在的[[prototype]]链,也就是原型链
。如果仍然没有,才会返回 undefined
。
两种方法:in
操作符和 hasOwnProperty()
检查对象中是否包含某个属性
无论属性是否枚举,只要存在,都会返回true
。区别只在于是否查找原型链
。
// in 操作符 会检查属性名称是否存在对象及其原型链中
console.log("b" in myObj) // true
console.log("c" in myObj) // false
// hasOwnProperty(...)只会检查属性名称是否在对象自身上
myObj.hasOwnProperty("c") // false
注意: in 操作符是检查对象的属性名称是否存在,在数组中 3 in [2, 3, 6]
中会返回false
,属性3在数组中指的是索引。