这篇博文中着重讲解一个Object对象的静态方法——
Object.defineProperty()
,它可以在一个对象上定义一个新属性,或者修改一个已经存在的属性。
存在很高的自定义性,灵活运用可满足我们很多实际需求。属于Object对象的高级知识。
Object.defineProperty(obj, prop, descriptor);
控制该属性是否可写。当且仅当该属性为 true
时,属性的值(value)才能被赋值运算符( = )改变。
默认为 false
。
该属性对应的键值。可以是任何JS数据类型的有效值(Number,String,Boolean,Array,Object,Function等)。
默认为 undefined
。
属性的 getter 函数。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。
默认为 undefined
。
属性的 setter 函数。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。
默认为 undefined
。
控制该属性除 value 和 writable 特性外的其他特性是否可以被修改。只有当configurable = true
时,该描述符内的配置键值对才会起作用,同时该键值对还控制着属性是否可以被删除(delete语句)。
默认为 false。
控制该属性是否是可枚举的。只有当enumerable = true
时,该属性才会被for-in
循环遍历出来或者键名出现在Object.keys()
的返回数组中。
数据描述符键值对和存取描述符键值对都可以搭配共享的可选键值对组成完整的数据描述符或者存取描述符。切记数据描述符键值对和存取描述符键值对是不能同时出现在一个描述符里的,否则会报错。
之前使用的默认赋值其实是数据描述符的等价形式。默认配置为:
var obj = {
};
obj.name = "zevin";
//等价于:
Object.defineProperty(obj,"name",{
configurable:true, //是否可配置的 + 可删除的
enumerable:true, //是否可枚举
writable:true, //是否可写
value:"zevin" //属性值
});
当然也可以省略其中某些键值对(这些键值对将使用它们的默认值)。
Object.defineProperty(obj,"name",{
writable:true, //是否可写
value:"zevin" //属性值
});
需要自定义set和get方法。
var obj = {
};
Object.defineProperty(obj,"name",{
configurable:true, //是否可配置的 + 可删除的
enumerable:true, //是否可枚举
set:function(par){
},
get:function(){
}
});
比如我们希望对象obj新增的属性"name"有以下需求:
满足这个需求应该使用数据描述符,直接对应enumerable
键值对。
var obj = {
age = 21;
};
Object.defineProperty(obj,"name",{
configurable:true, //是否可配置的 + 可删除的
enumerable:false, //是否可枚举
writable:true, //是否可写
value:"zevin" //属性值
});
我们可以发现,正常打印obj对象,刚刚添加的"name"属性并没有输出,for-in循环也遍历不出来,相当于添加了一个隐藏属性。但是我们仍然可以访问到。
console.log(obj); // {age:21}
for(var key in obj){
console.log(key,obj[key]); // age 21
};
console.log(obj.name); // "zevin"
属性只读需要设置数据描述符里的writable:false
.
var obj = {
age = 21;
};
Object.defineProperty(obj,"name",{
configurable:true, //是否可配置的 + 可删除的
enumerable:true, //是否可枚举
writable:false, //是否可写
value:"zevin" //属性值
});
obj.name = "code";
console.log(obj.name); // "zevin"
上述代码这么写在正常模式里不会报错,并且改变属性值的赋值语句无效。但在严格模式中会报Cannot assign to read only property 'name' of object
的错误。
这一部分跟严格模式关联性比较大,就小提一下严格模式,之后另立新帖。
(function() {
'use strict';
var obj = {
};
Object.defineProperty(obj, 'name', {
value: "zevin",
writable: false
});
obj.name = "code";
// throws TypeError: "name" is read-only
return obj.name; // "zevin"
}());
设置不可删除后,发现delete
输出false
,我们仍然可以访问到"name"的属性值。严格模式下删除一个不可配置属性仍然会显式报错。就不代码演示了。
var obj = {
};
Object.defineProperty(obj,"name",{
configurable:false, //是否可配置的 + 可删除的
enumerable:true, //是否可枚举
writable:false, //是否可写
value:"zevin" //属性值
});
console.log(delete obj.name);
console.log(obj.name);
——————OUTPUT——————
false
zevin
下滑线开头的属性一般定义的是内置属性,是不允许轻易访问的,通常会对外暴露一个同名无下划线的属性age,_age属性其实是age属性的真正存储位置。
var obj = {
_age:21
};
Object.defineProperty(obj,"age",{
set:function(par){
console.log("长大了一岁"); // 添加钩子函数,输出监测结果
this._age = par;
},
get:function(){
return this._age;
},
configurable:false, //是否可删除的
enumerable:true, //是否可枚举
});
obj.age++; // 外部第一次修改age属性值
obj.age++; // 外部第二次修改age属性值
obj.age++; // 外部第三次修改age属性值
console.log(obj.age);
console.log(obj);
——————OUTPUT——————
长大了一岁
长大了一岁
长大了一岁
24
{
_age:24,age:[Getter/Setter]}
从结果来看,外部每次修改age值,我们都监测到了。
当我们通过点访问符定义一个属性后,我们仍然可以使用Object.defineProperty()方法来修改该属性的配置。比如:
var obj = {
};
obj.name = "zevin";
// 等价于:
Object.defineProperty(obj,"name",{
configurable: true,
enumerable: true,
writable: true,
value: "zevin"
});
//修改"name"属性配置
Object.defineProperty(obj,"name",{
enumerable:false,
value:"code"
});
console.log(obj);
console.log(obj.name);
——————OUTPUT——————
{
}
code
当这个属性直接是由Object.defineProperty()方法定义的时候,它的等价形式跟点访问符定义的形式完全不同。再用Object.defineProperty()方法修改的时候,就会有各种限制:
var obj = {
};
Object.defineProperty(obj,"name",{
value: "zevin"
});
// 等价于:
Object.defineProperty(obj,"name",{
configurable:false,
enumerable:false,
writable:false,
value: "zevin"
});
console.log(obj); // 不可枚举
obj.name = "code"; // 不可写
console.log(delete obj.name); // 不可删除
console.log(obj.name); // 可以访问
——————OUTPUT——————
{
}
false
zevin
等价形式默认是数据描述符形式,所以我们可以修改的特性就有:configurable
,enumerable
,writable
,value
四个。configurable
特性是控制该属性除 value
和 writable
特性外的其他特性是否可以被修改。所以当configurable:false
时,我们仍然可以修改 value
和 writable
,修改configurable
,enumerable
就会直接报错。
我们依次试试看:
var obj = {
};
Object.defineProperty(obj,"name",{
value: "zevin"
});
// 修改"name"属性的value配置
Object.defineProperty(obj,"name",{
value: "code"
});
console.log(obj.name);
——————OUTPUT——————
code
configurable:false
的条件下,修改writable
特性只能从true改成false,其他修改形式会报错。var obj = {
};
Object.defineProperty(obj,"name",{
value: "zevin"
});
// 修改"name"属性的writable配置
// 由默认值false改成true会报错
Object.defineProperty(obj,"name",{
writable:true
});
var obj = {
};
Object.defineProperty(obj,"name",{
writable:true,
value: "zevin"
});
// 修改"name"属性的writable配置
// 由true改成false不会报错
Object.defineProperty(obj,"name",{
writable:false
});
obj.name = "code"; // 不可写
console.log(obj.name); // 可以访问
——————OUTPUT——————
zevin
var obj = {
};
Object.defineProperty(obj,"name",{
value: "zevin"
});
// 报错 throws a TypeError
Object.defineProperty(obj,"name",{
configurable:true
});
enumerable
特性就会直接报错。var obj = {
};
Object.defineProperty(obj,"name",{
value: "zevin"
});
// 报错 throws a TypeError
Object.defineProperty(obj,"name",{
enumerable: true
});
当我们直接定义一个get函数时,描述符的类型就转换成了存取描述符,它的等价形式如下:
var obj = {
};
Object.defineProperty(obj,"name",{
get() {
return 1; }
});
// 等价形式
Object.defineProperty(obj,"name",{
get() {
return 1; },
set:undefined,
configurable:false,
enumerable:false
});
对应的可修改的属性变成了:get
,set
,configurable
,enumerable
。
var obj = {
};
Object.defineProperty(obj,"name",{
get() {
return 1; }
});
// 报错 throws a TypeError
Object.defineProperty(obj,"name",{
get() {
return 1; }
});
var obj = {
};
Object.defineProperty(obj,"name",{
get() {
return 1; }
});
// 报错 throws a TypeError (set was undefined previously)
Object.defineProperty(obj,"name",{
set() {
}
});
var obj = {
};
Object.defineProperty(obj,"name",{
get() {
return 1; }
});
// 报错 throws a TypeError
Object.defineProperty(obj,"name",{
configurable: true
});
var obj = {
};
Object.defineProperty(obj,"name",{
get() {
return 1; }
});
// 报错 throws a TypeError
Object.defineProperty(obj,"name",{
enumerable: true
});
如果描述符修改不属于自己的特性都会报错,比如:在存取描述符中修改value值会报错,但是在数据描述符中就可以正常修改。
Object.defineProperty()方法名变 y 为 i 加 es,就可以完成批量添加属性的操作!!
Object.defineProperties(obj, props);
var obj = {
};
Object.defineProperties(obj, {
'property1': {
value: "zevin",
writable: true
},
'property2': {
get(){
}
}
// etc. etc.
});